Skip to main content

Overview

Native game functions are represented by partial methods with attributes that tell the source generator how to create wrappers. The library uses function pointers for all native calls, providing minimal managed↔unmanaged overhead.

Function Types

There are three main types of native functions:
  1. MemberFunction - Non-virtual member functions (including static functions)
  2. VirtualFunction - Virtual member functions (called via vtable)
  3. StaticAddress - Static singleton instances and global pointers

MemberFunction

Basic Usage

For non-virtual member functions and static functions:
[MemberFunction("E8 ?? ?? ?? ?? C1 E7 0C")]
public partial void AddEvent(AtkEventType eventType, uint eventParam, 
    AtkEventListener* listener, AtkResNode* nodeParam, bool isGlobalEvent);

Static Functions

Mark the method as static if it’s a static function:
[MemberFunction("E8 ?? ?? ?? ?? 66 45 2B E6")]
public static partial float GetGlobalUIScale();

How It Works

The generator creates a wrapper that:
  1. Resolves the function address from the signature
  2. Checks if the address is valid
  3. Calls the function via function pointer
  4. Passes the struct pointer as this for member functions
// Generated code (simplified)
public partial void AddEvent(AtkEventType eventType, uint eventParam, 
    AtkEventListener* listener, AtkResNode* nodeParam, bool isGlobalEvent)
{
    if (MemberFunctionPointers.AddEvent is null)
        ThrowHelper.ThrowNullAddress("AddEvent", "E8 ?? ?? ?? ?? C1 E7 0C");
    
    MemberFunctionPointers.AddEvent(
        (AtkResNode*)Unsafe.AsPointer(ref this),  // this pointer
        eventType, eventParam, listener, nodeParam, isGlobalEvent);
}

Example: Instance Member Function

[MemberFunction("E8 ?? ?? ?? ?? 48 3B FD 74 36")]
public partial void SetTargetId(GameObjectId id);

Example: Static Member Function

[MemberFunction("E8 ?? ?? ?? ?? 66 45 2B E6")]
public static partial float GetGlobalUIScale();

VirtualFunction

Basic Usage

For virtual member functions (called through vtable):
[VirtualFunction(78)]
public partial StatusManager* GetStatusManager();
The number is the zero-based index in the vtable.

How It Works

The generator:
  1. Creates a VirtualTable struct with function pointers at specific offsets
  2. Adds a VirtualTable pointer field at offset 0x0
  3. Creates a wrapper that calls through the vtable
// Generated VirtualTable struct (simplified)
[StructLayout(LayoutKind.Explicit)]
public unsafe struct CharacterVirtualTable
{
    // Index 78 * 8 bytes (pointer size) = offset 0x270
    [FieldOffset(0x270)] 
    public delegate* unmanaged<Character*, StatusManager*> GetStatusManager;
}

// Generated wrapper
public partial StatusManager* GetStatusManager() 
    => VirtualTable->GetStatusManager((Character*)Unsafe.AsPointer(ref this));

Finding Virtual Function Index

Method 1: Count from IDA/Ghidra

  1. Locate the vtable in IDA/Ghidra
  2. Count the position of your function (starting from 0)
  3. Use that as the index

Method 2: Calculate from Offset

If you know the vtable offset:
Index = Offset / 8  (on 64-bit)
For example, offset 0x270 = index 78 (0x270 ÷ 8 = 78)

Examples

[VirtualFunction(1)]
public partial GameObjectId GetGameObjectId();

[VirtualFunction(4)]
public partial bool GetIsTargetable();

[VirtualFunction(77)]
public partial void SetMode(CharacterModes mode, byte modeParam);

StaticAddress

Basic Usage

For returning static object locations (usually singletons):
[StaticAddress("44 0F B6 C0 48 8B 0D ?? ?? ?? ??", 7, isPointer: true)]
public static partial Framework* Instance();
Parameters:
  1. signature: Pattern to find the reference
  2. offset: Byte offset to the address within the signature
  3. isPointer: Whether the address points to a pointer (**) or instance (*)

isPointer Parameter

When isPointer: true

The static address is a pointer to a pointer (common for heap-allocated singletons):
// C++ equivalent: static Framework* FrameworkInstance;
[StaticAddress("48 8B 0D ?? ?? ?? ??", 3, isPointer: true)]
public static partial Framework* Instance();

