Download docs/research/CHAT_PATHS.md from nmarshall/urt-rs: direct link, hf CLI and curl.
- Browser
- Download file 8 kB
-
https://huggingface.co/nmarshall/urt-rs/resolve/main/docs/research/CHAT_PATHS.md
- Command line
-
hf download hf://nmarshall/urt-rs/docs/research/CHAT_PATHS.md
-
curl -L -o CHAT_PATHS.md https://huggingface.co/nmarshall/urt-rs/resolve/main/docs/research/CHAT_PATHS.md
Native client command admission and bounded chat paths
Static pass, 2026-10-11. Reproduce with python3 scripts/analyze_chat_paths.py; check with python3 scripts/test_chat_paths.py. Generated evidence includes source/disassembly hashes, both Linux ELF64 native command tables/graphs, QVM chat helper partitions and bounded join/recipient traces. CALLs are recorded, not entered; token lengths and team-mode helper results are supplied fixture inputs. No original game or complete QVM runs.
CHAT-001: native admission, sanitation and weighted limits
Both Linux ELF64 ucmds tables contain eight name/handler pairs followed by a null pair: userinfo, disconnect, cp, vdr, download, nextdl, stopdl, donedl. Their handlers are respectively SV_UpdateUserinfo_f, SV_Disconnect_f, SV_VerifyPaks_f, SV_ResetPureClient_f, SV_BeginDownload_f, SV_NextDownload_f, SV_StopDownload_f and SV_DoneDownload_f. The collector reads bounded, file-backed 16-byte rows and verifies string termination; it does not execute those handlers.
SV_ExecuteClientCommand tokenizes input and compares argv 0 with built-in names through strcmp. A match invokes its handler with the native client pointer before the subsequent forwarding decision. Its third native argument controls that decision: zero permits a handled return or unhandled debug diagnostic; nonzero reaches game forwarding only when no built-in matched. The unmatched forwarding path additionally requires native server state 2 and native client state 4. Those values belong to native structures and must not be confused with QVM recipient state 2 below. The caller's third-argument production, flood throttling and complete native state lifecycle remain open.
Cmd_Args_Sanitize replaces LF (10) and CR (13) with space (32) in argument strings after argv 0. The file-backed sanitizer bytes are 0a 0d 00; the symbol-address and graph evidence identify traversal beginning at cmd_argv + 8, i.e. argv 1 in these eight-byte-pointer images. The graph is retained; native mutation/tokenizer behavior is not executed in fixtures.
After sanitation, Q_stricmp selects say, say_team and tell for weighted budget 256, and ut_radio for budget 122. Other unmatched game commands bypass this particular limit scan. The scanner visits argv argc-1 down to 1, counts bytes and inter-argument separators, and adds 16 extra units per $ byte. Thus each dollar costs 17 units including its ordinary byte. More than six dollar bytes or a signed weighted total above the chosen budget takes the warning/log/server-command path instead of VM forwarding. Equality with a budget passes the visible comparison. This is instruction-graph recovery, not native execution or proof of downstream expansion/escaping semantics. The native command-6 tail still requires its preceding admission/filter path.
CHAT-002: token concatenation capacity
Helper 180858 uses a global buffer at VM address 35737412. It obtains argc, starts at its incoming token index, retrieves each token into a 1024-byte local buffer and gets its byte length. Before copying it checks signed used + token_length < 1023; otherwise it stops before that token. Accepted tokens are copied through trap 101 (encoded CALL -102), and a space is appended unless the token is the final argument. A terminating NUL is written at the resulting used offset.
Bounded slices supply token lengths rather than entering argv/length/memcpy callees. They show that used 1021 plus length 1 can copy and append a separator, reaching used 1023 with NUL at offset 1023. Used 1022 plus an empty nonfinal token does likewise. Used 1022 plus length 1 rejects the token, as does an initial length 1023. Empty nonfinal tokens can therefore still contribute a separator. The visible routine truncates by stopping at a whole-token boundary; it does not partially copy the rejected token.
This is a shared global buffer, not an owned return string. Reentrancy, caller lifetime, signed overflow with impossible/corrupt length inputs and native copy semantics remain unverified. Helper 187381 subsequently copies chat content with capacity 150 and calls 186435; the exact transformation and final effective text limits are still open.
CHAT-003: recipient front gates and remaining send behavior
The delivery helper 186721 applies these recovered early gates:
- Sender-record offset 3128 greater than global time 34992532 rejects delivery; equality passes. This is a mute-deadline interpretation supported by the later
(MUTED)log format, not a complete mute lifecycle or time-wrap contract. - Registered g_allowChat, mirror integer 34077172, supplied default
2, must be nonzero. Channel mode zero additionally requires its value at least 2; modes 1/2/4 pass the observed gate when it is 1. Other numeric domains have not been constrained by runtime configuration. - Recipient object must be nonzero, object offset 532 nonzero, and its record pointer at offset 520 nonzero. Offset 532's complete field identity is not assigned here.
- Recipient-record word 3780 + 4 * sender_slot must be zero. The sender slot is derived from its record pointer, shared record base and stride 6336. Any nonzero ignore word rejects; complete ignore-table creation, bounds and lifetime remain open.
- Recipient-record state at 468 must equal 2.
- For channel mode 1, the visible team path accepts matching record fields 3096 when helper 33715 reports nonzero. If that helper reports zero or the comparison fails, the same-object exception allows sender-to-self; otherwise delivery is rejected. Fixtures supply the helper result explicitly.
These are individually bounded gates, not a composed end-to-end recipient decision. The intervening g_allowChat==3/global-bit/state branch, later dead-chat policy, fields 3740/4324/4, round/pause state, and exact formatting remain open and are retained in the complete helper partition.
Send helper 187381 uses a mode jump table, builds a 64-byte name prefix, copies/transforms text, then either sends toward an explicit target with a possible sender echo or iterates slot objects 34091368 + slot * 880, invoking 186721 per slot. The final delivery helper formats me, ordinary chat variants or cchat for private messages, then sends trap 17 to the recipient slot derived from the object address. Full table/format selection, mutations, escaping, reliable-command admission and rendering are not closed by this pass.
Correction to the preceding batch
The previous BND-003 fixture incorrectly treated local 3356 as the chat channel mode. With ENTER frame 3340, locals 3348/3352/3356 hold the first/second/third incoming arguments; channel mode is 3352. The early branch at 187929 reads the third argument. All observed say/say_team/me callers pass zero there and can proceed toward slash/auth checks. A nonzero third argument bypasses those checks through concatenation/common-send handling. The analyzer, generated evidence, tests, prior findings and BND-003 documentation are corrected together. This correction does not establish that every later permission or command check succeeds on every channel.
Completion and remaining work
RE-261–263 cover native table/admission/filter evidence, bounded concatenation boundaries and recipient front gates. RE-264 tracks native caller/flood/tokenizer execution, complete chat/dead-chat/ignore/mute lifecycle and formatting/queue/rendered-output fixtures. RE-260 remains open for the broader native filtering and chat runtime contract. Original assets remain in ignored cache; only code/documentation and generated analysis evidence are added.
The DEL-001–003 follow-up recovers bounded dead-chat policy, formatter arguments and echo/fanout calls. It narrows the later-path questions above; complete lifecycle and runtime output remain open under RE-264/268.