Skip to main content

Overview

Signatures are byte patterns used to locate functions and data in the game binary at runtime. They’re essential for making the library work across game patches without hardcoding addresses.

Signature Format

All signatures in FFXIVClientStructs must follow these strict rules:

Required Format

  • Use ?? for wildcard bytes
  • Include 2 hex characters per byte (e.g., 0F not F)
  • Separate bytes with spaces
  • Use uppercase hex digits (recommended for consistency)

Valid Examples

E8 ?? ?? ?? ?? C1 E7 0C
48 89 5C 24 ?? 57 48 83 EC 20
40 53 48 83 EC 20 48 8B D9
48 8B 0D ?? ?? ?? ?? 48 85 C9 74 ?? 48 8B 01

Invalid Examples

E8 ???????? C1 E7 0C          # Wrong: wildcards must be ??
E8 ? ? ? ? C1                # Wrong: single ? instead of ??
E8 xxxxxx C1 E7 0C           # Wrong: x instead of ??
e8 ?? ?? ?? ??               # Wrong: lowercase (should be uppercase)
E8????????C1E70C             # Wrong: no spaces

Finding Signatures

Using IDA Pro

Step 1: Locate the Function

  1. Load FFXIV binary in IDA Pro
  2. Import the rename database
  3. Find your function by name or navigate to it
  4. Press Space to switch to text view if needed

Step 2: Identify Unique Bytes

Look for instruction sequences that are:
  • Unique: Not found elsewhere in the binary
  • Stable: Won’t change across patches
  • Long enough: Usually 8-20 bytes

Step 3: Create the Pattern

Example function:
.text:0000000140A1B2C0    mov     [rsp+arg_0], rbx
.text:0000000140A1B2C5    push    rdi
.text:0000000140A1B2C6    sub     rsp, 20h
.text:0000000140A1B2CA    mov     rbx, rcx
Convert to bytes:
48 89 5C 24 08 57 48 83 EC 20 48 8B D9

Step 4: Test Uniqueness

  1. In IDA: Alt+B (Search > Sequence of bytes)
  2. Paste your pattern: 48 89 5C 24 08 57 48 83 EC 20 48 8B D9
  3. Search - should find exactly ONE location
  4. If multiple results, make pattern longer or more specific

Using Ghidra

Step 1: Locate the Function

  1. Load FFXIV binary
  2. Import rename database
  3. Navigate to the function
  4. Right-click in listing → Copy SpecialByte String

Step 2: Test the Pattern

  1. SearchMemory (or press S)
  2. Choose “Hex” format
  3. Paste bytes (spaces optional)
  4. Verify single result

Using x64dbg (Runtime)

For finding signatures while the game is running:
  1. Attach x64dbg to FFXIV process
  2. Navigate to the function (if you know the address)
  3. Copy bytes from the disassembly
  4. Use SearchPattern to test uniqueness

Creating Stable Signatures

What Makes a Good Signature?

✓ Good Patterns

Function prologues:
40 53 48 83 EC 20 48 8B D9           # push rbx; sub rsp, 20h; mov rbx, rcx
48 89 5C 24 ?? 57 48 83 EC 20        # mov [rsp+arg_0], rbx; push rdi; sub rsp, 20h
Function calls with following code:
E8 ?? ?? ?? ?? 48 8B F8              # call func; mov rdi, rax
E8 ?? ?? ?? ?? 84 C0 74 ??           # call func; test al, al; jz ...
Unique instruction sequences:
48 8B 0D ?? ?? ?? ?? 48 85 C9 74 ?? # mov rcx, [rip+X]; test rcx, rcx; jz ...
F3 0F 10 05 ?? ?? ?? ?? F3 0F 59    # movss xmm0, [rip+X]; mulss ...

✗ Bad Patterns

