Skip to content

Per-Game Caps

The core’s mechanisms (registry walking, vtable interposition, stub emission) are game-generic. Its numbers are not: singleton RVAs, hook slots, registry heads differ per title — and per build. usems-cap is the single table where those facts live.

GameCap {
game: Game, // Portal1 | Portal2 | HalfLife2 | HalfLife2Episodes
game_folders: &["portal"], // -game values that identify the title
engine_size: 7180288, // engine.dll image size — build fingerprint
reg_heads: &[ // s_rgInterfaces head globals (regwalk)
RegHeadSpec { module: "vstdlib.dll", rva: 0x8fbcc },
RegHeadSpec { module: "engine.dll", rva: 0x687e94 },
RegHeadSpec { module: "client.dll", rva: 0x555f40 },
RegHeadSpec { module: "server.dll", rva: 0x775e18 },
],
client: Some(ClientVtableSpec {
version: "VClient017", // interface string to resolve
singleton_rva: 0x4ab534, // where CreateFn's object lives
gp_globals_rva: 0x4ab294, // CGlobalVarsBase* global
frame_slot: 11, // HudUpdate vtable slot
}),
server: Some(ServerVtableSpec {
version: "ServerGameDLL009",
singleton_rva: 0x676e68, // CreateFn: mov eax, 0x10676e68; ret
tick_slot: 5, // GameFrame (66/s cadence verified live)
}),
}

GameCap::detect(command_line, engine_size):

  1. Parse -game <folder> from the process command line.
  2. Match against game_folders. No match → None (unknown title).
  3. Fingerprint check: engine_size must equal the caps’ recorded value. Mismatch → None, even though the folder matched — a patched or updated binary invalidates every RVA at once, so all game-specific features turn off rather than crash.
let cap = GameCap::detect(r#""...\hl2.exe" -game portal -steam"#, 7180288);
assert_eq!(cap.unwrap().game, Game::Portal1);
GameCap::detect("-game portal", 12345) // wrong build → None
GameCap::detect("-game garrysmod", ...) // unknown → None

At boot the core logs the verdict:

[USEMS] game detected: Portal1 (caps verified)
-- or --
[USEMS] game NOT recognized (or binary fingerprint mismatch); game-specific features disabled

Unsupported or unrecognized builds still get: interface probing by name, the mod loader, Luau sandboxing, commands and cvars. They lose: regwalk dumps, the frame hook, everything that needs an offset. The degradation is logged, never silent, never fatal.

Game reg_heads client server Notes
Portal 1 4 heads, verified live verified (slot 11) verified (slot 5) every value from shipped binaries
Half-Life 2 empty None None awaits RE pass
HL2 Episodes empty None None awaits RE pass
Portal 2 empty None None different branch (VClient018-era); full RE pass required

Every fact in a cap came from a tool in tools/, and every new game gets the same treatment (see RE Toolkit):

1. tools/find_reghead2.py <dll>... → registry head RVAs
2. walk the registry (regwalk) → interface names + CreateFn RVAs
3. resolve the interface, disassemble CreateFn → singleton RVA
4. usems_hook_profile client|server → candidate slots
5. disassemble candidates → confirm GameFrame/HudUpdate
6. write the GameCap entry + engine_size fingerprint
7. inject, verify live, commit