Skip to content

Research Report R8 Tno Cvars

LopeKinz edited this page Oct 4, 2026 · 1 revision

TNO Cvar Registry, Loader Audit, and Delivery Fix

Round-8 pass on The New Order's cvar registry, read from the decrypted runtime image: the full registered cvar list with flags and ranges, the startup order of command-line cvars, the cheat reset and the startup commands, and the force path behind toggle. It audits every TNO cvar the loader can emit and documents the resulting loader change: cheat cvars are now also sent as +toggle name value value, which survives the reset. Static only; the one-launch in-game test at the end has not been run.

Static only. Nothing launched or attached to, and nothing written under the game folder or Saved Games. Source: the decrypted runtime capture re_probes/runtime_tno_186352 (base 0x7ff74a390000) plus Ghidra (tools/decomp.py --gzf D:/wolf-decomp/ghidra/tno_runtime.gzf --snapshot re_probes/runtime_tno_186352).

1. Registry: method and counts

The registry walk worked, so nothing had to fall back to string mining. idCVar::Init (RVA 0x9dea10) links every cvar into one list: mov rax,[0x1ef6cf0]; mov [rbx+0x70],rax; mov [0x1ef6cf0],rbx. The cheat reset in idCommonLocal::Init walks this same list (r7). Every node is a static object in .data. The layout comes from the constructor's own stores: +0x00 value idStr, +0x28 int, +0x2c float, +0x30 name, +0x38 reset string, +0x40 description, +0x48 flags, +0x4c min, +0x50 max, +0x58 value-string list, +0x60 completion, +0x70 next. "No range" is min > max (1, -1).

tools/extract_cvars_tno.py pins those constructor bytes. It walks the list and writes docs/cvars_tno.json ({source, count, cvars:[{name, description, value(=reset/default), type, flags, min, max, values}]}, sorted). --self-check re-walks the list and compares the result with the file.

cvars 3194, 0 duplicates, the walk ends at NULL, every node lies inside the image
CHEAT / NOCHEAT 2865 / 329. Every entry has exactly one of the two, as the constructor's test edi,0x1c010 predicts
ROM / INIT / ARCHIVE 8 / 38 / 169. Flag bits 0x20-0x2000 are never used
ranges / value lists 130 / 4
plausibility TNC's registrar dump has 3256 cvars (same engine family). 3132 of the 4543 prefix_name-shaped .rdata strings are registered (the rest are entity keys, decls and so on). 807 of the 877 kind=cvar entries of the old adjacency miner (tno_cmdtable.json) are registered. 692 of its 1154 kind=cmd entries are actually cvars, so that table's cmd/cvar split is unreliable
negative check 0 of the 192 real commands (re_probes/tnocommands/commands.json) are cvars. map devmap toggle kill teleport exec bind god noclip notarget give are all absent
known cvars com_allowConsole BOOL CHEAT, devgui INTEGER CHEAT 0..2 (both as r7 P2.2 predicts). g_permaGodMode BOOL NOCHEAT ARCHIVE, g_fov INTEGER NOCHEAT 10..100, pm_runspeed FLOAT CHEAT

Correction to r7 P2.4: g_permaGodMode and g_permaInfiniteAmmo are not cheat cvars. Both are ARCHIVE, and therefore NOCHEAT.

2. Startup order (new, decompiled idCommonLocal::Init 0x133380 and FUN 0x130b70)

This also answers r7's open question about where the command-line +cvar parser lives.

  1. 0x13365c: FUN_130b70(args, 1) runs over every +name value pair. For each name that is a cvar, it calls Set(cvar, value, force=1) (0x9dd3f0 -> 0x9dd2d0), INIT cvars included.
  2. 0x1337ad: com_skipIntroVideo is read here (cmp [0x161e248],0, the flag goes to the intro start).
  3. build.cfg, then FUN_130b70 again (0x13397b). Then exec default.cfg (4 devices), wolfConfig.cfg (gated lines), and an inline third pass of +cvar pairs that skips INIT cvars and force-sets the rest (0x133d99). So a +name value beats the user's wolfConfig.cfg.
  4. 0x133e22: ResetFlaggedVariables(CHEAT), the cheat reset.
  5. After Init: the startup commands (+toggle, +bind). Measured: +toggle devgui survives the reset (docs/limits.md).

toggle (0x9ddc00, decompiled):

  • With 2 or 3 arguments it is a float toggle (non-zero -> 0, else the value).
  • With 4 or more arguments it cycles through the values, comparing them with the current string.
  • It sets through cvar-system slot +0x38, the force path (r7 P2.1).
  • So toggle name v v always ends at v: on a match it takes argv[3], otherwise it wraps to argv[2]. It logs name = v.

3. Audit: every TNO cvar the loader can emit

