Skip to content

[Memhack] Struct Manager

DasKeifer edited this page Sep 30, 2026 · 3 revisions

Struct Manager

The Struct Manager is memhack's system for defining and working with C++ memory structures from Lua. It automatically generates getters, setters, and wrapper functions for accessing game memory in a type-safe way.

For modders: You typically don't need to define new structs yourself. All game structs (Pilot, PilotLvlUpSkill, etc.) are already defined and ready to use. This documentation is mainly for understanding how the system works.

Overview

When you define a struct, the Struct Manager:

  1. Creates a Lua table to represent the C++ struct
  2. Automatically generates getters and setters for each field
  3. Handles type conversions between Lua and C++ memory
  4. Validates memory access and struct integrity
  5. Registers the struct in memhack.structs for easy access

How Structs Are Defined

Structs are defined using memhack.structManager:define() (colon - method call):

local MyStruct = memhack.structManager:define("MyStruct", {
    fieldName = { offset = 0x00, type = "int" },
    otherField = { offset = 0x04, type = "pointer", subType = "OtherStruct" },
})

This creates a struct with auto-generated functions:

  • MyStruct.new(address) - Create struct instance at address
  • getFieldName() / setFieldName(value) - Access the int field
  • getOtherField() / getOtherFieldPtr() - Access the pointer field

Supported Field Types

The Struct Manager supports these field types:

Type Description Size Auto-Generated Methods
int 32-bit integer 4 bytes getXxx(), setXxx(value)
bool Boolean 1 byte getXxx(), setXxx(value)
byte Single byte 1 byte getXxx(), setXxx(value)
float Single precision float 4 bytes getXxx(), setXxx(value)
double Double precision float 8 bytes getXxx(), setXxx(value)
pointer 32-bit pointer 4 bytes getXxxPtr(), setXxxPtr(ptr), getXxx() (if subType)
struct Inline struct Variable getXxx() only (no setter)
string Null-terminated string Variable getXxx(), setXxx(str) (requires maxLength)
bytearray Array of bytes Variable getXxx(), setXxx(bytes) (requires length)

Field Options

Each field definition can include these options:

Option Type Description
offset int Required. Byte offset from struct start
type string Required. Field type (int, pointer, etc.)
subType string or table Type name for pointers/structs. For pointers, enables automatic wrapping. Can be a string (struct name) or table { type = "string", maxLength = X, lengthFn = fn } for string/bytearray subTypes
hideGetter bool If true, getter is prefixed with _ (e.g., _getXxx())
hideSetter bool If true, setter is prefixed with _ (e.g., _setXxx())
noSetter bool If true, no setter is generated at all (read-only field). Used widely (e.g. BoardPawn cores, GameMap fields, Vector size).
maxLength int Required for string type fields if not supplying subType as a table. Max string length including null terminator. Also used in subType tables
length int Required for bytearray type fields if not supplying subType as a table. Array length in bytes
lengthFn function Optional function returning actual length to read. Used in subType tables with maxLength (e.g. string)

Generated Methods

For Basic Types (int, bool, byte, float, double)

fieldName = { offset = 0x10, type = "int" }

Generates:

  • getFieldName() -> int - Reads the value from memory
  • setFieldName(value) - Writes the value to memory

For Pointers

Pointer to Struct:

pilotPtr = { offset = 0x20, type = "pointer", subType = "Pilot" }

Generates:

  • getPilotPtrPtr() -> int - Returns the raw pointer value
  • setPilotPtrPtr(ptr) - Sets the raw pointer value
  • getPilotPtr() -> Pilot - Returns wrapped Pilot struct at pointed address

Pointer to String:

strRemote = { 
    offset = 0x0, 
    type = "pointer", 
    subType = {
        type = "string",
        maxLength = 1024,
        lengthFn = function(self) return self:getStrLen() end
    }
}

