Halo PC and Custom Edition Memory Offsets
While working on various Halo scripts, I have slowly accumulated a fairly large collection of memory offsets.
Some came from building the replay system. Others came from the nametag mod, vehicle animation experiments, or trying to understand how Halo updates a spawned biped. A few were found while digging through the dedicated server with Ghidra and scanmem.
The problem was, they were spread across scripts, old notes and previous investigations. Every time I needed one again, I had to remember where it originally came from.
This post is my attempt to collect them in one place.
It isn't a complete description of Halo's memory layout. Some offsets have been tested directly, while others are still working labels or come from older community references. I have marked those differences where they matter.
A note on versions
Halo PC, Halo Custom Edition, haloded.exe and haloceded.exe do not all use the same absolute addresses.
This matters when using older offset lists. An address found for the Halo CE client cannot just be copied into a Halo PC dedicated-server script and expected to work.
Object offsets tend to be more reusable. If get_dynamic_player() returns the address of a player object, the player's position can still be found relative to that object. The object itself may move in memory, but its internal layout remains consistent for that build.
Absolute addresses are different. They refer to a fixed location inside a particular executable and may change between PC, CE or another build of the game.
For example:
local chat_is_open = read_byte(0x0064E788) 0x0064E788 is an absolute address.
local player = get_dynamic_player()
local x = read_float(player + 0x5C) 0x5C is an offset relative to the player object.
Where possible, I have described an entry as one of the following:
Status | Meaning |
|---|---|
Verified | I changed or inspected the value and observed the expected result. |
Identified | The field is supported by working code or previous research, but has not been exhaustively tested. |
Candidate | The value appears to match the behaviour, but its full purpose is not yet known. |
External | Taken from a community reference and not yet verified against my current executable. |
Halo PC keyboard and interface state
These addresses came from adding an F3 toggle to the Halo PC nametag script.
Halo Custom Edition already uses F3 to toggle teammate nametags. Retail Halo PC does not have that feature, so the Chimera script needed to read the keyboard state directly.
Address | Purpose | Status | Notes |
0x0064C550 | Keyboard state table | Verified | Add the key's scan code and read the resulting byte. A pressed key returns 1. |
0x0064E788 | Chat input state | Verified | Returns 0 while chat is closed. |
F3 uses scan code 0x3D:
local keyboard_input_address = 0x0064C550
local f3_is_pressed = read_byte(keyboard_input_address + 0x3D) == 1 The 0x3D value is the keyboard scan code rather than a Halo object offset. It selects F3 from the keyboard state table.
The final input check also makes sure that chat and the console are closed:
local keyboard_input_address = 0x0064C550
local chat_is_open = read_byte(0x0064E788)
local player = get_dynamic_player()
if chat_is_open == 0
and console_is_open() == false
and player ~= nil
and read_byte(keyboard_input_address + 0x3D) == 1 then
-- Toggle nametags.
end Without those checks, pressing F3 while typing or using the console could also toggle the mod.
Dynamic player object
The following offsets are relative to the object returned by get_dynamic_player().
Position and velocity
These are among the most useful offsets in both SAPP and Chimera scripts:
Offset | Type | Purpose | Status |
0x5C | float | Position X | Verified |
0x60 | float | Position Y | Verified |
0x64 | float | Position Z | Verified |
0x68 | float | Velocity X | Identified |
0x6C | float | Velocity Y | Identified |
0x70 | float | Velocity Z | Identified |
The position fields were used by the nametag projection and replay systems. The same base position and velocity layout is also used by vehicle objects.
Facing, aim and movement input
Offset | Type | Working purpose | Status |
0x224 | dword | Unit-facing data | Candidate |
0x230 | dword | Desired-aim data | Candidate |
0x23C | dword | Aim data | Candidate |
0x278 | float | Forward input | Identified |
0x27C | float | Left input | Identified |
0x280 | float | Up input | Identified |
0x289 | byte | Animation or update gate | Candidate |
The input floats at 0x278, 0x27C and 0x280 are straightforward. They describe the movement being applied to the unit.
The earlier fields, not quite as certain.
Community references commonly describe 0x230, 0x234 and 0x238 as the X, Y and Z components of the unit's forward or aiming vector. My Ghidra notes also contain a control structure in which 0x230 was labelled desired_aim.
Those descriptions may be looking at the same data from different levels of the structure, but I have not yet reconciled them. For now, the labels at 0x224, 0x230 and 0x23C should be treated as working names rather than final types.
The byte at 0x289 was involved in a native animation path. A value of 3 was required for the function I was following to continue, although the field may represent a broader unit state rather than an animation-specific flag.
Biped animation state
The replay system originally needed a way to animate a spawned player biped.
Recording every body-part transform would have required 234 floats for a complete pose. A much smaller option was to record Halo's existing animation state and allow the game to calculate the final skeleton.
Runtime testing identified the following fields:
Offset | Size | Purpose | Status |
0xD0 | 16-bit | Base animation index | Verified |
0xD2 | 16-bit | Current animation frame | Verified |
0xD4 | 16-bit | Transition frame | Identified |
0xD6 | 16-bit | Transition length | Identified |
0x2A0 | 8-bit | Animation stance | Identified |
0x2A3 | 8-bit | High-level animation state | Identified |
Writing animation index 173 to a spawned cyborg at biped + 0xD0 caused it to play the native forward-running animation. Which was a good start.
The frame at biped + 0xD2 then advanced all on its own and looped when the animation finished. This confirmed that the spawned biped had entered Halo's native animation update, rather than merely holding the values I had written.
Native animation functions
Following the same update path in Ghidra identified several useful locations:
Address or offset | Purpose | Status |
0x0069B75C | Per-frame biped function-pointer table | Identified |
0x004C1530 | Primary biped frame stepper | Verified with breakpoints |
0x004D48D0 | Advances biped + 0xD2 and selects the next state | Identified |
[ESI + 0x44] | Per-class data checked before animation update | Identified |
These are absolute code locations from the executable I investigated. They should not be assumed to match another build without checking.
Biped body-part transforms
The standard cyborg biped has 18 body-part transforms beginning at biped + 0x550.
Each transform is 0x34 bytes and contains 13 floats: a scale value, a 3x3 rotation matrix and a translation.
function getBodyPart(address, offset)
address = address + (offset or 0x0)
return {
scale = read_float(address + 0x00),
rotationx0 = read_float(address + 0x04),
rotationy0 = read_float(address + 0x08),
rotationz0 = read_float(address + 0x0C),
rotationx1 = read_float(address + 0x10),
rotationy1 = read_float(address + 0x14),
rotationz1 = read_float(address + 0x18),
rotationx2 = read_float(address + 0x1C),
rotationy2 = read_float(address + 0x20),
rotationz2 = read_float(address + 0x24),
x = read_float(address + 0x28),
y = read_float(address + 0x2C),
z = read_float(address + 0x30),
}
end The known transform offsets are:
0x550, 0x584, 0x5B8, 0x5EC, 0x620, 0x654,
0x688, 0x6BC, 0x6F0, 0x724, 0x758, 0x78C,
0x7C0, 0x7F4, 0x828, 0x85C, 0x890, 0x8C4 These transforms are useful for exact pose capture or procedural animation, but they were not a practical primary format for the replay system. Halo may also replace direct writes when it recalculates the skeleton later in the update or render process.
The compact animation fields are the better option when the required animation already exists in the map.
Node-data offsets by object type
The Wizard and aLTis offset list records different node-data locations for each object type:
Object type | Offset | Status |
Biped | 0x550 | Verified |
Vehicle | 0x5C0 | External |
Weapon | 0x340 | External |
Equipment | 0x294 | External |
Grenade | 0x244 | External |
Projectile | 0x2B0 | External |
Scenery | 0x1F8 | External |
Machine | 0x228 | External |
Machine control | 0x21C | External |
I have verified the biped node block through the animation work. The other object types are included as useful references, but I have not yet checked them against the latest build.
Animation table structure
The animation-table investigation also identified the beginning of its descriptor and entry layouts.
The descriptor contains:
Offset | Purpose | Status |
0x00 | Entry count | Identified |
0x04 | Pointer to the first entry | Identified |
Each entry begins with:
Offset | Purpose | Status |
0x00 | Animation name pointer | Identified |
0x04 | Unknown secondary field | Candidate |
I haven't yet added the table's base address or entry stride to the consolidated notes, so this part is still incomplete.
Vehicle object
The vehicle block was originally discovered through work by 002 and shaft, with several fields later tested or clarified by aLTis.
I have been using it while investigating vehicle replay and animation.
Vehicle state
Offset | Type | Purpose | Status |
0x4CC | Bitmask | Vehicle state flags | Verified |
0x4CE | word | Physics-active timer | Verified |
0x4D0 | byte | Airborne timer | Verified |
Known bits within vehicle + 0x4CC are:
Bit | Purpose | Status |
0 | Tire blur | Verified |
2 | Crouch | Identified |
3 | Jump | Identified |
local flags = vehicle + 0x4CC
local tires_are_blurred = read_bit(flags, 0) == 1
local crouch_is_active = read_bit(flags, 2) == 1
local jump_is_active = read_bit(flags, 3) == 1 Vehicle movement and animation
Offset | Type | Purpose | Status |
0x4D4 | float | Speed | Identified |
0x4D8 | float | Slide | Identified |
0x4DC | float | Turn | Identified |
0x4E0 | float | Tire position | Identified |
0x4E4 | float | Tread position | Identified |
0x4E8 | float | Tread position | Identified |
0x4EC | float | Hover | Identified |
0x4F0 | float | Thrust | Identified |
0x4FC | float | Hovering position X | Identified |
0x500 | float | Hovering position Y | Identified |
0x504 | float | Hovering position Z | Identified |
0x508 | float | Collision pushback X | Identified |
0x50C | float | Collision pushback Y | Identified |
The final left/right assignment for the tread values at 0x4E4 and 0x4E8 still needs to be confirmed.
These values allow a replay to reproduce more than the vehicle's position and rotation. Wheel blur, steering, treads, hovering parts and thrust can all be recorded separately so the playback looks like a moving vehicle rather than an object being pushed along a path.
Halo Custom Edition client addresses
The following addresses come from the Wizard and aLTis list:
Address | Purpose | Status | Notes |
0x00643064 | Current map name | External | Marked as possibly incorrect in the source itself. |
0x0063EE28 | Server IP address | External | Halo CE client address. This is not the dedicated-server address. |
I have kept these here because they have been relevant to previous investigations, but I have not verified them against the current CE executable.
Dedicated-server port addresses
The internal server port has now been located independently in both dedicated executables:
Executable | Address | Status | How it was checked |
Halo PC haloded.exe | 0x006A1C80 | Verified | Located with scanmem. |
Halo CE haloceded.exe | 0x00626100 | Verified | Located with scanmem, then confirmed by changing INTERNAL_PORT. |
local SERVER_PORT_ADDR_PC = 0x6A1C80
local SERVER_PORT_ADDR_CE = 0x626100 An earlier Ghidra note recorded the PC port as 0x002A1C80. This was the location relative to the executable image rather than the runtime address.
With the normal 0x00400000 image base:
0x002A1C80 + 0x00400000 = 0x006A1C80 The adjacent 0x002A1C70 location was recorded as a possible server IP string. Applying the same image base gives 0x006A1C70, but I have not yet verified that value independently.
Executable | Address | Working purpose | Status |
Halo PC haloded.exe | 0x006A1C70 | Server IP string | Candidate |
Community offset lists
Two community references have been particularly useful throughout this work.
The first is the offsets.lua list in aLTis's Halo_CE repository. It was originally created by Wizard and later edited by aLTis.
The file contains a large collection of Halo PC and Custom Edition addresses, object fields and patches. It also includes its own warning that earlier versions contained incorrect or missing offsets.
I treat it as a starting point rather than proof that an address is correct for the executable I am testing.
The second is Chalwk's Halo: Understanding Memory Offsets. It provides a much more readable collection of common PC and CE addresses, dynamic player fields, static player data, weapon offsets and SAPP signatures.
The same warning applies. I have not independently verified every value in either list against the latest Halo PC or Custom Edition build.
Hardcoded addresses and signatures
SAPP exposes sig_scan(), which can locate a byte pattern instead of relying entirely on a fixed address.
For example, the community references locate the network structure using:
local address = sig_scan(
"F3ABA1????????BA????????C740??????????E8????????668B0D"
)
if address then
local network_struct = read_dword(address + 3)
end I have not independently verified that particular signature. It is included because it shows the better long-term approach for SAPP scripts.
A hardcoded address may stop working when the executable changes. A good signature can survive smaller changes because it searches for the surrounding instructions, although it still needs to return one unique result and should be checked against each supported build.
Chimera does not expose the same signature-scanning function to Lua, so client scripts generally have to use version-specific addresses or locate the data another way.
What is still missing
There are still a few obvious gaps in this reference:
record the exact version and executable hash for every absolute address;
resolve the player-control fields around 0x224 and 0x230;
finish the animation-table layout and record its base address;
confirm the left and right tread assignments;
verify the possible Halo PC server IP address;
identify the unclassified haloded.exe locations; and
retest useful entries from the aLTis and Chalwk lists against the latest builds.
I will keep updating this post as those fields are confirmed or corrected.
For now, anything marked Candidate or External should be treated as a lead rather than a guarantee. The verified entries are the ones I have already used or tested while building the nametag, replay and animation systems.
References
- [1] Addresses and Offsets (offsets.lua) — Wizard and aLTis
- [2] Halo: Understanding Memory Offsets — Chalwk