Skip to main content
Ghidra is a free, open-source reverse engineering tool developed by the NSA. FFXIVClientStructs provides full support for Ghidra through Python and Java scripts.

Getting Started with Ghidra

Prerequisites

  1. Download and install Ghidra
  2. Install Java runtime (required by Ghidra)
  3. Follow Ghidra’s installation guide for your platform

Creating a New Project

  1. Launch Ghidra
  2. File → New Project or press Ctrl+N
  3. Select Non-Shared Project and click Next
  4. Choose a directory and project name, then click Finish

Importing FFXIV Executable

  1. File → Import File or press i
  2. Navigate to your FFXIV installation:
    • Windows: C:\Program Files (x86)\SquareEnix\FINAL FANTASY XIV - A Realm Reborn\game
  3. Select ffxiv_dx11.exe and click Select File To Import
  4. Accept default import settings and click OK
  5. Double-click the imported file in the project window

Initial Analysis

On first opening, Ghidra will prompt you to analyze:
  1. Click Yes when asked to analyze
  2. Accept the default analysis options (or customize as needed)
  3. Click Analyze
  4. Wait for analysis to complete (this can take several minutes)
Initial analysis is a good time to install script dependencies while you wait.

Installing Script Dependencies

Ghidra uses an embedded Jython (Java implementation of Python 2) interpreter, which makes dependency installation slightly more complex than IDA Pro.

Step 1: Install Python 2

Download and install Python 2.7.18 (or any Python 2.7.x version).
Must be Python 2.x (not Python 3). Ghidra’s Jython only supports Python 2 syntax.

Step 2: Install Required Packages

Run this command, replacing <YourGhidraFolder> with your Ghidra installation path:
python.exe -m pip install -t <YourGhidraFolder>\Ghidra\Features\Jython\lib\Lib\site-packages pyyaml==5.4.1 anytree==2.8.0
Example:
C:\Python27\python.exe -m pip install -t C:\ghidra_10.3.2\Ghidra\Features\Jython\lib\Lib\site-packages pyyaml==5.4.1 anytree==2.8.0
If you have multiple Python versions installed, use the full path to the Python 2 executable.

Step 3: Add Script Directories

  1. In the CodeBrowser window, click the Script Manager icon (or Window → Script Manager)
  2. Click the Manage Script Directories button (folder icon)
  3. Click the + button to add directories
  4. Add both:
    • FFXIVClientStructs/ida (contains ffxiv_idarename.py)
    • FFXIVClientStructs/Ghidra/scripts (contains SigScanner.java and others)
  5. Click OK

Step 4: Configure Script Keybindings

  1. In Script Manager, select __UserScripts category
  2. You should see all FFXIVClientStructs scripts
  3. Right-click scripts to assign keybindings:
    • SigScanner.javaCtrl+Alt+S (default)
    • ffxiv_idarename.py → Your preference
  4. Check the box next to each script to enable it

Understanding Ghidra Naming

Ghidra’s auto-analysis creates default labels based on what it detects:
Label FormatMeaning
FUN_14#######Function
PTR_FUN_14#######Pointer to function
DAT_14#######Data
LAB_14#######Code label (not a function)
SUB_14#######Subroutine
All addresses start with 14 because the default Windows 64-bit image base is 0x140000000.

Address Translation

When you see an address in Dalamud like:
7FF6DDEAD480 ffxiv_dx11.exe+1A0D480
To navigate in Ghidra:
  1. Press g (Go To…)
  2. Enter: 141A0D480
  3. Press Enter
The offset (+1A0D480) is appended to the base (0x14000000) = 0x141A0D480.

Available Scripts

ffxiv_idarename.py

Despite the name, this script fully supports Ghidra through the abstraction layer.

What It Does

Ingests data.yml and renames symbols throughout the disassembly:
  • Global variables: DAT_*g_FloatOne
  • Functions: FUN_*Client::System::Framework::Framework.ctor
  • Vtables: Unnamed addresses → vtbl_Component::GUI::AtkResNode
  • Virtual functions: With inheritance tracking
  • Instances: Named global pointers to classes

Running the Script

  1. Open Script Manager (Window → Script Manager or toolbar icon)
  2. Navigate to __UserScripts
  3. Find ffxiv_idarename.py
  4. Right-click → Run Script (or use your keybinding)
  5. Wait for completion (watch console for progress)
Output in console:
Executing
[Various processing messages...]
Done

Example Transformation

