Skip to main content

Your First Program

This guide walks you through using FFXIVClientStructs to interact with native FFXIV game structures. We’ll build a simple program that reads game state and calls native functions.
All examples assume you’ve completed the installation steps and initialized signature resolution if needed.

Basic Concepts

Before diving into code, understand these key concepts:

Unsafe Context

All FFXIVClientStructs code must run in unsafe blocks or methods since we’re working with native pointers

Pointer Validation

Always null-check pointers before dereferencing - native memory can be invalid or unmapped

Memory Layout

Structs use explicit layout with FieldOffset to match native memory exactly

Generated Code

Function calls are source-generated wrappers over native function pointers

Example 1: Accessing Framework

The Framework singleton is the main entry point to game systems.
1

Import the namespace

using FFXIVClientStructs.FFXIV.Client.System.Framework;
2

Get the Framework instance

Example.cs
unsafe {
    var framework = Framework.Instance();
    if (framework == null) {
        Console.WriteLine("Framework not available - is the game running?");
        return;
    }

    Console.WriteLine($"Framework address: 0x{(nint)framework:X}");
}
Framework.Instance() uses the [StaticAddress] attribute to resolve the singleton’s location via signature scanning
3

Read framework data

Reading Fields
unsafe {
    var framework = Framework.Instance();
    if (framework != null) {
        // Access frame timing information
        var deltaTime = framework->FrameDeltaTime;
        var frameCount = framework->FrameCounter;
        var realDeltaTime = framework->RealFrameDeltaTime;

        Console.WriteLine($"Frame #{frameCount}");
        Console.WriteLine($"Delta Time: {deltaTime:F4}s");
        Console.WriteLine($"Real Delta Time: {realDeltaTime:F4}s");

        // Check game state
        Console.WriteLine($"Is Exiting: {framework->IsExiting}");
        Console.WriteLine($"Is Destroying: {framework->IsDestroying}");
    }
}

Example 2: Working with UI Module

Access UI-related functionality through the UIModule singleton.
UIModule Example
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Client.UI;

unsafe {
    // UIModule is accessed through Framework
    var framework = Framework.Instance();
    if (framework == null) return;

    var uiModule = framework->GetUIModule();
    if (uiModule == null) {
        Console.WriteLine("UI Module not available");
        return;
    }

    // Alternative: Use the static helper
    var uiModule2 = UIModule.Instance();
    
    // Read UI state
    Console.WriteLine($"Should Exit Game: {uiModule->ShouldExitGame}");
    Console.WriteLine($"Frame Count: {uiModule->FrameCount}");
    Console.WriteLine($"Linkshell Cycle: {uiModule->LinkshellCycle}");
}
The source code shows UIModule.Instance() at UIModule.cs:23 implements the pattern of accessing it through Framework

Example 3: Character and Status Management

Work with character data and status effects using virtual and member functions.
Character Example
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object;

unsafe {
    // Assume you have a method to get a character pointer
    Character* character = GetCharacterPointer(); // Your implementation
    if (character == null) return;

    // Virtual function call - GetStatusManager is at vtable index 78
    var statusManager = character->GetStatusManager();
    if (statusManager == null) {
        Console.WriteLine("No status manager");
        return;
    }

    // Check for a specific status (e.g., Sprint = 50)
    uint sprintStatusId = 50;
    if (statusManager->HasStatus(sprintStatusId)) {
        // Get the status index
        var statusIndex = statusManager->GetStatusIndex(sprintStatusId);
        if (statusIndex >= 0) {
            // Get remaining time
            var remainingTime = statusManager->GetRemainingTime(statusIndex);
            Console.WriteLine($"Sprint active: {remainingTime:F1}s remaining");
            
            // Get the source of the status
            var sourceId = statusManager->GetSourceId(statusIndex);
            Console.WriteLine($"Status source: 0x{sourceId:X}");
        }
    }

    // Get character target using member function
    var targetId = character->GetTargetId();
    Console.WriteLine($"Character target: {targetId}");

    // Access character fields directly
    Console.WriteLine($"Name ID: {character->NameId}");
    Console.WriteLine($"Current World: {character->CurrentWorld}");
    Console.WriteLine($"Home World: {character->HomeWorld}");
}
Status IDs correspond to entries in the game’s Status Excel sheet. Use the correct ID for the status you want to check.