Generates:

  • getStrRemotePtr() -> int - Returns the raw pointer value
  • setStrRemotePtr(ptr) - Sets the raw pointer value
  • getStrRemote() -> string - Reads string at pointed address (using lengthFn if provided)

For Inline Structs

name = { offset = 0x14, type = "struct", subType = "ItBString" }

Generates:

  • getName() -> ItBString - Returns wrapped struct at field address
  • No setter - Setters should be defined in the returned struct

For Strings

text = { offset = 0x30, type = "string", maxLength = 256 }

Generates:

  • getText() -> string - Reads null terminated string
  • setText(str) - Writes null-terminated string

Struct Validation

Structs can optionally include validation to ensure memory addresses are valid:

VTable Validation

local SomeStruct = memhack.structManager:define("SomeStruct", layout, 0x004320d4)
  • Automatically verifies vtable pointer at offset 0
  • Adds hidden vtable field / getVtable helpers when a numeric vtable address is passed
  • Used for C++ virtual objects with known vtable addresses

Custom Validation

local MyStruct = memhack.structManager:define("MyStruct", layout, function(instance)
    -- Custom validation logic
    if instance:getSomeField() < 0 then
        return false, "Field cannot be negative"
    end
    return true
end)
  • Allows custom validation logic
  • Should return (success, errorMessage) from the validator

All validation automatically includes:

  • Check that address is not nil or 0
  • Check that memory at address is readable

Using Defined Structs

Creating Instances

For modders: generally these structs should be accessed by the functions added to the game objects.

-- From a known address
local pilot = memhack.structs.Pilot.new(0x12345678)

-- With validation: returns instance or nil (does not return err - failure is logged)
local pilot = memhack.structs.Pilot.new(address, true)
if not pilot then
    LOG("Invalid pilot at address")
end

Accessing Fields

-- Auto-generated getters
local name = pilot:getName()      -- Returns ItBString struct
local xp = pilot:getXp()          -- Returns int value

-- Auto-generated setters
pilot:setXp(50)                   -- Sets int value
pilot:setName("New Name")         -- Sets via ItBString wrapper

Working with Nested Structs

-- Inline structs (struct type)
local nameStruct = pilot:getName()  -- Returns ItBString
local nameStr = nameStruct:get()    -- Get actual string

-- Pointers (pointer type with subType)
local skillsArray = pilot:getLvlUpSkills()        -- Returns wrapped struct
local skillsPtr = pilot:getLvlUpSkillsPtr()       -- Returns raw pointer value

Hidden Getters/Setters

Fields can be marked with hideGetter or hideSetter to make them private. These are not expected to be used outside the struct functions themselves.

xp = { offset = 0x30, type = "int", hideSetter = true }

This generates:

  • getXp() - Public getter (normal)
  • _setXp(value) - Private setter (prefixed with _)

Use this when the field should not be modified directly by external code. For example, Pilot's xp field uses hideSetter: true because setXp() is custom-implemented to handle automatically leveling up or down if its high enough or below 0.

Common Functions, Fields, and Parent Tracking

Common functions and fields (including some optional ones like parent tracking) generated for memhack structs are detailed in Memhack base struct.

Advanced: Creating Arrays

Create an array of struct instances:

local skills = memhack.structManager:array(
    memhack.structs.PilotLvlUpSkill,  -- Struct type
    baseAddress,                       -- Starting address
    2,                                 -- Count
    0x74                               -- Stride (bytes between elements)
)

-- Access array elements (1-indexed)
local skill1 = skills[1]
local skill2 = skills[2]

-- Array metadata
LOG("Array has " .. skills.count .. " elements")
LOG("Stride: " .. skills.stride .. " bytes")

Advanced: Addition Function Wrappers

Struct manager also provides functions to generate wrappers for many common cases:

  • Wrapping a structs :get() and :set() functions
  • Wrapping setters to automatically fire a hook on change
  • Wrapping getters to track and provide getters for parent objects

Clone this wiki locally