Skip to content
Damian Small

Fixing Halo PC Race Lap Limits with Chimera Lua

While working on HRL's custom race lap limits, I ran into an interesting Halo PC client limitation.

The server can happily set a race to more than the default number of laps, but the Halo PC client isn't necessarily aware of the new limit.

For example, the client may begin with a 15-lap limit while the server later raises it to 50. Halo can then behave as though the race ended at the original limit. The HUD also continues to show the old limit:

15 laps

rather than:

50 laps

Getting the limit from the server

The first problem was figuring out how to get the new limit from the server to the client.

Chimera Lua doesn't appear to expose a normal chat callback, but it does expose an rcon message callback.

This gave me a simple way to communicate the new limit to Chimera without having to interfere with normal chat.

On the server, HRL now uses SAPP's rprint():

function LapLimitManager:announce(limit)
    if not self.config.announce_changes then
        return
    end

    local message = string.format(
        self.config.message,
        limit,
        limit ~= 1 and "s" or ""
    )

    for i = 1, 16 do
        if player_present(i) then
            rprint(i, message)
        end
    end
end

Chimera receives this through:

set_callback("rcon message", "on_rcon_message")

For HRL, the message looks like:

[LAPLIMIT] Score limit changed to 50 laps

Other servers use different messages, so the client script uses several patterns to extract the limit:

local patterns = {
    -- [LAPLIMIT] Score limit changed to 25 laps (HRL)
    "%[laplimit%]%s*score limit changed to%s*(%d+)%s*laps?",

    -- The score limit has been changed to 25 laps (Lickity)
    "the score limit has been changed to%s*(%d+)%s*laps?",

    -- Generic lap-limit formats
    "lap limit%s*[:=]%s*(%d+)",
    "lap limit%s+changed to%s+(%d+)%s*laps?",
    "lap limit%s+set to%s+(%d+)%s*laps?",
    "race limit%s*[:=]%s*(%d+)%s*laps?",
}

This gives us a server-independent value:

Server message
↓
"50"
↓
Chimera

Finding the client-side limit

The next part was finding where Halo PC actually stores the client's lap limit.

I used Cheat Engine to search for the default race limit. Race normally begins at around 15 laps, so I searched for 15 and narrowed the results while a race was running.

Eventually I found:

0x006F1CE0

Changing this value from:

15

to:

50

immediately changed the client's displayed race limit.

More importantly, this isn't just a cosmetic HUD value. The client now understands the race as having the higher limit.

I could then update it directly from Chimera:

local LAP_LIMIT_ADDRESS = 0x006F1CE0

local function set_lap_limit(limit)
    write_dword(LAP_LIMIT_ADDRESS, limit)

    console_out(string.format(
        "[LapLimit] Set client lap limit to %d",
        limit
    ))
end

Then the RCON handler becomes:

function on_rcon_message(message)
    local limit = detect_lap_limit(message)

    if limit then
        set_lap_limit(limit)
    end
end

That completed the path from the server message to the client-side value.

SAPP
│
│ rprint()
▼
Halo PC
│
│ RCON message
▼
Chimera Lua
│
│ detect_lap_limit()
▼
0x006F1CE0
│
▼
Halo PC client

What's next?

The address is currently hardcoded. I still want to identify exactly where this field sits within Halo PC's Race Globals.

Chalwk's Halo: Understanding Memory Offsets documents race_globals at 0x639FA0, so that's the obvious place to investigate next.

Eventually, I want to stop relying on the absolute 0x006F1CE0 address. Resolving Race Globals would let the script access the lap-limit field relative to that structure instead.

The proof of concept works: a server can change the race to a lap limit that the Halo PC client wasn't originally aware of, and Chimera can update the client's limit on the fly.