Worker w-cmdtable-a1, Task cmdtable. Source: re_probes/runtime_87400/module.bin (92,188,672 Bytes, Layout "virtual RVA", Base 0x7FF62C210000, pid 87400).
Tool: tools/probe_cmdtable.py, Result: re_probes/cmdtable/commands.json.
All data is purely static and read-only; no game launch, no process attach, no write access.
PYTHONPATH=re_probes/python_deps python tools/probe_cmdtable.py
Summary
map and devmap are registered. They are simply not discoverable because the Dispatcher searches in one of two command lists depending on the execution context. The console always runs in a restricted context and sees only 34 of 234 commands. This is not a Cvar or a launch parameter, but an internal context value – however, there is already an unlocked command that legally sets it to "unrestricted": resourceExec.
This confirms result variant (c) of the briefing, with the caveat: the command exists, is gated, and the gate is reachable without a patch – but runtime confirmation is still pending.
1. Registry Structure
idCmdSystemLocal resides as a Singleton in .data at RVA 0x4104420 (vtable 0x2978618, verified). It contains two idList heads:
| Field | RVA | Meaning | Status in Snapshot |
|---|---|---|---|
+0x08 / +0x10 / +0x14 |
0x4104428 |
List all | 234 / Capacity 256 |
+0x28 / +0x30 / +0x34 |
0x4104448 |
List exposed | 34 / Capacity 64 |
+0x200b8 |
0x41244d8 |
Restriction-Level | 0 (Idle) |
The two arrays themselves are Heap allocations (0x15e579120, 0x21c170200) and lie outside the Module Capture. A pure data run over the live table is therefore impossible offline – enumeration had to proceed via registration.
Node layout (read from AddCommand itself, not assumed), 0x38 Bytes:
+0x00 name* +0x08 function* +0x10 argCompletion* +0x18 description* +0x20 flags
idCmdSystemLocal::AddCommand (vtable slot 4, RVA 0x1264840, identified via the string idCmdSystemLocal::AddCommand: %s already defined) converts the Flags:
0x12648e8 mov ecx, [rsp+0xa8] ; flags-Argument of caller
0x12648f3 or esi, 4
0x12648f6 and al, 2
0x12648f8 cmove esi, ecx ; stored = (arg & 2) ? arg|4 : arg
...
0x1264a37 test byte [rdi+0x20], 4 ; only with bit 2 also in the second list
0x1264a3b je ...
Bit 1 (0x2) is passed as a constant at the call site; Bit 2 (0x4) derives from AddCommand and decides inclusion in the exposed list. There is no code path that changes this subsequently.
2. Two Independent Attacks
Code Attack – Disassembly (capstone 5.0.7, vendored) of every function referencing the Singleton or calling AddCommand directly; Register/Stack-slot tracker reads name, callback, description, argCompletion, flags from each call site. Result: 165 call sites (virtually call [rax+0x20] and directly call 0x1264840 in idCmdSystemLocal::Init).
Data Attack – two static registration forms in .data:
- Self-registration list, head at
0x30B5900→ first node0x2fe4d00, read byInitat0x1264794and passed toAddCommandvia a loop (0x126479b–0x12647ca). 63 nodes (0x30 Bytes,{next,name,fn,comp,desc,flags}). - Flat table at
0x2efd230, 0x28-Byte Records: 6 entries (mdnt_*).
Sum: 165 + 63 + 6 = 234. Overlap 0.
3. Checks
| Control | Result |
|---|---|
Positive control: writeConfig, conDump, listCvars, exec, bind, toggle, TakeScreenshot |
all present – OK |
Cross-check against live table re_probes/runtime_87400/commands_live.json |
234 vs. 234, only_live=[], only_static=[], 0 flag deviations, 0 exposed deviations |
Negative control --sec-delta 0x8 / 0x28 / 0x40 / -0x1000 / 0x200000 |
each terminates with Exit 2, no table |
Negative control --base-delta 0x10000 |
Terminate: vtable 0x2968618 != expected 0x2978618 |
Negative control --sec-delta -0x1b0 (Offset containing a module pointer) |
Terminate at vtable comparison, not at pointer test |
Negative control with --ignore-anchor (intentionally bypassing anchor guard) |
Chain-Walk yields 0 nodes, no plausible table |
Flag distribution is unambiguous: 0x6 → 34 times, all exposed; 0x0 → 196 times; 0x200000 → 4 times (God, InfiniteHealth, NoPlayerDeath, Notarget), none of which are exposed. Flag↔exposed correlation: 234/234.
4. Why map reports "Unknown command"
Dispatcher idCmdSystemLocal vtable slot 16, RVA 0x1265150:
0x12651a7 mov eax, 0x28 ; Offset exposed list
0x12651ac mov ecx, 8 ; Offset all list
0x12651b1 cmp dword [r14+0x200b8], 0
0x12651b9 cmove eax, ecx ; Raw bytes 0f 44 c1 = CMOVZ
Level 0 → search in all 234; Level ≠0 → search only in the 34 exposed. No match → 0x126533e → Unknown command '%s'\n (0x2982790).
The level (cmdSystem+0x200b8, RVA 0x41244d8) is not a Cvar. A complete scan of all 121,682 functions / 7,072,620 instructions yields 146 references; as a literal, only 0 (constructor 0x1214619) and 1 are written, everything else is Save/Restore pairs or passed-through parameters. Setting it to 1 is done by:
| Context | Function |
|---|---|
| Console input | 0xa8db00 (sets 0xa8db77, restores 0xa8e42a), 0xa90410 |
Start parameter +… |
0xa5fdf0 (references com_enableDeveloperMode, devMode_) |
| Start Config | 0xa60220 |
| Common Init/Frame | 0xa638b0 |
Command exec |
0x12638e0 (0x1264170) |
Therefore, map, devmap, and also +map as a start parameter fail, while writeConfig/conDump/listCvars work. The snapshot shows Level 0 because it was taken in idle state outside any command execution – this is consistent, not a contradiction.
Addendum to the Cvar question: devMode_enable (int value at 0x2fe3790, currently 1) only switches the branch at 0x12652ce ("Attempting to call a developer command" → warning and execution, instead of "Dev Mode is not enabled…"). This branch is unreachable at Level 1 because the search there never happens outside the exposed list. com_enableDeveloperMode=1, devMode_enable=1, g_demoDevMode=1 change nothing at Level – the measurement is thus fully explained.
5. Likely path without patch: resourceExec
resourceExec is one of the 34 exposed commands (flags 0x6, callback 0xa7a040), so callable from the console:
USAGE: resourceExec <filename> -s [define]: execute a script file
Unlike exec (enforces Level 1), it sets the level to 0:
0xa7a74b mov ebx, [0x41244d8] ; save old level
0xa7a76d mov edx, edi ; edi = 0
0xa7a76f mov [0x41244d8], edx ; Level := 0
0xa7a790 lea rdi, [0x4104470] ; buffer "unlimited"
0xa7a797 lea r15, [0x41144a0] ; buffer "limited"
0xa7a7a6 test edx, edx
0xa7a7a8 cmovne rcx, r15 ; edx=0 -> unlimited buffer
There are two separate command buffers in .data that preserve the level across the delayed execution. The frame drain empties them with the respective level:
0xa6569d xor edx, edx ; Level 0 0xa6569f call 0x1265800 ; -> unrestricted buffer 0xa656a4 mov edx, 1 ; Level 1 0xa656b0 call 0x1265800 ; -> restricted buffer
This causes the content of a script file executed via resourceExec to run at Level 0, allowing the dispatcher to search all 234 commands – map, devmap, teleport, killMonsters, etc., would be accessible there.
Status: static derivation, not verified at runtime. Verifiable prediction: a script file in the Resource archive containing the line map <name>, invoked as resourceExec <file>. A runtime test was forbidden to me.
Second verifiable prediction that would falsify the model: listCmds selects its list at 0x1262bf3/0x1262bfa also via the level. Therefore, it must output exactly 34 commands when entered in the console. Similarly, toggle (flags 0x0) must deliver "Unknown command" in the console, even though it is registered.
6. Verdict by Name
| Name | registered | Flags | exposed | Callback | Source |
|---|---|---|---|---|---|
map |
yes | 0x0 |
no | 0xa7e2c0 |
Call site 0xa6f707 |
devmap |
yes | 0x0 |
no | 0xa7e0d0 |
Call site 0xa6f765 |
spawn |
no | – | – | – | no call site, no record |
teleport |
yes | 0x0 |
no | 0xf3bdf0 |
Call site 0xf46751 |
god (small) |
no | – | – | – | – |
God (large) |
yes | 0x200000 |
no | 0xfdfaa0 |
Registrar list 0x2fb4bf0 |
noclip |
no | – | – | – | – |
killMonsters |
yes | 0x0 |
no | 0xf39df0 |
Call site 0xf4683c |
reloadDecls |
yes | 0x0 |
no | 0xa415e0 |
Call site 0xa3a682 |
listCmds |
yes | 0x6 |
yes | 0x1262b10 |
Init, direct call 0x12646b6 |
Additional map loaders, all with flags 0x0 and thus not exposed:
fastmap (0xa7dee0), loosemap (0xa7e3e0), cmap (0xa7e2c0, same callback as map), checkmap (0xa7dcc0), slowmap/slowmapcheckpoint/slowcombomap, nextmap (0xa80600), RestartMap*, loadGameIntoMap (0xa85d80), LoadPreviousMapFromSaveGame (0x11dedd0), restartmapwithlobby (0xa80830).
They all share the argument completion 0xa7e500.
For the save route relevant: loadGame (0xa85680) and saveGame (0xa86250) are exposed (flags 0x6) and thus directly callable from the console; the non-exposed loadGameIntoMap uses the same completion function 0x12667c0.
The 34 exposed commands:
activateConsole, bind, BuildInfo, BuildInfoToClipBoard, clear, conDump, configVersion, dumpWarnings, echo, exec, exit, find, getviewpos, listBinds, listCmds, listCvars, loadGame, parse, print, quit, resourceExec, RS_ValidateContainers, saveGame, TakeScreenshot, ToggleMainMenu, unbind, unbindAll, vid_restart, vstr, vt_delayedRestart, vt_restart, wait, where, writeConfig
7. Open Points and Risks
- The
resourceExecpath is not verified at runtime. It remains unclear from which path the file is read (the name suggests the Resource archive) and whether name resolution imposes additional restrictions. - One call site (
0x12647bf) remains intentionally unresolved in the code attack – this is the loop that iterates through the Registrar list, and is covered by the data attack. - "String present in
.rdata" was not consistently treated as evidence. Each of the 234 entries either has a decodedAddCommandcall site or a validated static record; the count matches exactly with the live table.