Skip to content

Interface Resolution

Source modules expose their functionality through a factory system: each DLL exports CreateInterface(name, &retcode) and keeps a linked registry of everything it can hand out. PoZoUSEMS never hardcodes what exists — it walks the registries at runtime and calls the factories.

The registry layout was recovered DIY by disassembling the shipped CreateInterface exports (see RE Toolkit):

InterfaceReg { +0: CreateFn(void*), +4: Name(char*), +8: Next(InterfaceReg*) }
s_rgInterfaces — per-module head global (pointer to first node)

find_interface(module, version) -> Option<*mut c_void>

Section titled “find_interface(module, version) -> Option<*mut c_void>”

Calls a module’s CreateInterface export and returns the interface pointer only on success (retcode == 0).

// Resolve the engine's client interface (the one the frame hook rides on):
let client = interfaces::find_interface("client.dll", "VClient017");
match client {
Some(ptr) => msg(&format!("[USEMS] VClient017 -> {ptr:p}\n")),
None => msg("[USEMS] VClient017 NOT found\n"),
}

call_create_interface(module, version) -> Result<*mut c_void, i32>

Section titled “call_create_interface(module, version) -> Result<*mut c_void, i32>”

The diagnostic variant: returns the factory’s return code on failure so callers can tell “unknown version” from “module not loaded”.

match interfaces::call_create_interface("engine.dll", "VEngineCvar004") {
Ok(ptr) => msg(&format!("[USEMS] engine has it: {ptr:p}\n")),
Err(1) => msg("[USEMS] engine.dll doesn't host VEngineCvar004 (rc=1)\n"),
Err(-1) => msg("[USEMS] engine.dll not mapped (rc=-1)\n"),
Err(rc) => msg(&format!("[USEMS] refused, rc={rc}\n")),
}
// Portal 1 reality: engine.dll returns rc=1 for VEngineCvar004 —
// the cvar system lives in vstdlib.dll, which returns it at rc=0.

Canonical ICvar acquisition: vstdlib.dllCreateInterface("VEngineCvar004"), falling back to the VStdLib_GetICVarFactory() export if needed. This is the pointer every mod command and cvar ultimately registers through.

if let Some(icvar) = interfaces::find_icvar() {
msg(&format!("[USEMS] ICvar live at {icvar:p}\n"));
// register_builtins(icvar) → usems_test command + cvar
// register_dynamic_command(...) → mod-owned commands
}

create_interface_addr(module) -> Option<*mut c_void>

Section titled “create_interface_addr(module) -> Option<*mut c_void>”

Raw address of a module’s CreateInterface export — for when a bridge needs to call the factory itself with custom arguments.

let addr = interfaces::create_interface_addr("server.dll");
if let Some(addr) = addr {
msg(&format!("[USEMS] server factory at {addr:p}\n"));
}

loaded(module) -> bool / module_summary() -> Vec<(&str, usize)>

Section titled “loaded(module) -> bool / module_summary() -> Vec<(&str, usize)>”

loaded checks one module; module_summary lists every engine module that is currently resident with its image size. Boot logs this to show which DLLs the game has mapped before we touch anything.

if !interfaces::loaded("server.dll") {
msg("[USEMS] server.dll not mapped yet (still at main menu?)\n");
}
for (name, size) in interfaces::module_summary() {
msg(&format!("[USEMS] module {name} base-size={size}\n"));
}

regwalk::walk(module, head_rva) -> Result<Vec<RegEntry>, String>

Section titled “regwalk::walk(module, head_rva) -> Result<Vec<RegEntry>, String>”

Walks a module’s InterfaceReg chain from its head global (head RVAs come from per-game caps). Validates every hop stays inside the module and caps the walk at 512 nodes — a corrupt chain errors instead of crashing the game.

let module = platform::get_module("vstdlib.dll").unwrap();
for head in cap.reg_heads { // from usems-cap
match regwalk::walk(&module, head.rva as usize) {
Ok(entries) => for e in &entries {
msg(&format!("[USEMS] {} @ {:#x}\n", e.name, e.create_fn));
},
Err(why) => msg(&format!("[USEMS] walk failed: {why}\n")),
}
}

The whole-repository dump: walks every capped module and logs each interface name with its CreateFn address. One injection → complete map of what the running game exposes.

regwalk::dump_all();
// [USEMS] regwalk: engine.dll exposes 46 interface(s)
// [USEMS] regwalk: client.dll exposes 78 interface(s)
// ...151 total on Portal 1

The generic pattern works for every entry in the tables below:

// Pick anything from the tables, e.g. GAMEEVENTSMANAGER002 from engine.dll:
let events = interfaces::find_interface("engine.dll", "GAMEEVENTSMANAGER002");
// Server tick side, e.g. ServerGameDLL009:
let gamedll = interfaces::find_interface("server.dll", "ServerGameDLL009");

Recovered by regwalk::dump_all() from a running game — 2026-08 build. RVAs are per-module, from the shipped Portal 1 binaries.

