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, if the client initially knows about a 15-lap race and the server changes it to 50 laps, Halo can start behaving as though the race has 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 starts with a limit of around 15 laps, so I searched for the value 15 and narrowed down the results while running a race.

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.

So we can 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

And that's basically it.

SAPP

│ rprint()

Halo PC

│ RCON message

Chimera Lua

│ detect_lap_limit()

0x006F1CE0


Halo PC client

What's next?

For now the address is hardcoded, but I'd like 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.

The eventual goal would be to avoid relying on the absolute 0x006F1CE0 address and instead resolve the Race Globals structure and access the lap-limit field relative to it.

For now, though, 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.