Skip to main content
The Component.Excel namespace provides access to FFXIV’s game data system, which stores most static game data in Excel sheets (.exd files).

Overview

FFXIV stores game data in Excel-like sheets:
  • Items, actions, quests, NPCs, etc.
  • Multi-language support
  • Row-based data with columns
  • Type-safe access through ExcelRow

ExcelModuleInterface

Main interface for accessing Excel sheets.

Structure

public unsafe partial struct ExcelModuleInterface {
    [FieldOffset(0x08)] public ExdModule* ExdModule;
}

Getting Sheets

// Get sheet by name
var sheet = excelModule->GetSheetByName("Item");

// Get sheet by index
var sheet = excelModule->GetSheetByIndex(SheetsEnum.Item);

// Get current language
var language = excelModule->GetLanguage();

Supported Languages

public enum ExcelLanguage {
    Japanese,              // ja
    English,               // en
    German,                // de
    French,                // fr
    ChineseSimplified,     // chs
    ChineseTraditional,    // cht
    Korean,                // ko
    ChineseTraditional2    // tc
}

ExcelRow

Represents a single row in an Excel sheet.

Structure

public unsafe partial struct ExcelRow {
    [FieldOffset(0)] public void* Data;
}

Reading Data

// Get column pointer
var columnPtr = row->GetColumnPtr(columnIndex: 0);

// Read column data
if (!row->IsColumnRsv(columnIndex: 0)) {
    // Column contains actual data
    var value = *(int*)columnPtr;
}

// Get string column (with indirection)
var stringPtr = ExcelRow.ResolveStringColumnIndirection(columnPtr);

// Get first column as string
var text = row->GetFirstColumnAsString();

Column Types

Excel columns can be:
  • Numeric: int, uint, byte, short, etc.
  • String: Text data (UTF-8)
  • RSV (Reserved): Placeholder/unused columns

ExcelRowDescriptor

Describes the structure of a row.

Structure

public unsafe partial struct ExcelRowDescriptor {
    [FieldOffset(0x00)] public uint RowId;
    [FieldOffset(0x04)] public uint Offset;
    [FieldOffset(0x08)] public ushort DataSize;
}

IExcelRowWrapper

Interface for strongly-typed Excel row access.

Structure

public unsafe partial struct IExcelRowWrapper {
    [FieldOffset(0x00)] public void** VTable;
    [FieldOffset(0x08)] public ExcelRow* Row;
    [FieldOffset(0x10)] public ExcelSheet* Sheet;
}

Purpose

Provides type-safe wrapper around raw ExcelRow data:
  • Validates row structure
  • Provides named property access
  • Handles type conversions

ExcelLanguageEvent

Event triggered when language changes.

Structure

public unsafe partial struct ExcelLanguageEvent {
    [FieldOffset(0x00)] public void** VTable;
    [FieldOffset(0x08)] public ExcelLanguage NewLanguage;
    [FieldOffset(0x0C)] public ExcelLanguage OldLanguage;
}

Common Excel Sheets

Items

// Get Item sheet
var itemSheet = excelModule->GetSheetByName("Item");

// Get specific item row
var itemRow = itemSheet->GetRow(rowId: 1);
if (itemRow != null) {
    // Read item name (column 9 in Item sheet)
    var namePtr = itemRow->GetColumnPtr(9);
    var name = ExcelRow.ResolveStringColumnIndirection(namePtr);
    
    // Read item level (column 11)
    var levelPtr = itemRow->GetColumnPtr(11);
    var itemLevel = *(ushort*)levelPtr;
}

Actions

// Get Action sheet
var actionSheet = excelModule->GetSheetByName("Action");

// Get action data
var actionRow = actionSheet->GetRow(actionId: 7);
if (actionRow != null) {
    // Read action name
    var namePtr = actionRow->GetColumnPtr(0);
    var actionName = ExcelRow.ResolveStringColumnIndirection(namePtr);
    
    // Read cast time (column 23)
    var castTimePtr = actionRow->GetColumnPtr(23);
    var castTime = *(byte*)castTimePtr;
}

Quest Data

// Get Quest sheet
var questSheet = excelModule->GetSheetByName("Quest");

// Get quest info
var questRow = questSheet->GetRow(questId: 65536);
if (questRow != null) {
    // Read quest name
    var namePtr = questRow->GetColumnPtr(0);
    var questName = ExcelRow.ResolveStringColumnIndirection(namePtr);
}

Integration with ExdModule

The Excel system works with Component.Exd.ExdModule:
// Access through ExcelModuleInterface
var excelModule = ExcelModuleInterface.Instance();
var exdModule = excelModule->ExdModule;

// ExdModule handles actual file loading
// ExcelModuleInterface provides high-level access

Example: Reading Item Data

public unsafe void PrintItemInfo(uint itemId) {
    // Get Excel module
    var excelModule = ExcelModuleInterface.Instance();
    if (excelModule == null) return;
    
    // Get Item sheet
    var itemSheet = excelModule->GetSheetByName("Item");
    if (itemSheet == null) return;
    
    // Get item row
    var itemRow = itemSheet->GetRow(itemId);
    if (itemRow == null) return;
    
    // Read columns
    var namePtr = itemRow->GetColumnPtr(9);  // Column 9 = Name
    var name = ExcelRow.ResolveStringColumnIndirection(namePtr);
    
    var levelPtr = itemRow->GetColumnPtr(11); // Column 11 = Item Level
    var itemLevel = *(ushort*)levelPtr;
    
    var rarityPtr = itemRow->GetColumnPtr(13); // Column 13 = Rarity
    var rarity = *(byte*)rarityPtr;
    
    // Print info
    Console.WriteLine($"Item: {Marshal.PtrToStringUTF8((IntPtr)name)}");
    Console.WriteLine($"Level: {itemLevel}");
    Console.WriteLine($"Rarity: {rarity}");
}

Example: Iterating Sheet Rows

public unsafe void ListAllMounts() {
    var excelModule = ExcelModuleInterface.Instance();
    var mountSheet = excelModule->GetSheetByName("Mount");
    if (mountSheet == null) return;
    
    // Get row count
    var rowCount = mountSheet->RowCount;
    
    for (uint i = 0; i < rowCount; i++) {
        var row = mountSheet->GetRow(i);
        if (row == null) continue;
        
        // Check if row is valid (not RSV)
        if (row->IsColumnRsv(0)) continue;
        
        // Get mount name
        var namePtr = row->GetColumnPtr(0);
        var name = ExcelRow.ResolveStringColumnIndirection(namePtr);
        
        if (name != null) {
            Console.WriteLine($"Mount {i}: {Marshal.PtrToStringUTF8((IntPtr)name)}");
        }
    }
}

Performance Considerations

  1. Cache sheet pointers: GetSheetByName has lookup cost
  2. Batch reads: Read multiple columns in one access
  3. Check RSV columns: Skip reserved/unused columns
  4. String indirection: Strings require extra pointer resolution

Best Practices

  1. Validate pointers: Always null-check sheet and row pointers
  2. Know column indices: Column order varies by sheet
  3. Handle languages: Some data is language-specific
  4. Use Lumina: For comprehensive sheet definitions, see Lumina library
  5. Type safety: Cast column pointers to correct types

Common Pitfalls

  1. Wrong column index: Columns vary between game versions
  2. Missing RSV check: Reading reserved columns returns garbage
  3. String lifetime: Resolved strings are pointers, not copies
  4. Language mismatch: Current language affects text columns
  5. Row ID vs Index: Some sheets use sparse row IDs

See Also

Build docs developers (and LLMs) love