Notable: VClient017 is the client interface the frame hook rides on; VClientEntityList003 is the entity enumeration target; VClientPrediction001 drives prediction overrides.

Interface CreateFn RVA
VCLIENTTOOLS001 0x284a60
ClientRenderTargets001 0x2390c0
GameMovement001 0x237db0
PortalOpenAmount_IMaterialProxy003 0x230950
PortalStatic_IMaterialProxy003 0x230970
PortalStaticModel_IMaterialProxy003 0x230990
PortalPickAlphaMask_IMaterialProxy003 0x230740
Shield_IMaterialProxy003 0x223730
VClientPrediction001 0x218430
FleshInterior_IMaterialProxy003 0x213910
VortEmissive_IMaterialProxy003 0x210c40
HeliBlade_IMaterialProxy003 0x1fcdf0
WorldDims_IMaterialProxy003 0x1eb400
WaterLOD_IMaterialProxy003 0x1e8790
MotionBlur_IMaterialProxy003 0x1dab90
engine_post_IMaterialProxy003 0x1dab40
VCENTERPRINT002 0x1c5710
ToggleTexture_IMaterialProxy003 0x1be900
CurrentTime_IMaterialProxy003 0x1be760
TextureScroll_IMaterialProxy003 0x1be720
Pupil_IMaterialProxy003 0x1a2f00
PlayerLogo_IMaterialProxy003 0x1a2b20
EntityRandom_IMaterialProxy003 0x1a2ac0
EntitySpeed_IMaterialProxy003 0x1a2af0
PlayerPosition_IMaterialProxy003 0x1a2b80
PlayerSpeed_IMaterialProxy003 0x1a2be0
PlayerView_IMaterialProxy003 0x1a2c40
PlayerTeamMatch_IMaterialProxy003 0x1a2c10
PlayerProximity_IMaterialProxy003 0x1a2bb0
Health_IMaterialProxy003 0x1a14c0
VParticleSystemQuery001 0x1883f0
ParticleSphereProxy_IMaterialProxy003 0x17dc00
MatrixRotate_IMaterialProxy003 0x1716e0
TextureTransform_IMaterialProxy003 0x171710
SelectFirstIfNonZero_IMaterialProxy003 0x1711d0
WrapMinMax_IMaterialProxy003 0x171290
LessOrEqual_IMaterialProxy003 0x171140
Empty_IMaterialProxy003 0x171030
Abs_IMaterialProxy003 0x170f70
Exponential_IMaterialProxy003 0x171080
GaussianNoise_IMaterialProxy003 0x1710e0
UniformNoise_IMaterialProxy003 0x171260
LinearRamp_IMaterialProxy003 0x171170
Int_IMaterialProxy003 0x171110
Frac_IMaterialProxy003 0x1710b0
Equals_IMaterialProxy003 0x171050
Sine_IMaterialProxy003 0x171200
Clamp_IMaterialProxy003 0x170fd0
Divide_IMaterialProxy003 0x171000
Multiply_IMaterialProxy003 0x1711a0
Subtract_IMaterialProxy003 0x171230
Add_IMaterialProxy003 0x170fa0
lamphalo_IMaterialProxy003 0x16f810
lampbeam_IMaterialProxy003 0x16f6e0
IsNPC_IMaterialProxy003 0x16c790
Ep1IntroVortRefract_IMaterialProxy003 0x126fc0
EntityOriginAlyx_IMaterialProxy003 0x126f60
EntityOrigin_IMaterialProxy003 0x126f90
IEffects001 0x126960
Dummy_IMaterialProxy003 0x1260e0
ShadowModel_IMaterialProxy003 0x115a30
Shadow_IMaterialProxy003 0x115a80
ClientLeafSystem002 0x108f90
VClientEntityList003 0x104f90
ClientVirtualReality001 0x1024b0
VClient017 0xfae80
VClientDllSharedAppSystems001 0xfae60
GameClientExports001 0xfae70
Camo_IMaterialProxy003 0xf8340
MaterialModifyAnimated_IMaterialProxy003 0xda1f0
MaterialModify_IMaterialProxy003 0xda220
ConveyorScroll_IMaterialProxy003 0xd3c90
BreakableSurface_IMaterialProxy003 0xd3990
AnimateSpecificTexture_IMaterialProxy003 0x7dd20
AnimatedTexture_IMaterialProxy003 0x7dba0
AnimatedOffsetTexture_IMaterialProxy003 0x7db30
AnimatedEntityTexture_IMaterialProxy003 0x7da90
Alpha_IMaterialProxy003 0x7d510

Notable: VEngineClient014 (client-side engine — exec, cvar plumbing), VEngineServer021 (server-side engine), GAMEEVENTSMANAGER002 (event listen/fire), VEngineRenderView014 (world→screen, future overlay work), VEngineRandom001 (seeded RNG identical to the engine’s).

