Skip to content
Damian Small

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. [1]
  2. [2]