Example 4: String Handling

FFXIV uses UTF-8 encoded strings. The library provides helpers for working with them.
String Example
using FFXIVClientStructs.FFXIV.Component.GUI;

unsafe {
    // Native functions that take strings use byte* for C-style strings
    // The library generates convenient overloads for you

    var atkStage = AtkStage.Instance();
    if (atkStage == null) return;

    var raptureAtkUnitManager = atkStage->RaptureAtkUnitManager;
    if (raptureAtkUnitManager == null) return;

    // Option 1: Use the string overload (auto-generated)
    var addon = raptureAtkUnitManager->GetAddonByName("NamePlate");

    // Option 2: Use UTF-8 string literal (C# 11+)
    var addon2 = raptureAtkUnitManager->GetAddonByName("NamePlate"u8);

    // Option 3: Manual UTF-8 conversion for byte*
    var nameBytes = System.Text.Encoding.UTF8.GetBytes("NamePlate\0");
    fixed (byte* namePtr = nameBytes) {
        var addon3 = raptureAtkUnitManager->GetAddonByName(namePtr);
    }

    if (addon != null) {
        Console.WriteLine($"Found addon at: 0x{(nint)addon:X}");
    }
}
The [GenerateStringOverloads] attribute automatically creates string and ReadOnlySpan<byte> overloads for functions with byte* parameters. See README.md:316-347 for details.

Example 5: Working with Collections

FFXIV uses C++ STD library collections. The library provides wrappers to access them.
Collections Example
using FFXIVClientStructs.STD;

unsafe {
    // Example with a StdVector (std::vector<T>)
    // Assuming you have a native struct with a vector field
    
    // StdVector provides Span-like access
    StdVector<int>* nativeVector = GetSomeNativeVector();
    if (nativeVector != null) {
        var count = nativeVector->Count;
        Console.WriteLine($"Vector contains {count} elements");

        // Access elements by index
        for (var i = 0; i < count; i++) {
            var element = (*nativeVector)[i];
            Console.WriteLine($"  [{i}] = {element}");
        }
    }

    // StdMap example (std::map<K, V>)
    StdMap<uint, Pointer<void>>* nativeMap = GetSomeNativeMap();
    if (nativeMap != null) {
        var count = nativeMap->Count;
        Console.WriteLine($"Map contains {count} entries");
    }
}

Understanding Attributes

FFXIVClientStructs uses custom attributes to describe native interop:

[StaticAddress]

Resolves static singleton instances or global variables.
Framework.cs:23
[StaticAddress("48 8B 1D ?? ?? ?? ?? 8B 7C 24", 3, isPointer: true)]
public static partial Framework* Instance();
  • First parameter: Signature pattern to find the address
  • Second parameter: Offset within the signature to read the address from
  • Third parameter: Whether the location is a pointer (true) or direct instance (false)

[MemberFunction]

Calls non-virtual native member functions via signature.
Character.cs:98
[MemberFunction("E8 ?? ?? ?? ?? 4C 8B E0 48 8B 4F")]
public partial GameObjectId GetTargetId();
The generator creates a wrapper that:
  1. Resolves the function address from the signature
  2. Validates the address is not null
  3. Passes this pointer automatically
  4. Calls the native function pointer

[VirtualFunction]

Calls virtual functions via the object’s vtable.
Character.cs:78 (from README)
[VirtualFunction(78)]
public partial StatusManager* GetStatusManager();
The index (78) is the position in the C++ vtable. The generator:
  1. Creates a VirtualTable struct with function pointers at correct offsets
  2. Adds a VirtualTable* field at offset 0
  3. Generates a wrapper that calls through the vtable

[GenerateStringOverloads]

