Skip to main content

Overview

The IGameInteropProvider service is the recommended high-level interface for interacting with game memory. It provides a unified way to create hooks, resolve signatures, and manage the lifecycle of game interop operations.

Getting Started

Basic Usage

using Dalamud.Plugin.Services;
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;

public class MyPlugin : IDalamudPlugin
{
    private readonly IGameInteropProvider gameInterop;
    
    public MyPlugin(IGameInteropProvider gameInterop)
    {
        this.gameInterop = gameInterop;
    }
    
    public void Initialize()
    {
        // Use InitializeFromAttributes for all [Signature] fields
        this.gameInterop.InitializeFromAttributes(this);
    }
}

InitializeFromAttributes

The most powerful feature of IGameInteropProvider is automatic initialization of signature-decorated members:
public class GameInteraction
{
    // Automatically resolved addresses
    [Signature("48 8D 0D ?? ?? ?? ??", ScanType = ScanType.StaticAddress)]
    private readonly nint playerDataPointer;
    
    // Automatically created delegates
    private delegate nint GetPlayerDelegate(nint playerObj);
    
    [Signature("E8 ?? ?? ?? ?? 48 85 C0 74 ??")]
    private readonly GetPlayerDelegate? getPlayer;
    
    // Automatically created hooks
    private delegate void UpdateDelegate(nint obj, float deltaTime);
    
    [Signature(
        "40 53 48 83 EC 20 F3 0F",
        DetourName = nameof(UpdateDetour)
    )]
    private Hook<UpdateDelegate>? updateHook;
    
    public void Initialize(IGameInteropProvider interop)
    {
        // One call resolves everything
        interop.InitializeFromAttributes(this);
        
        // Enable hooks
        this.updateHook?.Enable();
    }
    
    private void UpdateDetour(nint obj, float deltaTime)
    {
        PluginLog.Debug($"Update called: dt={deltaTime}");
        this.updateHook!.Original(obj, deltaTime);
    }
    
    public void Dispose()
    {
        this.updateHook?.Dispose();
    }
}

Supported Member Types

InitializeFromAttributes can handle:
  1. IntPtr/nint fields: Resolved signature addresses
  2. Delegate fields: Function pointers as callable delegates
  3. Hook fields: Automatically created hooks with detours
public class AttributeExamples
{
    // Type 1: Raw address
    [Signature("48 8B 05 ?? ?? ?? ??")]
    private nint functionAddress;
    
    // Type 2: Delegate
    private delegate void MyFunctionDelegate(int param);
    
    [Signature("40 53 48 83 EC 20")]
    private MyFunctionDelegate? myFunction;
    
    // Type 3: Hook
    [Signature("E8 ?? ?? ?? ??", DetourName = nameof(MyDetour))]
    private Hook<MyFunctionDelegate>? myHook;
    
    private void MyDetour(int param)
    {
        myHook!.Original(param);
    }
}

Creating Hooks

HookFromAddress

Create a hook from a memory address:
public class HookExample
{
    private delegate void SomeDelegate(nint param);
    private Hook<SomeDelegate>? myHook;
    
    public void CreateHook(IGameInteropProvider interop, nint address)
    {
        this.myHook = interop.HookFromAddress<SomeDelegate>(
            address,
            this.Detour
        );
        
        this.myHook.Enable();
    }
    
    private void Detour(nint param)
    {
        PluginLog.Info($"Hooked: {param:X}");
        this.myHook!.Original(param);
    }
}

HookFromSignature

Create a hook directly from a signature:
public void CreateFromSignature(IGameInteropProvider interop)
{
    var signature = "E8 ?? ?? ?? ?? 48 8B 4C 24 ??";
    
    this.myHook = interop.HookFromSignature<SomeDelegate>(
        signature,
        this.Detour
    );
    
    this.myHook.Enable();
}

HookFromSymbol

Hook exported functions from DLLs:
private delegate int SendDelegate(
    nint socket,
    nint buffer,
    int length,
    int flags
);

public void HookWinSock(IGameInteropProvider interop)
{
    var hook = interop.HookFromSymbol<SendDelegate>(
        "ws2_32.dll",
        "send",
        this.SendDetour
    );
    
    hook.Enable();
}

private int SendDetour(nint socket, nint buffer, int length, int flags)
{
    PluginLog.Info($"Sending {length} bytes");
    return this.myHook!.Original(socket, buffer, length, flags);
}

HookFromImport

Hook via import table:
using System.Diagnostics;

public void HookImport(IGameInteropProvider interop)
{
    var module = Process.GetCurrentProcess().MainModule;
    
    var hook = interop.HookFromImport<SendDelegate>(
        module,
        "ws2_32.dll",
        "send",
        0, // hint/ordinal
        this.SendDetour
    );
    
    hook.Enable();
}

HookFromFunctionPointerVariable

Hook by replacing a function pointer variable:
public void HookPointerVariable(IGameInteropProvider interop, nint ptrAddress)
{
    var hook = interop.HookFromFunctionPointerVariable<SomeDelegate>(
        ptrAddress,
        this.Detour
    );
    
    hook.Enable();
}

Hook Backends

Choose between Reloaded (default) and MinHook:
public void ChooseBackend(IGameInteropProvider interop, nint address)
{
    // Automatic (recommended)
    var hook1 = interop.HookFromAddress<SomeDelegate>(
        address,
        this.Detour,
        IGameInteropProvider.HookBackend.Automatic
    );
    
    // Explicitly use Reloaded
    var hook2 = interop.HookFromAddress<SomeDelegate>(
        address,
        this.Detour,
        IGameInteropProvider.HookBackend.Reloaded
    );
    
    // Use MinHook (only if Reloaded doesn't work)
    var hook3 = interop.HookFromAddress<SomeDelegate>(
        address,
        this.Detour,
        IGameInteropProvider.HookBackend.MinHook
    );
}
Always prefer HookBackend.Automatic or HookBackend.Reloaded unless you have a specific reason to use MinHook.

