Skip to content
Damian Small

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 the rcon message callback, performs whatever action you want, and can send information back to the server by executing an RCON command with execute_script().

This gives you 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

The important part here is checking Environment == 1. This tells SAPP the command originated 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?

You might have noticed the message starts with |n.

rprint(PlayerIndex, "|nhas_chimera?")

|n inserts a newline into the message. Halo only displays the first line of an incoming RCON message, so placing the actual command on the second line hides it from the player's console while still allowing Chimera to receive the full string.

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

Building Your Own Protocol

Once the communication channel is established, you can send whatever messages you need.

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.

A Couple of Things I Found

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 is that rprint() isn't just useful for printing text. Combined with Chimera's rcon message callback, it becomes a simple messaging layer that can be used for feature detection, synchronising client-side effects, or passing custom commands between the server and client.

It's a neat technique that's been hiding in plain sight.