Interface CreateFn RVA
VWorkshop001 0x24f440
VEngineVGui001 0x23dde0
VSERVERENGINETOOLS001 0x2380a0
VCLIENTENGINETOOLS001 0x238090
VTOOLFRAMEWORKVERSION002 0x2380b0
VENGINETOOLFRAMEWORK003 0x236fb0
VENGINETOOL003 0x236fb0
XboxSystemInterface001 0x22dd00
VProfExport001 0x22b4f0
VoiceServer002 0x22a2b0
VENGINE_GAMEUIFUNCS_VERSION005 0x224b20
VENGINE_HLDS_API_VERSION002 0x224b00
VENGINE_LAUNCHER_API_VERSION004 0x224b10
StaticPropMgrServer002 0x2203d0
StaticPropMgrClient004 0x2203c0
SpatialPartition001 0x21c890
ReplayDemoPlayer001 0x20c320
VEngineRandom001 0x20a7f0
VEngineServerStringTable001 0x201030
VEngineClientStringTable001 0x200c10
VModelInfoClient006 0x1e9100
VModelInfoServer004 0x1e9110
VModelInfoServer003 0x1e9110
VENGINE_MATCHMAKING_VERSION001 0x1e59d0
GAMEEVENTSMANAGER001 0x1b9fe0
GAMEEVENTSMANAGER002 0x1b9da0
EngineTraceClient003 0x1b7430
EngineTraceServer003 0x1b7440
EngineClientReplay001 0x1b4e00
EngineReplay001 0x1b4e10
DownloadSystem001 0x1a7e60
VCvarQuery001 0x1a14c0
VEngineServer021 0x17d4e0
ServerUploadGameStats001 0x17aef0
GameServerData001 0x179150
ISERVERPLUGINHELPERS001 0x174b30
VEngineRenderView014 0x164340
VEngineShadowMgr002 0x1623e0
VEngineEffects001 0x156b20
VEngineModel016 0x1342b0
VPhysicsDebugOverlay001 0x1071c0
VDebugOverlay003 0x1071b0
VEngineClient013 0xc9090
VEngineClient014 0xc9090
IEngineSoundServer003 0x7e600
IEngineSoundClient003 0x7de80
// Engine's own RNG — same numbers the engine would roll:
let rng = interfaces::find_interface("engine.dll", "VEngineRandom001");
// Game events (listen/fire), the modern manager version:
let events = interfaces::find_interface("engine.dll", "GAMEEVENTSMANAGER002");

Notable: ServerGameDLL009 is the server interface a future tick hook will ride; ServerGameEnts001 gives entity access server-side; PlayerInfoManager002 wraps player enumeration. The repeated ServerGameTags001 entries with distinct RVAs are per-vtable registrations of the same interface for different entity classes — a registry quirk worth knowing when matching counts.

Interface CreateFn RVA
VSERVERCHOREOTOOLS001 0x483270
VSERVERTOOLS002 0x483280
VSERVERTOOLS001 0x483280
ServerGameTags001 0x46c070
GameMovement001 0x435450
ServerGameTags001 0x28fac0
ServerGameTags001 0x2771b0
ServerGameTags001 0x24c560
PluginHelpersCheck001 0x211690
BotManager001 0x210550
PlayerInfoManager002 0x210530
PlayerInfoManager001 0x210540
ServerGameTags001 0x20c870
ServerGameTags001 0x1b3310
HLTVDirector001 0x1a8600
VServerDllSharedAppSystems001 0x18c0b0
ServerGameClients004 0x18c0c0
ServerGameClients003 0x18c0c0
ServerGameEnts001 0x18c0e0
ServerGameDLL009 0x18c0d0
ServerGameDLL008 0x18c0d0
ServerGameTags001 0x18c0f0
IEffects001 0x15b6a0
ServerGameTags001 0xe2df0
ServerGameTags001 0xbf200
// The server-side game interface — future tick-hook host:
let gamedll = interfaces::find_interface("server.dll", "ServerGameDLL009");
// Player enumeration, newest version:
let players = interfaces::find_interface("server.dll", "PlayerInfoManager002");

The cvar system’s true home — the single most important interface in the stack. VEngineCvar004 is what registers every mod command and cvar; see Hook Engine for how it’s driven.

Interface CreateFn RVA
VProcessUtils001 0x9120
VEngineCvar004 0x4810
// The interface everything else is built on:
let icvar = interfaces::find_interface("vstdlib.dll", "VEngineCvar004");
// engine.dll also exposes VCvarQuery001 (0x1a14c0) — the engine's
// linkability checker, not the registrar. Don't confuse them.
  • Same name, different trailing number = newer interface generation (VEngineClient013 vs 014, ServerGameDLL008 vs 009). Prefer the highest the target game hosts; both entries often share one CreateFn.
  • 001-only names (VWorkshop001, DownloadSystem001) are one-shot interfaces that never versioned.
  • Two interfaces sharing an RVA (VSERVERTOOLS001/002, VENGINETOOLFRAMEWORK003/VENGINETOOL003) are aliases — resolving either yields the same object.