Too many wildcards:
?? ?? ?? ?? ?? ?? 48 8B             # Too generic
E8 ?? ?? ?? ??                      # Just a call, very common
Alignment/padding bytes:
CC CC CC CC CC                      # int3 padding (changes)
90 90 90 90                         # nop padding (unstable)
Data references that might move:
48 8D 0D [specific address]         # Might change if data moves

Wildcard Placement

When to Use Wildcards

Function call targets:
E8 ?? ?? ?? ??                      # call instruction - target varies
RIP-relative addresses:
48 8B 0D ?? ?? ?? ??                # mov rcx, [rip + offset]
48 8D 05 ?? ?? ?? ??                # lea rax, [rip + offset]
Small immediate values that might change:
48 83 EC ??                         # sub rsp, ?? - stack size might vary
B8 ?? ?? ?? ??                      # mov eax, ?? - constant might change

When NOT to Use Wildcards

Opcodes:
48 8B ??                            # Wrong: opcodes should not be wildcards
48 8B 0D                            # Correct: full opcode
Register encodings (usually):
48 8B D9                            # mov rbx, rcx - registers often stable
48 8B F8                            # mov rdi, rax - keep specific

Signature Length

  • Minimum: 8-12 bytes for uniqueness
  • Typical: 12-20 bytes for stability
  • Maximum: 30-40 bytes (longer = more fragile)

Too Short

E8 ?? ?? ?? ??                      # Only 5 bytes - thousands of matches
48 8B D9                            # Only 3 bytes - very common

Too Long

48 89 5C 24 08 57 48 83 EC 20 48 8B D9 48 8B FA 48 8B 01 FF 90 88 00 00 00 84 C0 74 1F
# 29 bytes - likely to change in patches

Just Right

48 89 5C 24 ?? 57 48 83 EC 20 48 8B D9  # 13 bytes - good balance
E8 ?? ?? ?? ?? 48 8B F8 48 85 C0        # 11 bytes - stable and unique

StaticAddress Signatures

For [StaticAddress], you need to find where the static variable is referenced:

Example: Finding Framework Instance

Step 1: Locate Reference

Find code that accesses the static instance:
.text:0000000140B2A1E0    mov     rcx, cs:FrameworkInstance
.text:0000000140B2A1E7    test    rcx, rcx
Bytes:
48 8B 0D [4 bytes offset] 48 85 C9

Step 2: Create Signature

Wildcard the offset:
48 8B 0D ?? ?? ?? ?? 48 85 C9

Step 3: Determine Offset

Signature: "48 8B 0D ?? ?? ?? ?? 48 85 C9"
Byte pos:   0  1  2  3  4  5  6  7  8  9
Offset:              ^^
The offset is 3 (where the address bytes start).

Step 4: Determine isPointer

If the assembly is:
mov rcx, cs:pFrameworkInstance    # Loading a pointer variable
Then isPointer: true (it’s a Framework**) If the assembly is:
lea rcx, cs:FrameworkInstance     # Loading address of instance
Then isPointer: false (it’s a Framework*)

Common Patterns

# Pointer to pointer (isPointer: true)
48 8B 05 ?? ?? ?? ??                # mov rax, cs:pInstance
48 8B 0D ?? ?? ?? ??                # mov rcx, cs:pInstance
48 8B 15 ?? ?? ?? ??                # mov rdx, cs:pInstance

# Direct instance (isPointer: false)  
48 8D 05 ?? ?? ?? ??                # lea rax, cs:Instance
48 8D 0D ?? ?? ?? ??                # lea rcx, cs:Instance
48 8D 15 ?? ?? ?? ??                # lea rdx, cs:Instance

VTableAddress Signatures

For [VTableAddress], find where the vtable pointer is assigned in the constructor:

Example

