urt-rs / docs /research /GAME_CVARS.md
nmarshall's picture
Recover authentication selectors command checks and mode paths
9ac939d
|
Raw History Blame Contribute Delete
8.24 kB
# Game cvar registry and authentication announcement callers
Static pass, 2026-10-11. Reproduce with `python3 scripts/analyze_game_cvars.py`; check with `python3 scripts/test_game_cvars.py`. [Generated evidence](game-cvars.json) includes every row of the recovered main qagame registry, 126 decoded-instruction registration argument traces, registration/update function partitions, three direct announcement callers, 11 verbosity references and both Linux ELF64 Cvar_Register/Cvar_Update graphs. Inputs are source/disassembly-hash verified. No original game process or complete QVM executes.
## CVAR-001: 126-row main registry
Qagame registration function 2677 initializes its table pointer to VM address **4**, increments it by **28** bytes per row, and compares the row index against the initialized word at **3532**, whose value is **126**. Thus `4 + 126 * 28 = 3532`: the table ends exactly at the count word. Update function 2950 uses the same start, stride and count address.
Each row contains seven 32-bit words. The registration argument slice loads mirror pointer at offset 0, name pointer at 4, default pointer at 8 and flags at 12, forwarding them to trap 3 (encoded CALL target -4). Offset 16 is initialized modification-count cache and is assigned from mirror offset 4 when a mirror exists. Offsets 20 and 24 are retained as raw words pending complete behavioral analysis; their presence in control flow does not by itself establish final domain names.
The report records all 126 unique names, defaults, raw flags, mirror addresses, original string addresses and source table/file offsets. Empty defaults are valid and preserved. Each row’s actual registration instruction slice is evaluated with initialized table memory and checked against that row’s decoded fields. It records the call without invoking registration or applying cvar policy.
This is the **main table**, not every engine or game cvar. Additional direct registration sites exist elsewhere, including registration calls after this table loop and other subsystems. Those and dynamically generated names need separate coverage. Flags remain numeric here; do not infer archive/serverinfo/readonly meanings solely from an upstream enum without verifying this native fork. Defaults are supplied registration arguments, not effective settings after configs, command-line values or native registration merges.
Authentication-related rows include:
| Name | Supplied default | Raw flags |
| --- | --- | --- |
| auth | 0 | 68 |
| auth_status | init | 68 |
| auth_enable | 1 | 1 |
| auth_cheaters | 1 | 1 |
| auth_tags | 1 | 1 |
| auth_notoriety | 0 | 1 |
| auth_groups | Empty | 1 |
| auth_owners | Empty | 1 |
| auth_verbosity | 1 | 1 |
| auth_cmds_friend | bigtext say | 1 |
| auth_cmds_member | bigtext say map restart | 1 |
| auth_cmds_referee | bigtext say map restart swapteams shuffleteams slap reload | 1 |
Command-list strings do not establish how commands are authorized, parsed or overridden. That policy remains research work.
## CVAR-002: native mirror and auth_verbosity identity
`auth_verbosity` is registry row 122 at VM address 3420. Its mirror pointer is **35002960**. Both native `Cvar_Update` graphs copy a native cvar’s numeric words into mirror offsets 8 and 12, and its string into offset 16 with a 256-byte bound. The native integer word is stored at mirror offset **12**. Therefore the announcer's global **35002972 = 35002960 + 12** is the integer view of `auth_verbosity`.
Native Cvar_Update reads the mirror handle at offset zero and checks it against the native index count. It compares modification counts and skips unchanged values; a missing native string also skips the update. On the update path it assigns mirror modification count at offset 4 before checking the string length, copies the string at offset 16, then copies numeric words to offsets 8 and 12. The visible mirror view spans 272 bytes; this does not establish every surrounding allocation field.
`strlen + 1` must be unsigned at most **256**, so at most 255 non-NUL bytes fit on the normal path. An invalid handle or longer string calls `Com_Error(1, ...)`. The earlier [error boundary](ENGINE_CONTROL.md) establishes frame abort on the ordinary Linux mode-1 path; post-call instructions in a syntactic graph are not proof that these failures return. Registration graphs are retained for subsequent merge/default/flag policy recovery, rather than claimed completely specified here.
The announcer’s fallback behavior now has a cvar identity: `auth_verbosity=1` selects broadcast print, `=2` selects broadcast chat, and other values select no fallback output. These are exact comparisons in the helper. This does not imply that effective runtime verbosity is always its default.
The previously traced positive gate at **35017616** is a different address. It is not the integer mirror of `auth_enable` (35003256 + 12), `auth` (35003848 + 12), or auth_verbosity. Its initialization and role remain to be traced; do not rename it from its apparent effect alone.
## CVAR-003: three direct announcement callers
A complete adjacent CONST-165352/CALL scan finds these three qagame call sites:
| Function entry / CALL instruction | Arguments and bounded interpretation |
| --- | --- |
| 1264 / 1379 | Selected record, mode 2, caller’s third-argument object. Its usage/error strings identify an `auth-whois` query helper; successful tail forwards those values unchanged. |
| 166169 / 166744 | Response-selected record, mode 0, zero third argument. This local branch calls only when auth_verbosity is signed positive. |
| 174399 / 175025 | Begin-selected record, mode 1, zero third argument, after the one-shot/pending gates already documented. |
Query helper 1264 starts with the positive-35017616 gate. An empty selector chooses usage output; a failed selection chooses unknown-client output. For a zero caller object these paths use diagnostic helper 5123; for a nonzero caller object they calculate its recipient through object offset 520 and client stride 6336, then send chat commands. The success path invokes the detailed mode-two announcer. Exact selector grammar and identity lookup in helper 253561 remain open. Usage strings alone do not prove the command dispatch/registration route or caller permissions.
The authentication response slice tests auth_verbosity with `<= 0`, so a positive value other than one or two still invokes mode-zero helper 165352; its fallback then produces no output. Five synthetic verbosity inputs -1/0/1/2/3 verify this local distinction. They do not execute preceding response parsing or validate a packet’s trust/identity.
The 11 literal integer-view references occur in entries 171, 165352, 166169 and 171668. Several are outside the announcer and affect initialization/response/private message paths. All are retained as instruction windows rather than prematurely interpreted as a complete global usage specification. The registry pointer supplies an indirect update route even though no literal CONST-35002960 occurs in qagame code.
## Completion and next work
Follow-up: [CHG-001–003](CVAR_CHANGES.md) recover the visible word-20/24 behavior, modification-cache gates, four numeric setting branches and 28 direct registration sites (23 exact constant-argument traces). Raw field values remain preserved; complete native policy and all row-word mutations are still open.
RE-245–247 cover the main registry extraction, native mirror/verbosity connection and bounded direct caller argument/gate evidence. RE-230/244 remain open for full caller/object/global-update/reset and rendered-output behavior. RE-248 adds the remaining cvar registrations, native flag/merge/update policy, table word-20/24 semantics, positive-gate initialization and auth-whois selector/dispatch/permission recovery. The next implementation contract should use recovered names and fields rather than inferred global labels.
Further follow-up: [QUERY-001–003](AUTH_QUERIES.md) connects auth-whois to both direct dispatch routes, recovers its selector helpers and role-check policy, and assigns the positive gate a bounded internal authentication-mode role. Full selector/permission/configuration lifecycle remains open.