[Memhack] Struct Manager - Redacted-Rice/ITB-RedactedRiceExts GitHub Wiki
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:
- Creates a Lua table to represent the C++ struct
- Automatically generates getters and setters for each field
- Handles type conversions between Lua and C++ memory
- Validates memory access and struct integrity
- Registers the struct in
memhack.structsfor 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 addressgetFieldName()/setFieldName(value)- Access the int fieldgetOtherField()/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 memorysetFieldName(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 valuesetPilotPtrPtr(ptr)- Sets the raw pointer valuegetPilotPtr()->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 valuesetStrRemotePtr(ptr)- Sets the raw pointer valuegetStrRemote()->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 stringsetText(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
vtablefield /getVtablehelpers 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