-
Notifications
You must be signed in to change notification settings - Fork 0
Pipe Protocol
ZeusMod uses two Windows named pipes to bridge external clients
(Electron UI, inspect.py) with IcarusInternal.dll. This page is
the wire-format spec — what bytes go on the wire, what responses look
like, and how to extend it.
| Pipe | Purpose | Default client |
|---|---|---|
\\.\pipe\ZeusModPipe |
Cheat toggles + config values | Electron launcher |
\\.\pipe\ZeusModDbg |
Reflection + memory access | scripts/inspect.py |
Both run the same server code in different threads, share the same wire format, and differ only in which command vocabulary is recognised.
-
Transport —
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGEon the server side. Clients write and read as UTF-8 byte arrays. - Framing — one message per request, one message per response. Pipe message boundaries are the framing mechanism; there is no length prefix.
-
Encoding — 7-bit ASCII for commands and args, UTF-8 for FName
payloads returned in responses. Addresses are always rendered as
uppercase
0x[0-9A-F]+.
The pipe client in the Electron app is node:net with
\\.\pipe\<name> as the address. inspect.py uses open("\\\\.\\pipe\\ZeusModDbg", "r+b")
on Windows.
<cmd>:<value>
-
cmd— one of the names listed in Feature Reference. -
value— a token interpreted by the cheat handler.
Examples:
godmode:1
stamina:0
speed_mult:4.0
time_val:12
give:Wood,10
dbg:<cmd>:<arg0>:<arg1>:...:<argN>
-
cmd— one of the primitives listed in Debug Client. - Args are colon-separated. Hex addresses are bare (no
0xprefix on the wire). Spaces are illegal in a single arg.
Examples:
dbg:classof:28F2A3AC010
dbg:read64:28F2A3AC010
dbg:dump:28F2A3AC010:40
dbg:propget:28F2A3AC010:Inventory:CurrentWeight:4
dbg:search:7FF640330000:1000:ascii:DOS
Inside the DLL, HandleDbgRaw receives (cmd, args) already split.
Every response starts with one of three tokens, followed by a space
and a free-form payload. The payload is always a single-message
write and lines inside it are separated by \n.
| Prefix | Meaning |
|---|---|
OK |
Success. Payload conveys the result. |
ERR |
Failure. Payload is a short human-readable reason. |
WARN |
Succeeded but partial. Rare. |
Examples:
OK character=0x28F2A3AC010 Off::Player_InventoryComp=0x758
OK modules count=174
0x00007FF640330000 size=0x06A62000 Icarus-Win64-Shipping.exe
0x00007FFD05540000 size=0x00267000 ntdll.dll
…
OK dump @0x28F2A3AC010 size=0x40
+0x000 90 F0 E6 44 F6 7F 00 00 … a.Name.o..
+0x010 40 21 26 CB A1 01 00 00 …
…
ERR unknown primitive 'modules'
ERR usage refs:<target>:<scanStart>:<range>
+0x<offset> <byte0> <byte1> <byte2> … <ascii or tag>
The inspect.py formatter parses this against a regex so label
overlays and diffs can be layered on top without re-implementing a
hex dumper.
pipe-server thread:
ConnectNamedPipe
loop:
ReadFile → raw line
if starts with "dbg:" → HandleDbgCommand
else → HandleCheatCommand
WriteFile ← response
(loop)
HandleDbgCommand is the first touchpoint for the debug pipe. It
splits the line on the first : (after stripping dbg:), then
forwards (cmd, args, out, outCap) to HandleDbgRaw. The raw
handler is a giant if (!strcmp(cmd, "…")) chain — see
native/internal/src/cheats/Trainer.cpp.
Cheats share a similar dispatcher, but the handler is
Trainer::OnCheatChange(cmd, value) which simply writes the matching
bool or value-field.
- Per-client thread on the server side — one per accepted pipe handle. In practice both clients each hold a single connection.
- The trainer tick runs on its own thread. UPROPERTY writes from the debug pipe and cheat-toggle writes are not synchronised against the trainer tick — writes are small (≤8 bytes) and naturally atomic on x64, and game-state reads inside the tick are defensive.
- Cheat pipe: 256 bytes. Cheat responses are short.
- Debug pipe: 64 KiB per single response.
dumptops out at0xC000bytes (48 KiB), leaving room for the formatting overhead.
Anything longer is truncated with …(truncated). In practice this
never triggers outside memory dumps against a huge range, which
is exactly what inspect.py's chunk-walking composites are for.
Adding a new debug command:
- In
Trainer.cpp, insideHandleDbgRaw, add a newif (!strcmp(cmd, "myprim")) { … }block and emit anOK …response. - In
scripts/inspect.py, register the command in theCOMMANDSlist with the correctgroup(Scanner,Memory, whatever fits) soDBG_PRIMITIVESpicks it up (the gate that letsprimitive_sendforward the command over the wire instead of rejecting it client-side). - (Optional) Add a composite wrapper if the command benefits from client-side orchestration (label overlays, argument defaulting).
- Document it in Debug Client.
Adding a new cheat toggle:
- Add a bool field on
Trainerand wire it into the tick. - In
HandleCheatCommand, accept the new token. - On the Electron side, add a card in
app/src/renderer/index.htmlwithdata-cheat-toggle="mycheat". The renderer forwards it to the pipe automatically — no additional JS wiring required.
- Debug Client — the Python client that speaks this protocol.
- Hook Catalog — cheats and their hooks.
- Architecture — where the pipes fit in the bigger picture.