Before running the script:
undefined8 FUN_1406cb6c0(undefined4 param_1)
{
  undefined8 *puVar1;
  puVar1 = (undefined8 *)
           Component::Exd::ExdModule.GetRowBySheetIndexAndRowIndex
                     (*(undefined8 *)
                       (g_Client::System::Framework::Framework_InstancePointer2 + 0x2b38),0x3b,
                      param_1);
  if (puVar1 == (undefined8 *)0x0) {
    return 0;
  }
  return *puVar1;
}
Notice that some names like GetRowBySheetIndexAndRowIndex are already applied from data.yml.

Vtable Visualization

The script adds inheritance trees as comments to vtables: Before:
vtbl_Component::GUI::AtkModuleInterface::AtkEventInterface
141918ca0 50 97 5b   addr    _purecall
          41 01 00
          00 00
After:
vtbl_Component::GUI::AtkModuleInterface::AtkEventInterface
# Component::GUI::AtkModule
# └── Component::GUI::AtkModuleInterface::AtkEventInterface
#     └── Client::UI::Agent::AgentInterface
141918ca0 50 97 5b   addr    _purecall
          41 01 00
          00 00

Ghidra-Specific Function Handling

The Ghidra implementation handles different naming patterns:
Current NameAction
FUN_*, LAB_*, SUB_*, DAT_*Rename to class.function
thunk_*Strip prefix and rename
_purecallSkip (named in derived class)
Already namedUpdate if changed in data.yml
See /home/daytona/workspace/source/ida/ffxiv_idarename.py:372-407 for implementation details.

SigScanner.java

Generates byte pattern signatures for functions that work across game patches.

What It Does

Creates the smallest unique signature for a function:
  1. Reads instruction bytes at cursor position
  2. Replaces dynamic offsets with wildcards (??)
  3. Tests signature uniqueness in .text section
  4. Copies result to clipboard

How Signatures Work

Consider this instruction sequence:
1400597e7 e8 e4 69      CALL    Client::System::Framework::Framework.ctor
          03 00
1400597ec 48 8b c8      MOV     RCX,RAX
1400597ef 48 89 05      MOV     qword ptr [g_Framework], RAX
          c2 eb 04 02
1400597f6 eb 0a         JMP     LAB_140059802
1400597f8 48 8b ce      MOV     RCX,RSI
Raw bytes:
e8 e4 69 03 00 48 8b c8 48 89 05 c2 eb 04 02 eb 0a 48 8b ce
The bytes e4 69 03 00 and c2 eb 04 02 are offsets that change with each patch. Wildcarded signature:
E8 ?? ?? ?? ?? 48 8B C8 48 89 05 ?? ?? ?? ?? EB ?? 48 8B CE
This signature is durable across patches because it matches the instruction pattern, not absolute addresses.

Using SigScanner

  1. Navigate to the instruction you want a signature for
  2. Place cursor on the instruction
  3. Press Ctrl+Alt+S (or your configured keybinding)
  4. The signature is generated and copied to your clipboard
  5. Paste into your code or data.yml
If you place the cursor on the first instruction of a function, SigScanner will find a caller of that function instead (useful for Dalamud signatures).

Configuration

Edit SigScanner.java to customize behavior:
public int MaxReferences = 1999;  // Max callers to check
public int MaxResults = -1;  // Stop after N signatures (-1 = no limit)
public int MinSignatureLength = 3;  // Minimum bytes
public int MaxSignatureLength = 256;  // Maximum bytes
public boolean ListAltSignatures = false;  // Show alternatives
public boolean CopyResultToClipboard = true;  // Auto-copy
See /home/daytona/workspace/source/Ghidra/scripts/SigScanner.java:22-28.

Common Issues and Solutions

Ghidra’s analysis sometimes produces different results than IDA. Here are common issues when running ffxiv_idarename.py.

Function Appears Read-Only

In the Symbol Tree, virtual functions may appear in two places:
  • Functions node (light blue) - Editable
  • Labels node (dark blue) - Read-only, grayed decompiler
Cause: Ghidra didn’t detect it as a function (no CALL instructions reference it). Solution:
  1. Navigate to the address
  2. Right-click first instruction → Create Function
  3. Or press f

Error: Unexpected name “Client::UI::Agent::AgentInterface.vf1”

This occurs when a base class has multiple virtual functions, but Ghidra only detected the first one. Solution:
  1. Navigate to vtbl_Component::GUI::AtkModuleInterface::AtkEventInterface
  2. You’ll see:
    1418ebca0 50 97 5b    addr    _purecall
              41 01 00
              00 00
    1418ebca8 10          ??      10h
    1418ebca9 45          ??      45h
    
  3. The second entry should be a pointer. Place cursor on line 1418ebca8
  4. Press p (create pointer) or right-click → Data → Pointer
  5. Delete the incorrect label: cursor on label, press Delete
  6. Re-run ffxiv_idarename.py

