SwiftlyS2
Development

Native Functions and Hooks

This page shows how to get, call and hook a native function by its address or signature.

Get the function

Get signature from gamedata

You can get the signature from gamedata by using Core.Memory.GetSignatureFromGamedata method. Example:

var signature = Core.GameData.GetSignature("CBaseEntity::DispatchSpawn");

Get a function address by signature

To get an address by signature, you can use Core.Memory.GetAddressBySignature method. Example:

var address = Core.Memory.GetAddressBySignature(Library.Server, "55 8B EC 83 EC 08 8B 45 08 5D C3");

Declaring function type delegate

Before you get an unmanaged native function, you must declare a delegate type for the function. Example:

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

This is the delegate type for CBaseEntity::DispatchSpawn function.

Get function from address

You can get a memory function by using Core.Memory.GetUnmanagedFunctionByAddress method.

var func = Core.Memory.GetUnmanagedFunctionByAddress<DispatchSpawnDelegate>(address);

Get virtual function by vtable pointer and offset

You can get a virtual function by using Core.Memory.GetVirtualFunctionByVTablePointerAndOffset method.

var func = Core.Memory.GetUnmanagedFunctionByVTable<DispatchSpawnDelegate>(*(void**)pObject, 15);

Call the function

Example:

var result = func.Call(1337, 0xDEADBEEF);

This example calls a function with two parameters.

With .Call, it will call the present function address, which might be hooked by other mods.

If you want to call the original function, you can use .CallOriginal.

Hook the function

Example:

var guid = func.AddHook((next) => {
  // You shouldn't write anything here!
  return (pEntity, pKV) => {
    // pre code
    Console.WriteLine("TestPlugin DispatchSpawn Pre");
    var result = next()(pEntity + 1, pKV); // modify the parameters and call the original function
    Console.WriteLine("TestPlugin DispatchSpawn Post");
    // post code
    return result;
  };
});

This example hooks the function and prints a message before the original function is called.

The next parameter is a getter of the original function.

Writing code before the call to the original function means it will be executed before the original function, and the same applies to post code.

If you want to cancel the call, you can just not call the original function.

All hooks will be automatically destroyed when the plugin is unloaded.

Unhooking the function

You can unhook the function by using func.RemoveHook method. Example:

func.RemoveHook(guid);

This example removes the hook from the function.

Hook an address

First, get an unmanaged memory object for the address you want to hook:

var memobj = core.Memory.GetUnmanagedMemoryByAddress(core.GameData.GetSignature("CBaseEntity::DispatchSpawn"));

Then add a mid-hook callback. The callback receives a MidHookContext structure that provides access to all CPU registers:

var hookId = memobj.AddHook((ref MidHookContext ctx) =>
{
    // Read register values
    Console.WriteLine($"MidHooked DispatchSpawn, RBX: {ctx.RBX}");

    // You can also modify registers
    ctx.RAX = 0x1337;
});

The MidHookContext structure provides access to:

  • General Purpose Registers: RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, R8-R15
  • XMM Registers: XMM0-XMM15 (SIMD registers for floating-point and vector operations)
  • Special Registers: RFLAGS, RIP, TRAMPOLINE_RSP

You can add multiple hooks to the same address. They will execute in the order they were added:

var memobj = core.Memory.GetUnmanagedMemoryByAddress(core.GameData.GetSignature("CBaseEntity::DispatchSpawn"));

memobj.AddHook((ref MidHookContext ctx) =>
{
    Console.WriteLine($"MidHooked DispatchSpawn, RBX: {ctx.RBX}");
});

memobj.AddHook((ref MidHookContext ctx) =>
{
    Console.WriteLine($"MidHooked2 DispatchSpawn, RBX: {ctx.RBX}");
});

memobj.AddHook((ref MidHookContext ctx) =>
{
    Console.WriteLine($"MidHooked3 DispatchSpawn, RBX: {ctx.RBX}");
});

Mid-hooks are powerful but potentially dangerous. Modifying registers incorrectly can crash the game. Always test thoroughly and understand the assembly code at the hook location.

Unhook an address

To remove a mid-hook, use the RemoveHook method with the hook's GUID that was returned when you added it:

// Add a hook and store its ID
var hookId = memobj.AddHook((ref MidHookContext ctx) =>
{
    Console.WriteLine($"This hook will be removed later");
});

// Later, remove the hook
memobj.RemoveHook(hookId);

All hooks will be automatically destroyed when the plugin is unloaded.

Reference

See IMemoryService.

See IUnmanagedFunction.

See IUnmanagedMemory.

See Library.

On this page