// Generated: Returns *ppInstance

When isPointer: false

The static address is a direct instance (allocated in the binary):
// C++ equivalent: static GameMain GameMainInstance;
[StaticAddress("48 8D 0D ?? ?? ?? ??", 3, isPointer: false)]
public static partial GameMain* Instance();

// Generated: Returns pInstance

Finding the Offset

The offset is where the address bytes are in your signature:
Signature: "48 8B 0D ?? ?? ?? ?? 48 85 C9"
Offset:     0  1  2  3  4  5  6  7  8  9
                      ^^^^^^^^^^^
                      Address is at offset 3
In x86-64 assembly:
  • 48 8B 0D ?? ?? ?? ?? = mov rcx, [rip + offset]
  • The 4 ?? bytes (offset 3-6) are the RIP-relative address
  • Use offset 3

Examples

[StaticAddress("48 8B 05 ?? ?? ?? ?? 48 8B 88 ?? ?? ?? ??", 3, isPointer: true)]
public static partial UIModule* Instance();

[StaticAddress("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 8B F8 EB", 3, isPointer: false)]
public static partial Configuration* GetInstance();

Function Signatures

All signatures must follow these rules:
  • Use ?? for wildcard bytes (two characters per byte)
  • Include 2 hex characters per byte (e.g., 0F not F)
  • Make signatures unique enough to find the function
  • Make signatures stable across patches

Good Signature Patterns

E8 ?? ?? ?? ?? 48 8B F8           # Function call followed by distinct code
48 89 5C 24 ?? 57 48 83 EC 20     # Common function prologue
40 53 48 83 EC 20 48 8B D9        # Another prologue pattern

Bad Signature Patterns

?? ?? ?? ?? ?? ?? ?? ??           # Too many wildcards
CC CC CC CC                       # Padding bytes (unstable)
90 90 90                          # NOP bytes (unstable)
See the Signatures Guide for detailed information.

String Parameters

The Problem

C# string cannot be used with DisableRuntimeMarshalling. Use byte* for C-style strings:
[MemberFunction("E8 ?? ?? ?? ?? 48 8B F8 41 B0 01")]
public partial AtkUnitBase* GetAddonByName(byte* name, int index = 1);

String Overloads

Add [GenerateStringOverloads] to generate convenient overloads:
[MemberFunction("E8 ?? ?? ?? ?? 48 8B F8 41 B0 01")]
[GenerateStringOverloads]
public partial AtkUnitBase* GetAddonByName(byte* name, int index = 1);
Generates two overloads:
// string overload with UTF-8 conversion
public AtkUnitBase* GetAddonByName(string name, int index = 1) { }

// ReadOnlySpan<byte> for UTF-8 literals
public AtkUnitBase* GetAddonByName(ReadOnlySpan<byte> name, int index = 1) { }

Usage Examples

// Using byte*
byte* namePtr = ...;
var addon = GetAddonByName(namePtr);

// Using string (with generated overload)
var addon = GetAddonByName("CharacterStatus");

// Using UTF-8 literal (C# 11+)
var addon = GetAddonByName("CharacterStatus"u8);

CStringPointer

For returned C strings, use CStringPointer:
[VirtualFunction(6)]
public partial CStringPointer GetName();

// Usage:
var name = gameObject.GetName();
var str = Marshal.PtrToStringUTF8((nint)name.StringPtr);
Never return string - it makes assumptions about memory lifetime.

Return Types

Allowed Return Types

  • Primitive types: void, bool, byte, int, float, etc.
  • Enums
  • Pointers: SomeStruct*
  • Struct values: SomeStruct (if small)
  • CStringPointer for C strings

Not Allowed

  • string - use CStringPointer or byte*
  • Managed types
  • Generic types

Method Modifiers

Required: partial

All function methods must be partial:
public partial void DoSomething();  // ✓ Correct
public void DoSomething();          // ✗ Error

Optional: static

Use for static functions:
public static partial float GetGlobalUIScale();  // Static function
public partial void InstanceMethod();            // Instance function

Complete Examples

GameObject.cs

namespace FFXIVClientStructs.FFXIV.Client.Game.Object;

