swiftlys2/docs/api/ai/

Native Functions and Hooks

This is low-level, unsafe territory. A wrong calling convention, a mismatched delegate signature, or a bad register edit in a mid-hook can crash the game server. Test on a disposable server first.

Native interop splits across two services: Core.GameData (APIIGameDataService) resolves named signatures/offsets shipped with your plugin, Core.Memory (APIIMemoryService) turns raw addresses into callable, hookable functions (or raw memory to mid-hook directly).

Resolving Addresses

From Your Plugin's GameData

Each plugin ships its own resources/gamedata/*.jsonc with signatures/offsets per platform, keyed by name. Look them up with TryGetSignature/TryGetOffset:

if (!Core.GameData.TryGetSignature("CBaseEntity::DispatchSpawn", out nint dispatchSpawnAddress))
{
    Core.Logger.LogWarning("Signature not found");
    return;
}
 
if (!Core.GameData.TryGetOffset("CCSPlayer_ItemServices::GiveNamedItem", out nint giveNamedItemOffset))
{
    return;
}

From a Raw Pattern or VTable Name

Core.Memory can also scan a library directly, skipping gamedata entirely:

nint? patternAddress = Core.Memory.GetAddressBySignature(Library.Server, "55 8B EC 83 EC 08 8B 45 08 5D C3");
nint? vtableAddress = Core.Memory.GetVTableAddress(Library.Server, "CCSPlayer_ItemServices");
nint? interfaceAddress = Core.Memory.GetInterfaceByName("VEngineServer");

APILibrary exposes the module names used by these lookups: Library.Engine, Library.Tier0, Library.Server, Library.NetworkSystem.

Declaring a Delegate

Wrapping a native function needs a delegate matching its exact signature and calling convention:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate nint DispatchSpawnDelegate(nint pEntity, nint pKeyValues);

Wrapping and Calling It

Wrap by address or by vtable + index; the resulting APIIUnmanagedFunction`1 exposes two delegate-typed members: Call (goes through any installed hooks) and CallOriginal (skips the hook chain).

IUnmanagedFunction<DispatchSpawnDelegate> func =
    Core.Memory.GetUnmanagedFunctionByAddress<DispatchSpawnDelegate>(dispatchSpawnAddress);
// or: Core.Memory.GetUnmanagedFunctionByVTable<DispatchSpawnDelegate>(vtableAddress.Value, 15);
nint result = func.Call(pEntity, pKeyValues);
nint resultUnhooked = func.CallOriginal(pEntity, pKeyValues);

Hooking It

AddHook takes a builder that receives "the next function in the chain" and must return your replacement delegate. Skipping next() skips the original call entirely.

Guid hookId = func.AddHook(next =>
{
    return (pEntity, pKeyValues) =>
    {
        // pre logic
        nint result = next()(pEntity, pKeyValues);
        // post logic
        return result;
    };
});
 
// later
func.RemoveHook(hookId);

Hooks are cleaned up automatically on unload, but removing them yourself once done is still good practice.

Mid-Function Hooks

When hooking function entry/exit isn't precise enough, Core.Memory.GetUnmanagedMemoryByAddress wraps an arbitrary address in an APIIUnmanagedMemory so you can hook mid-function, with read/write access to the CPU registers at that point:

IUnmanagedMemory mem = Core.Memory.GetUnmanagedMemoryByAddress(dispatchSpawnAddress);
 
Guid midHookId = mem.AddHook((ref MidHookContext ctx) =>
{
    Console.WriteLine($"RIP=0x{ctx.RIP:X}, RAX=0x{ctx.RAX:X}");
    ctx.RAX = 0x1337; // example register edit
});
 
// later
mem.RemoveHook(midHookId);

See APIMidHookContext for the full register list - every general-purpose register (RAX-R15, RSP, RBP, RIP, RFLAGS, plus the internal TRAMPOLINE_RSP) and XMM0-XMM15 as Xmm structs (.U32/.U64/.F32/.F64).

Higher-Level Alternative: Game Hooks

For common engine callbacks, Core.GameHooks (APIIGameHooks) wraps the vtable-hook plumbing above into typed categories with separate Pre/Post delegates - no manual delegate/signature bookkeeping. Example, hooking entity damage via APIITakeDamageEntityHook:

private void OnTakeDamagePre(ref TakeDamageEntityPreContext ctx)
{
    var entity = ctx.Params.Entity;
    ref var info = ref ctx.Params.Info;
 
    // ctx.SetHookResult(HookResult.Stop); // to block the damage
}
 
// Register in Load():
Core.GameHooks.Entities.TakeDamage.Pre += OnTakeDamagePre;

Check Core.GameHooks's categories (Controller, Movement, Entities, Items, Weapons, Pawn, and more) before reaching for a raw vtable hook.