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().
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 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 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 callback, this becomes a simple messaging layer. It can detect features, synchronise client-side effects, and pass custom commands between server and client.
It's a neat technique that's been hiding in plain sight.