Complete Example

Here’s a complete plugin using IGameInteropProvider:
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;

public class MyPlugin : IDalamudPlugin
{
    public string Name => "My Plugin";
    
    private readonly IGameInteropProvider gameInterop;
    
    // Static data addresses
    [Signature(
        "48 8D 0D ?? ?? ?? ??",
        ScanType = ScanType.StaticAddress
    )]
    private readonly nint localPlayerPointer;
    
    [Signature(
        "48 8B 05 ?? ?? ?? ??",
        ScanType = ScanType.StaticAddress
    )]
    private readonly nint targetManagerPointer;
    
    // Function pointers as delegates
    private delegate nint GetTargetDelegate(nint targetManager);
    
    [Signature("E8 ?? ?? ?? ?? 48 8B D8 48 85 C0 74 ??")]
    private readonly GetTargetDelegate? getTarget;
    
    // Hooks
    private delegate void UpdatePlayerDelegate(nint player, float deltaTime);
    
    [Signature(
        "40 53 48 83 EC 20 0F 29 74 24",
        DetourName = nameof(UpdatePlayerDetour)
    )]
    private Hook<UpdatePlayerDelegate>? updatePlayerHook;
    
    [Signature(
        "48 89 5C 24 08 48 89 74 24",
        DetourName = nameof(ProcessActionDetour),
        Fallibility = Fallibility.Fallible
    )]
    private Hook<ProcessActionDelegate>? processActionHook;
    
    private delegate void ProcessActionDelegate(nint manager, int actionId);
    
    public MyPlugin(
        DalamudPluginInterface pluginInterface,
        IGameInteropProvider gameInterop)
    {
        this.gameInterop = gameInterop;
        
        // Initialize all [Signature] members
        this.gameInterop.InitializeFromAttributes(this);
        
        // Enable hooks
        this.updatePlayerHook?.Enable();
        this.processActionHook?.Enable();
        
        // Use resolved addresses and delegates
        this.CheckTarget();
    }
    
    private void CheckTarget()
    {
        if (this.getTarget != null)
        {
            var target = this.getTarget(this.targetManagerPointer);
            PluginLog.Info($"Current target: {target:X}");
        }
    }
    
    private void UpdatePlayerDetour(nint player, float deltaTime)
    {
        // Custom logic
        if (player == this.localPlayerPointer)
        {
            PluginLog.Verbose($"Player update: dt={deltaTime:F3}");
        }
        
        // Call original
        this.updatePlayerHook!.Original(player, deltaTime);
    }
    
    private void ProcessActionDetour(nint manager, int actionId)
    {
        PluginLog.Info($"Action used: {actionId}");
        
        try
        {
            // Your custom logic
            this.HandleAction(actionId);
        }
        catch (Exception ex)
        {
            PluginLog.Error(ex, "Error in action processing");
        }
        
        // Always call original
        this.processActionHook!.Original(manager, actionId);
    }
    
    private void HandleAction(int actionId)
    {
        // Custom action handling
    }
    
    public void Dispose()
    {
        this.updatePlayerHook?.Dispose();
        this.processActionHook?.Dispose();
    }
}

Best Practices

Organization

Group related signatures in separate classes:
public class PlayerFunctions
{
    [Signature("48 8D 0D ?? ?? ?? ??", ScanType = ScanType.StaticAddress)]
    public readonly nint LocalPlayerAddress;
    
    private delegate void UpdateDelegate(nint player, float dt);
    
    [Signature("40 53 48 83 EC 20", DetourName = nameof(UpdateDetour))]
    private Hook<UpdateDelegate>? updateHook;
    
    public void Initialize(IGameInteropProvider interop)
    {
        interop.InitializeFromAttributes(this);
        this.updateHook?.Enable();
    }
    
    private void UpdateDetour(nint player, float dt)
    {
        this.updateHook!.Original(player, dt);
    }
    
    public void Dispose()
    {
        this.updateHook?.Dispose();
    }
}

public class CombatFunctions
{
    // Combat-related signatures and hooks
}

Error Handling

Use fallible signatures for optional features:
public class OptionalFeatures
{
    [Signature(
        "E8 ?? ?? ?? ??",
        Fallibility = Fallibility.Fallible
    )]
    private readonly nint? experimentalFunction;
    
    public void Initialize(IGameInteropProvider interop)
    {
        interop.InitializeFromAttributes(this);
        
        if (this.experimentalFunction.HasValue)
        {
            PluginLog.Info("Experimental feature available");
        }
        else
        {
            PluginLog.Warning("Experimental feature not found");
        }
    }
}

Initialization Order

Initialize dependencies in the correct order:
public class MyPlugin
{
    public MyPlugin(IGameInteropProvider interop)
    {
        // 1. Initialize attributes first
        interop.InitializeFromAttributes(this);
        
        // 2. Validate critical addresses
        if (this.criticalAddress == nint.Zero)
        {
            throw new Exception("Failed to find critical signature");
        }
        
        // 3. Enable hooks
        this.myHook?.Enable();
        
        // 4. Use resolved addresses
        this.SetupWithAddresses();
    }
}

Build docs developers (and LLMs) love