HookDOTS API

v1.1.1·Updated 2025-10-22

Provides an API for mods to hook Unity DOTS systems.

About this mod Author’s original text

HookDOTS API

HookDOTS is a modding library for hooking Unity DOTS games. By itself, it doesn't do anything.

Why use HookDOTS?

Harmony is an excellent tool, but it fails at one important thing: Hooking unmanaged systems in IL2CPP builds.\

HookDOTS was created to solve this problem.

Notable attributes provided (for plugin developers):

  • [EcsSystemUpdatePrefix(typeof(EquipItemSystem))]
  • [EcsSystemUpdatePostfix(typeof(EquipItemSystem))]
  • [WhenCreatedWorldsContainAny("Server", "Client_0")]

Installation

  • Install BepInEx
  • Extract HookDOTS.API.dll into (VRising folder)/BepInEx/plugins

Usage

For Server Operators

By itself, HookDOTS doesn't do anything. It is required by other plugins and should be kept up to date.

For Plugin Developers

How to use

  • Add a reference to the plugin.

dotnet add package HookDOTS.API -v 1.*

  • Add the API plugin as a dependency via the BepInDependency attribute on your plugin class.

[BepInPlugin(MyPluginInfo.PLUGIN_GUID, MyPluginInfo.PLUGIN_NAME, MyPluginInfo.PLUGIN_VERSION)]

[BepInDependency("HookDOTS.API")]

public class ExamplePlugin: BasePlugin

  • Register hooks in your plugin's Load method.

public override void Load()

{

//... other initialization code

// Register your plugin's hooks with HookDOTS

_hookDOTS = new HookDOTS.API.HookDOTS(MyPluginInfo.PLUGIN_GUID, Log);

_hookDOTS.RegisterAnnotatedHooks();

}

  • Teardown in your plugin's Unload method.

public override bool Unload()

{

//... other unloading code

_hookDOTS.Dispose();

return true;

}

  • Set up prefix and postifx hooks for System OnUpdate calls. Both managed and unmanaged systems can be hooked.

public class ExamplePatch

{

// The hook must be public and static.

// If you want SystemState* passed in, an unsafe context is also required.

[EcsSystemUpdatePrefix(typeof(EquipItemSystem))]

unsafe public static bool ExamplePrefix(SystemState* systemState)

{

var world = systemState->World;

ExamplePlugin.LogInstance.LogInfo($"ExamplePrefix executing in world {world.Name}.");

// You can return false, to skip the hooked OnUpdate. Other prefix hooks will still run.

bool shouldSkipTheOriginal = true;

return!shouldSkipTheOriginal; // this will be returned, and is false. Therefore the original will be skipped.

}

[EcsSystemUpdatePrefix(typeof(EquipItemSystem))]

unsafe public static bool ExamplePrefixNoSkip(SystemState* systemState)

{

// Prefer to use the AfterDetours constants instead of true/false.

// return AfterDetours.SkipOriginalMethod;

return AfterDetours.OkToRunOriginalMethod;

}

[EcsSystemUpdatePostfix(typeof(EquipItemSystem))]

unsafe public static void ExamplePostfix(SystemState* systemState)

{

var world = systemState->World;

ExamplePlugin.LogInstance.LogInfo($"ExamplePostfix executing in world {world.Name}.");

// Unlike a prefix hook, a postfix hook cannot return false to skip anything.

// The only valid return type is void.

}

}

onlyWhenSystemRuns

You can set onlyWhenSystemRuns to false, and the hook will be called even if the system doesn't actually run.

[EcsSystemUpdatePrefix(typeof(EquipItemSystem), onlyWhenSystemRuns: false)]

public static void ExamplePrefix2()

{

// (this is commented out, because the log would be spammed every frame)

// ExamplePlugin.LogInstance.LogInfo($"ExamplePrefix2 executing");

}

Throttling

If you're using hooks to dump information and don't want to be flooded every frame, the Throttle attribute can be used.

// You can specify days, hours, minutes, seconds, and milliseconds to define the throttle interval.

// Internally, these are used to create a System.TimeSpan

[Throttle(seconds: 10)]

[EcsSystemUpdatePrefix(typeof(EquipItemSystem), onlyWhenSystemRuns: false)]

public static void ExamplePrefixThrottled()

{

ExamplePlugin.LogInstance.LogInfo($"ExamplePrefixThrottled executing (throttled to once every 10 seconds)");

}

Alternative ways to register hooks

Procedurally, using a HookRegistrar:

var hookDOTS = new HookDOTS.API.HookDOTS(MyPluginInfo.PLUGIN_GUID, Log);

//...

var hook =

Install & use Install: BepInEx plugin

  1. The archive from playbuff is the mod itself — usually a BepInEx plugin (a .dll or a plugin folder once unzipped).
  2. Recommended: use a community mod manager (such as r2modman) — pick this game, import the mod, and the manager sets up BepInEx and its dependencies for you.
  3. Manual route: install the BepInEx loader in the game folder, then drop the unzipped .dll or plugin folder into BepInEx/plugins/.
  4. Launch the game — the BepInEx console logs every plugin it loads; seeing this mod in the log means it is installed.

Mods with listed dependencies need those installed too, or they will not load. Options are usually changed in-game or in the plugin's config file.

Dependencies

Install these first, or the mod will not work:

BepInEx-BepInExPack_V_Rising

File & checksum

Filename
HookDOTS_API-1.1.1.zip
File size
122.8 KiB