Repository navigation
[Memhack] 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.
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
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
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) |
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) |
fieldName = { offset = 0x10, type = "int" }Generates:
-
getFieldName()->int- Reads the value from memory -
setFieldName(value)- Writes the value to memory
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)
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
text = { offset = 0x30, type = "string", maxLength = 256 }Generates:
-
getText()->string- Reads null terminated string -
setText(str)- Writes null-terminated string
Structs can optionally include validation to ensure memory addresses are valid:
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
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
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-- 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-- 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 valueFields 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 and fields (including some optional ones like parent tracking) generated for memhack structs are detailed in Memhack base struct.
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")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