Creates convenience overloads for byte* string parameters.
UIModule.cs (from README:316)
[MemberFunction("E8 ?? ?? ?? ?? 48 8B F8 41 B0 01")]
[GenerateStringOverloads]
public partial AtkUnitBase* GetAddonByName(byte* name, int index = 1);
Generates overloads for:
  • string parameter (with UTF-8 conversion)
  • ReadOnlySpan<byte> parameter (for UTF-8 literals)

Complete Working Example

Here’s a complete program that ties everything together:
Program.cs
using System;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.Interop.Generated;
using InteropGenerator.Runtime;

class Program {
    static unsafe void Main() {
        // Step 1: Initialize (skip if using Dalamud plugin)
        Console.WriteLine("Initializing FFXIVClientStructs...");
        Resolver.GetInstance.Setup();
        Addresses.Register();
        Resolver.GetInstance.Resolve();
        Console.WriteLine("Initialization complete!\n");

        // Step 2: Access Framework
        var framework = Framework.Instance();
        if (framework == null) {
            Console.WriteLine("ERROR: Could not get Framework instance.");
            Console.WriteLine("Make sure FFXIV is running!");
            return;
        }

        Console.WriteLine("=== Framework Info ===");
        Console.WriteLine($"Address: 0x{(nint)framework:X}");
        Console.WriteLine($"Frame: {framework->FrameCounter}");
        Console.WriteLine($"Delta Time: {framework->FrameDeltaTime:F4}s");
        Console.WriteLine($"Is Exiting: {framework->IsExiting}");
        Console.WriteLine();

        // Step 3: Access UI Module
        var uiModule = UIModule.Instance();
        if (uiModule != null) {
            Console.WriteLine("=== UI Module Info ===");
            Console.WriteLine($"Address: 0x{(nint)uiModule:X}");
            Console.WriteLine($"UI Frame Count: {uiModule->FrameCount}");
            Console.WriteLine();
        }

        // Step 4: Monitor frame updates
        Console.WriteLine("Monitoring frames (press Ctrl+C to exit)...");
        var lastFrame = framework->FrameCounter;
        while (true) {
            var currentFrame = framework->FrameCounter;
            if (currentFrame != lastFrame) {
                Console.WriteLine($"Frame {currentFrame}: dt={framework->FrameDeltaTime:F4}s");
                lastFrame = currentFrame;
            }
            System.Threading.Thread.Sleep(16); // ~60 FPS
        }
    }
}

Key Takeaways

1

Always use unsafe context

All FFXIVClientStructs code requires unsafe blocks or methods due to native pointer usage.
2

Validate pointers

Always check for null before dereferencing any pointer. Native memory can be unmapped or invalid.
3

Understand the attributes

  • [StaticAddress] for singletons and globals
  • [MemberFunction] for non-virtual native functions
  • [VirtualFunction] for virtual member functions
  • [GenerateStringOverloads] for automatic UTF-8 string handling
4

Use generated overloads

When functions have byte* string parameters, use the auto-generated string or ReadOnlySpan<byte> overloads for convenience.
5

Access singletons via Instance()

Most major game systems provide static Instance() methods for easy access.

Common Patterns

Singleton Access Pattern

unsafe {
    var framework = Framework.Instance();
    if (framework == null) return;
    
    // Use framework...
}

Virtual Function Call Pattern

unsafe {
    Character* character = GetCharacter();
    if (character == null) return;
    
    var statusManager = character->GetStatusManager();
    if (statusManager != null) {
        // Use status manager...
    }
}

String Parameter Pattern

unsafe {
    // Option 1: String overload (most convenient)
    var addon = manager->GetAddonByName("AddonName");
    
    // Option 2: UTF-8 literal (C# 11+, most efficient)
    var addon2 = manager->GetAddonByName("AddonName"u8);
}

Next Steps

Explore the Source

Browse the repository to find available structs and functions

Join the Community

Get help and share your findings

Read the README

Deep dive into library architecture and contribution guidelines

IDA/Ghidra Database

Contribute to reverse engineering efforts
Remember: This library provides direct access to game memory. Use responsibly and be aware that incorrect usage can crash the game or cause undefined behavior.

Build docs developers (and LLMs) love