# Constructor code:
.text:0000000140C1A2B0    lea     rax, AddonRetainerTaskAsk_vtable
.text:0000000140C1A2B7    mov     [rbx], rax
Bytes:
48 8D 05 [offset] 48 89 03
Signature:
48 8D 05 ?? ?? ?? ?? 48 89 03
Offset: 3 Usage:
[VTableAddress("48 8D 05 ?? ?? ?? ?? 48 89 03", 3)]
public unsafe partial struct AddonRetainerTaskAsk { }

Testing Signatures

In IDA Pro

Alt+B → Paste signature → Search
  • Should find exactly 1 result
  • Should land at the correct function/location

In Ghidra

Search → Memory → Hex string
  • Should find exactly 1 result
  • Verify it’s the correct location

At Runtime

Use the ResolverTester project:
// In ResolverTester/Program.cs
var framework = Framework.Instance();
if (framework == null)
    Console.WriteLine("Failed to resolve Framework.Instance!");
else
    Console.WriteLine($"Framework found at 0x{(nint)framework:X}");
Build and run against the game.

Signature Maintenance

When Signatures Break

After a game patch, signatures may fail to resolve. Common causes:
  1. Function moved: Code location changed
  2. Function modified: Compiler generated different code
  3. Optimization changed: New compiler flags

Updating Broken Signatures

  1. Import new rename database for the patch
  2. Navigate to the function in IDA/Ghidra
  3. Find the same logical code section
  4. Create a new signature
  5. Test for uniqueness
  6. Submit PR with updated signature

Making Signatures More Stable

  • Choose code from the middle of functions (not start/end)
  • Avoid signatures near branch instructions
  • Include distinctive instruction sequences
  • Balance uniqueness vs. stability

Common Signature Patterns

Here are common x64 assembly patterns you’ll encounter:

Function Prologues

push    rbx                 # 53
sub     rsp, 20h            # 48 83 EC 20
mov     rbx, rcx            # 48 8B D9
Signature: 53 48 83 EC 20 48 8B D9
mov     [rsp+arg_0], rbx    # 48 89 5C 24 08
mov     [rsp+arg_8], rsi    # 48 89 74 24 10  
push    rdi                 # 57
Signature: 48 89 5C 24 ?? 48 89 74 24 ?? 57

Function Calls

call    SomeFunction        # E8 ?? ?? ?? ??
mov     rdi, rax            # 48 8B F8
Signature: E8 ?? ?? ?? ?? 48 8B F8

Static Data Access

mov     rcx, cs:gInstance   # 48 8B 0D ?? ?? ?? ??
test    rcx, rcx            # 48 85 C9
jz      short loc_XXX       # 74 ??
Signature: 48 8B 0D ?? ?? ?? ?? 48 85 C9 74 ??

Virtual Calls

mov     rax, [rcx]          # 48 8B 01
call    qword ptr [rax+50h] # FF 90 50 00 00 00
Signature: 48 8B 01 FF 90 50 00 00 00

Troubleshooting

Signature Not Found

  • Too specific: Add more wildcards
  • Function changed: Find the updated code
  • Wrong binary: Ensure you’re scanning the correct module

Multiple Matches

  • Too generic: Make signature longer
  • Add context: Include more surrounding bytes
  • Choose different location: Find a more unique part of the function

Signature Resolves to Wrong Location

  • Pattern collision: Signature matches unintended code
  • Increase specificity: Add more context bytes
  • Change pattern: Use a different part of the function

Best Practices

  1. Test thoroughly: Verify uniqueness before committing
  2. Document: Add comments if the signature is tricky
  3. Balance length: Not too short, not too long
  4. Stable opcodes: Choose instructions that won’t change
  5. Wildcard wisely: Only wildcard what needs to vary
  6. Verify runtime: Test that the signature resolves correctly

Tools Reference

  • IDA Pro: Industry standard, best RTTI support
  • Ghidra: Free alternative, good scripting
  • x64dbg: Runtime debugging and signature testing
  • ResolverTester: Test signatures with the actual resolver

Next Steps

Build docs developers (and LLMs) love