Error: Function at 0xXXXXXXXX had unexpected name ""

Ghidra didn’t identify this memory as code. Solution:
  1. Press g (Go To…)
  2. Enter the address from the error message
  3. You’ll see undefined bytes:
    1405129d0 48    ??    48h    H
    1405129d1 85    ??    85h
    
  4. Right-click → Disassemble or press d
  5. Right-click → Create Function or press f
  6. Re-run the script

Error: Unexpected name “Concurrency::details::FreeThreadProxy::…”

Ghidra’s analyzer matched this to a Visual Studio runtime function. Solution:
  1. Navigate to the function
  2. Right-click the name
  3. Remove Label or press Delete
  4. Repeat until it becomes FUN_XXXXXXXX
  5. Re-run the script

Error: Two vtables detected, second not labeled

Occurs when a class has multiple vtables, but Ghidra didn’t find pointers to the secondary vtable. Solution:
  1. Find where the script stopped processing (e.g., vf20)
  2. Look for the first overridden function in the secondary vtable (e.g., vf8)
  3. Navigate to that address and look upward for undefined bytes (not cc)
  4. First non-cc byte: press d (disassemble) then f (create function)
  5. In the decompiled function, find:
    *param_1 = &PTR_Component_GUI_AtkComponentMultipurpose.vf20_141935128;
    
  6. The address (141935128) is your secondary vtable
  7. Re-run the script

Error: Base vtbl size greater than actual class

Navigate to the vtable and look for the last entry. You might see:
141a59ef8 10 8c 15    addr    Client::UI::AddonActionDoubleCrossBase.vf78
          41 01 00
          00 00
141a59f00 a0          ??      A0h
              DAT_141a59f01
141a59f01 21          ??      21h    !
Solution:
  1. The a0 should be a pointer
  2. Place cursor on that line
  3. Press p to create a pointer
  4. Re-run the script

Known Ignorable Errors

These errors are expected and can be safely ignored:
Error: Function at 0x1401A0810 had unexpected name "Component::GUI::AtkModuleInterface::AtkEventInterface.vf1" during naming of Client::Game::Event::ChocoboTaxiStandEventHandler.vf274 (vtbl[274])
Error: Function at 0x1401A0810 had unexpected name "Component::GUI::AtkModuleInterface::AtkEventInterface.vf1" during naming of Client::Game::Event::GatheringPointEventHandler.vf288 (vtbl[288])
Error: Function at 0x1401A0810 had unexpected name "Component::GUI::AtkModuleInterface::AtkEventInterface.vf1" during naming of Client::Game::OutdoorTerritory.vf8 (vtbl[8])
Error: The sum of "Client::UI::RaptureAtkHistory"'s base vtbl sizes (8) is greater than the actual class itself (6)
See /home/daytona/workspace/source/Ghidra/Common Issues.md:92-98 for details.

Version Tracking (After Patches)

After each game patch, you need to re-import and re-analyze. Ghidra supports version tracking to help identify changes:
  1. Import the new ffxiv_dx11.exe into the same project
  2. Use folders to organize versions:
    Project
    ├── 7.0/
    │   └── ffxiv_dx11.exe
    └── 7.01/
        └── ffxiv_dx11.exe
    
  3. Open the new version and analyze
  4. Use Tools → Version Tracking to compare with previous version
  5. Re-run scripts on the new version

Tips and Tricks

  • g - Go to address
  • d - Disassemble
  • f - Create function
  • p - Create pointer
  • Delete - Remove label
  • Ctrl+Shift+E - Set equate (name a constant)
  • ; - Add comment

Finding Constructors

Look for functions that:
  1. Take a pointer as first parameter (usually rcx in x64)
  2. Write a vtable pointer to offset 0:
    lea     rax, [vtbl_Component_GUI_AtkResNode]
    mov     qword ptr [rcx], rax
    

Debugging Script Issues

Check the Console window for:
  • Python tracebacks
  • Warning messages
  • Processing progress
If the script seems stuck, check Task Manager - Ghidra may still be processing.

Performance Optimization

For large analysis sessions:
  1. Increase Ghidra’s memory in ghidraRun.bat or ghidra.sh:
    MAXMEM=8G
    
  2. Disable unnecessary analyzers for faster initial analysis
  3. Save frequently - analysis can crash on very large executables

Next Steps

Build docs developers (and LLMs) love