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)Core resolution functions
Section titled “Core resolution functions”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.find_icvar() -> Option<*mut c_void>
Section titled “find_icvar() -> Option<*mut c_void>”Canonical ICvar acquisition: vstdlib.dll → CreateInterface("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")), }}regwalk::dump_all()
Section titled “regwalk::dump_all()”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 1Resolving any interface by name
Section titled “Resolving any interface by name”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");All 151 interfaces (Portal 1, live dump)
Section titled “All 151 interfaces (Portal 1, live dump)”Recovered by regwalk::dump_all() from a running game — 2026-08 build.
RVAs are per-module, from the shipped Portal 1 binaries.
client.dll (78)
Section titled “client.dll (78)”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 |
engine.dll (46)
Section titled “engine.dll (46)”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");server.dll (25)
Section titled “server.dll (25)”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");vstdlib.dll (2)
Section titled “vstdlib.dll (2)”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.Version-suffix rules of thumb
Section titled “Version-suffix rules of thumb”- Same name, different trailing number = newer interface generation
(
VEngineClient013vs014,ServerGameDLL008vs009). 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.