Emitters: the catalogue (31), ALWAYS (com_skipIntroVideo), cli launch --god/--ammo/--console/--skip-intro/--fov (all catalogue names), the saved config.json tweaks (catalogue names), and the free-text box. No TNO mod carries cvars.

cvar loader (old): kind / default / range registry: type / default / range flags old +name v result new delivery
g_permaGodMode bool / 0 / bool / 0 / NOCHEAT ARCHIVE works (force-set after wolfConfig.cfg) +name v
g_permaInfiniteAmmo bool / 0 / bool / 0 / NOCHEAT ARCHIVE works +name v
g_infiniteAmmo bool / 0 / bool / 0 / CHEAT broken: set, then wiped by the reset +name v + +toggle name v v
g_damageScale float / 1 / 0..10 float / 1 / NOCHEAT ARCHIVE works (damage taken, the note was wrong) +name v
ai_damageScale float / 1 / 0..10 float / 1.0 / CHEAT broken + toggle
ai_invulnerable bool / 0 / bool / 0 / CHEAT broken + toggle
g_AimAssist_Disable_All bool / 0 / bool / 0 / CHEAT broken + toggle
g_playerHealthRegenSpeed float / ? / 0..1000 float / 5.75 / CHEAT broken + toggle
g_playerHealthRegenDelay float / ? / 0..10000 float / 3 / CHEAT broken + toggle
g_gravity float / ? / -2000..2000 float / 1066 / CHEAT broken + toggle
g_gameDifficulty choice 0-3 "easy..very hard" int / -1 / -1..4 CHEAT broken, choices wrong (-1 = game's own, 0 very easy .. 4 nightmare) + toggle
g_fov int / 80 / 60..130 int / 80 / 10..100 NOCHEAT works; values >100 are clamped (UpdateValue 0x9dcf10 reads min/max) +name v
pm_noBob bool / 0 / bool / 0 / CHEAT broken + toggle
pm_togglesprint bool / 0 / bool / 1 / NOCHEAT ARCHIVE works; default was wrong +name v
pm_autoSprint bool / 0 / bool / 0 / CHEAT broken + toggle
com_skipIntroVideo (ALWAYS) bool / 0 / bool / 0 / CHEAT probably works: read at 0x1337ad, before the reset (r6's "does not work" came from the reset log line only) +name v kept, toggle harmless
com_allowConsole bool / 0 / bool / 0 / CHEAT broken, and the label was false: it does not gate the console (r7 P2.4); its only reader is game init -> achievement check + toggle; note says leave it off
com_showFPS bool / 0 / int / 0 / 0..2 NOCHEAT ARCHIVE works +name v
r_swapInterval choice / -1 / int / -1 / NOCHEAT works +name v
g_showHud bool / 1 / bool / 1 / NOCHEAT ARCHIVE works (+g_showHud 0 verified in game) +name v
g_EnableGore bool / 1 / int / 1 / CHEAT broken; actually 0 blood only / 1 gore / 2 none + toggle
g_bloodEffects bool / 1 / bool / 1 / NOCHEAT ARCHIVE works +name v
g_goreBloodyMess bool "Maximum gore" / 0 float / 2.0 / CHEAT broken, and wrong meaning: a perk gore-probability factor, so "1" lowers it + toggle; now a float entry
pm_runspeed / walk / sprint / crouch float / ? float / 260 / 140 / 450 / 115 CHEAT broken + toggle
pm_jumpheight float / ? float / 62 CHEAT broken + toggle
pm_noclipspeed float / ? float / 400 CHEAT broken, and pointless: there is no noclip command + toggle
pm_sprintMaxTime float / ? / 0..100000 int / 1500 / 1..100000 CHEAT broken + toggle
g_freeCamSpeed float / ? float / 1.0 CHEAT broken + toggle

Cross-check against the measured lists: 0 mismatches. All 26 RETAIL_BLOCKED names are CHEAT, including devgui, g_debugGore, g_showThinks and g_useSavegameDifficulty, which are not in the catalogue. All 9 measured-accepted names in docs/limits.md are NOCHEAT. All 31 catalogue names exist, and none is ROM or INIT.

Commands and binds, checked against commands.json:

  • god, noclip, notarget, give and spawn exist neither as commands nor as cvars.
  • devgui is a cvar.
  • kill, teleport (an entity name, not coordinates), devmap, map, toggle, screenshot, killMonsters and restartmap exist.
  • The bind F3 noclip printed "Unknown command".
  • The closest working substitute is g_freeCam (CHEAT, "enable debug camera (free move)").
  • The god-mode equivalent is toggle g_permaGodMode 0 1.

Do the force-set values act? For all 22 catalogue CHEAT cvars plus g_freeCam and devgui, no function that reads the cvar also reads the cheat global 0x1df21c8. This is a same-function heuristic: callers were not checked. One exception: the read site of g_gameDifficulty (0x853b12) lies outside any .pdata function and was not checked.

4. What changed

  • wolfsdk/cvars.py:
    • tno_route(key) takes the routing from docs/cvars_tno.json (lazy, cached):
      • plain sends +name value.
      • cheat sends +name value and then +toggle name value value. The early +arg is kept for readers that run during startup, such as com_skipIntroVideo.
      • drop covers names that are not cvars, plus ROM cvars.
      • The registered spelling is used. Without the file, RETAIL_BLOCKED decides.
    • build_args(settings, binds, title_key=None) routes only when title_key == "tno".
    • blocked_for("tno") now means "needs the +toggle route".
    • TNO_BINDS and binds_for(title): F3 becomes toggle g_freeCam 0 1. TNC keeps DEFAULT_BINDS unchanged.
    • COMMANDS rewritten to commands that exist.
    • Catalogue corrections: g_fov range, pm_togglesprint default, g_gameDifficulty choices, g_EnableGore as a choice, g_goreBloodyMess as a float, pm_sprintMaxTime as an int, the missing defaults, and the com_allowConsole/g_permaGodMode/g_damageScale notes.
    • Docstring updated. Dead is_blocked() removed.
  • wolfsdk/gui.py: Start passes binds_for(title) and title_key, and names the dropped names in the status line. The TNO row tag "✕ blocked in retail" is now "↻ cheat cvar - also sent as +toggle at start". The Info tab text is corrected (Ctrl+^, real commands, TNO binds).
  • wolfsdk/cli.py: launch/play pass binds_for(title) and the title. The tweaks text and the --console help are corrected.
  • wolfsdk/mod.py: docstring sentence only.
  • tools/build_release.py: ships docs/cvars_tno.json. Without it, the release would fall back to RETAIL_BLOCKED.
  • New: docs/cvars_tno.json, tools/extract_cvars_tno.py, tools/verify_tno_tweaks.py.

5. Checks

python tools/extract_cvars_tno.py --self-check
  3194 cvars (2865 CHEAT, 329 NOCHEAT, 8 ROM, 38 INIT, 169 ARCHIVE); 0 of 192 console commands collide
  self-check: OK
python tools/verify_tno_tweaks.py      -> 25 PASS, "all checks passed" (exit 0)
  incl. "PASS  TNC +args identical to the pre-change capture" (args captured before any edit)
  mutation run (cheat->plain, old binds): 5 FAIL, so the checks can fail
python tools/verify_gui.py             -> "Alles in Ordnung." (exit 0)
python tools/verify_loader_english.py  -> "Alles in Ordnung." (exit 0)
python -m py_compile  (cvars, gui, cli, mod, build_release, extract_cvars_tno, verify_tno_tweaks) -> OK

6. Open points (only the game can settle these)

  • Nobody has run +toggle name v v with values in game. The code path is decompiled, and +toggle devgui without values is measured. Where the startup commands run (after Init) is known only from that measurement, not from code.
  • com_skipIntroVideo: the read before the reset is code-proven. That the 0x200 flag really skips the intro is not.
  • The maintainer's saved TNO tweaks change behaviour now.
    • g_goreBloodyMess 1 now takes effect and halves the factor (default 2.0). Clear it, or use e.g. 4.
    • com_allowConsole 1 now takes effect, and per r7 it feeds the achievement check. Clear it.
  • ARCHIVE cvars (g_permaGodMode, g_fov, g_showHud, ...) are written to wolfConfig.cfg by the game. Unticking one in the loader sends nothing, so the last value sticks. Set it back to its default once instead.
  • devgui via +toggle at startup is zeroed at the main menu by the DevGUI gate (r7 P2.5). It only works inside a level, through the console or a bind.
  • devmenu.py still lists noclip and notarget. It writes debugmenu.txt, which has no consumer in TNO (r7 P2.5), so it was left alone.

7. In-game test for the maintainer (one launch)

  1. In the loader (The New Order): clear g_goreBloodyMess and com_allowConsole. Set Run speed = 600 and FOV = 95, leave the key bindings on, then press Start.
  2. Did the intro logos play? Expected: no.
  3. Load a save and run. Expected: clearly faster than normal (600 vs 260). Press F3. Expected: a free camera. Press F3 again to return.
  4. Optional: Ctrl+^ should open the console. Type toggle g_permaGodMode 0 1.
  5. Quit and search %USERPROFILE%\Saved Games\MachineGames\Wolfenstein The New Order\base\qconsole.log for pm_runspeed. Expected: Resetting cheat cvar: pm_runspeed, later followed by pm_runspeed = 600. There should be no CANNOT be set in retail and no Unknown command for F3.

If pm_runspeed = 600 appears but running is not faster, the reader is gated somewhere upstream (see the heuristic in §3).

Related: Research Report: R7 Tno Cheat Gate, Research Report: R7 Tno Commands, Modding Limits, TNO CVar Catalog.

Clone this wiki locally