Skip to content
Damian Small

Halo PC and Custom Edition: Communicating Between SAPP and Chimera

While researching some existing SAPP and Chimera scripts, I noticed they were communicating with each other without using memory patches or any custom networking. After a bit of digging, I discovered they were using rprint() as a lightweight messaging system between the server and client.

It's a surprisingly simple technique, but I couldn't find much documentation explaining how it works. After putting together a minimal proof of concept, I was able to reproduce the same behaviour.

How it works

The communication flow looks like this:

SAPP
|
| rprint(PlayerIndex, "|nhas_chimera?")
v
Halo
|
| RCON message
v
Chimera Lua
|
| execute_script("rcon password am_chimera")
v
SAPP EVENT_COMMAND

The server sends an RCON message to a specific player using rprint(). Chimera receives the message through its RCON callback and performs the requested action. It can then send information back by running an RCON command through execute_script().

That produced a simple two-way communication channel between SAPP and Chimera.

Server (SAPP)

First, create a player variable that tracks whether a client is running Chimera.

add_var("has_chimera", 4)

When a player joins, send them a message asking if Chimera is installed.

function OnJoin(PlayerIndex)
    set_var(PlayerIndex, "$has_chimera", 0)
    rprint(PlayerIndex, "|nhas_chimera?")
end

When the client responds, handle it in EVENT_COMMAND.

function OnCommand(PlayerIndex, Command, Environment)

    if Environment == 1 then
        if Command == "am_chimera" then
            set_var(PlayerIndex, "$has_chimera", 1)
            say(PlayerIndex, "Chimera detected!")
            return false
        end
    end

    return true
end

I had to check Environment == 1 so SAPP would accept only commands originating from RCON, rather than chat or the server console.

Client (Chimera)

Register the rcon message callback.

set_callback("rcon message", "OnRconMessage")

Then listen for the incoming message.

function OnRconMessage(msg)

    if msg == "|nhas_chimera?" then
        execute_script("rcon password am_chimera")
        return false
    end

    return true
end

When Chimera receives the message, it simply executes an RCON command back to the server.

Why |n?

The message starts with |n.

rprint(PlayerIndex, "|nhas_chimera?")

|n inserts a newline into the message. Halo displays only the first line of an incoming RCON message. So, putting the command on line two hides it from the player's console while Chimera still receives the full string.

Without it, players would briefly see every protocol message printed to their console.

Building the protocol

Once the communication channel was established, I could send commands and arguments through the same path.

For example, the server could send:

rprint(PlayerIndex, "play_sound~flag_taken")
rprint(PlayerIndex, "set_colour~255~0~0")
rprint(PlayerIndex, "spawn_effect~explosion")

On the client, split the message into arguments and use the first value as the command.

function OnRconMessage(msg)

    local args = {}

    for value in string.gmatch(msg, "([^~]+)") do
        table.insert(args, value)
    end

    if args[1] == "play_sound" then
        -- Play a sound

    elseif args[1] == "set_colour" then
        -- Update colours

    elseif args[1] == "spawn_effect" then
        -- Spawn a local effect
    end

    return true
end

Using ~ as a delimiter makes it easy to pass multiple arguments in a single message. This is exactly how some larger SAPP/Chimera projects communicate between the server and client.

What caught me out

There were two things that caught me out while testing this.

The first is that the callback is registered as "rcon message" (with a space), even though Chimera refers to it internally as rcon_message. If you're reading the source code, it's easy to assume the Lua callback uses the underscore version.

The second was broader: rprint() and Chimera's RCON callback together form a messaging layer that can detect features, synchronise client-side effects, and pass custom commands between server and client.

The proof of concept confirmed both directions of the connection using existing SAPP and Chimera behaviour.