[GenerateInterop(isInherited: true)]
[VirtualTable("48 8D 05 ?? ?? ?? ?? C7 81 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 48 8B C1", 3)]
[StructLayout(LayoutKind.Explicit, Size = 0x1A0)]
public unsafe partial struct GameObject {
    // Fields...
    
    // Virtual functions
    [VirtualFunction(1)]
    public partial GameObjectId GetGameObjectId();
    
    [VirtualFunction(2)]
    public partial ObjectKind GetObjectKind();
    
    [VirtualFunction(4)]
    public partial bool GetIsTargetable();
    
    [VirtualFunction(6)]
    public partial CStringPointer GetName();
    
    [VirtualFunction(7)]
    public partial float GetRadius(bool adjustByTransformation = true);
}

Character.cs

namespace FFXIVClientStructs.FFXIV.Client.Game.Character;

[GenerateInterop(isInherited: true)]
[Inherits<GameObject>, Inherits<CharacterData>]
[StructLayout(LayoutKind.Explicit, Size = 0x2370)]
public unsafe partial struct Character {
    // Fields...
    
    // Member functions
    [MemberFunction("E8 ?? ?? ?? ?? 4C 8B E0 48 8B 4F")]
    public partial GameObjectId GetTargetId();
    
    [MemberFunction("E8 ?? ?? ?? ?? 48 3B FD 74 36")]
    public partial void SetTargetId(GameObjectId id);
    
    [MemberFunction("E8 ?? ?? ?? ?? 38 43 ?? 0F 85")]
    public partial bool HasStatus(uint statusId);
    
    // Virtual function
    [VirtualFunction(78)]
    public partial StatusManager* GetStatusManager();
}

AtkUnitBase.cs

namespace FFXIVClientStructs.FFXIV.Component.GUI;

[GenerateInterop(isInherited: true)]
[Inherits<AtkEventListener>]
[StructLayout(LayoutKind.Explicit, Size = 0x238)]
public unsafe partial struct AtkUnitBase : ICreatable {
    // Fields...
    
    // Static member function
    [MemberFunction("E8 ?? ?? ?? ?? 66 45 2B E6")]
    public static partial float GetGlobalUIScale();
    
    // Constructor for ICreatable
    [MemberFunction("E8 ?? ?? ?? ?? 33 D2 48 8D 9F")]
    public partial void Ctor();
    
    // Regular member functions
    [MemberFunction("E8 ?? ?? ?? ?? 44 84 B7")]
    public partial AtkResNode* GetNodeById(uint nodeId);
    
    [MemberFunction("E8 ?? ?? ?? ?? 8D 56 31")]
    public partial AtkComponentBase* GetComponentByNodeId(uint nodeId);
    
    // With string overloads
    [MemberFunction("E9 ?? ?? ?? ?? 48 8D 15 ?? ?? ?? ?? 41 B9 ?? ?? ?? ??")]
    [GenerateStringOverloads]
    public partial bool LoadUldByName(CStringPointer name, byte a3 = 0, uint a4 = 6);
}

Framework.cs (Singleton)

namespace FFXIVClientStructs.FFXIV.Client.System.Framework;

[StructLayout(LayoutKind.Explicit, Size = 0x3000)]
public unsafe partial struct Framework {
    // Fields...
    
    // Singleton instance
    [StaticAddress("48 8B 0D ?? ?? ?? ?? 48 8D 54 24 ?? 48 83 C1 08", 3, isPointer: true)]
    public static partial Framework* Instance();
    
    // Member functions
    [MemberFunction("E8 ?? ?? ?? ?? 80 8B ?? ?? ?? ?? ?? 48 8B 8B")]
    public partial void DoSomething();
}

Testing Functions

After adding functions:
  1. Build the project to verify syntax
  2. Check generated code for correctness
  3. Test in-game using ResolverTester or a Dalamud plugin
  4. Verify signatures resolve correctly
  5. Call the function and verify behavior
See the Testing Guide for more details.

Common Mistakes

  • Forgetting partial keyword
  • Using string instead of byte* or CStringPointer
  • Wrong virtual function index
  • Incorrect signature offset for StaticAddress
  • Missing static keyword for static functions
  • Wrong isPointer value for StaticAddress
  • Signature too short or unstable

Next Steps

Build docs developers (and LLMs) love