sots-re/ghidra/addresses.d/lane-v2.json
alex 648028db67 lane V2: vtable inversion — resolve indirect call edges image-wide
Every call-graph result in this repo was computed over direct (E8) edges.
5,045 of the 5,207 functions named by a vftable slot have zero direct call
sites, so all of those results were lower bounds. Lane Z's dominant RNG
consumer hung off exactly such an edge.

tools/vtable_map.py builds, from the RTTI walk plus a full sweep to the next
function start (rule 17):
  * vftable -> class -> sub-object offset -> slot -> target, and its inverse
  * the class hierarchy from the RTTI base lists, so an abstract interface
    with one concrete override resolves uniquely
  * constructor-derived member typing (ctor result -> [this+d])
  * the slot index at every indirect call site, with a backward register
    resolver that refuses to cross a branch target rather than guess
  * `this`-carrier spans and this/member call-graph propagation of class

Validation (12/12): rediscovers ServerTradeManagerImpl slot 10 ->
GenerateTradeRaidEncounters from the dispatch at 0x007d8469 with nothing
hand-fed, and re-derives the *Impl rule for both managers. Receiver-class
pinning reaches only 2.5% of the 6,398 virtual sites, at 0.6% out-of-range
against a 70% chance baseline; the displacement-only route measured worse
than random (81% vs 58%) and is rejected outright.

Closes lane K's tier-4 blind spot: all nine phase-23 calls and both phase-33
calls named. Four of the eleven reach a draw on the strategic generator
(StrategyServer+0x16c) at eight instruction-verified sites, none ever
observed firing — so "the tail draws nothing" is a property of eight turns,
not of the code. Also resolves the nine parked inlined-draw functions to
their vtable roots (correcting how that was recorded: none is itself in a
vftable; their topmost ancestors are), and finds 14,958 inter-function tail
jump edges without which three of them look like dead code.
2026-09-08 12:05:05 -04:00

102 lines
No EOL
6.5 KiB
JSON

{
"entries": [
{
"name": "StrategyServer_off_SpyManager",
"offset": "0x158",
"convention": "member",
"prototype": "Game::ServerSpyManager* StrategyServer::SpyManager, in lane T's S+4 frame (absolute StrategyServer+0x15c). StrategyServer ctor 0x007d78d0: `call 0x00832a30` (the ServerSpyManager ctor, identified by its store of vftable 0x00a3073c) then `mov [esi+0x15c],eax` at 0x007d7d8e. Corroborated independently by 0x007dcf90, which the RTTI inverse map shows is Game::StrategyServer vftable 0x00a26034 slot 14 at sub-object +4: it calls the same two ctors and stores at [esi+0x154] and [esi+0x158], exactly 4 lower than the base-frame 0x158/0x15c, as a +4 `this` requires. Sits immediately after StrategyServer_off_TradeManager (lane T, 0x154 in the same frame)",
"status": "verified"
},
{
"name": "ServerSpyManager_vftable",
"addr": "0x00a3073c",
"convention": "data",
"prototype": "void* Game::ServerSpyManager::vftable[18] // sub-object +0, COL 0x00a87ed4, bases Game::IServerSpyManager / Game::ISpyManager / Mars::IStreamable. Unlike the trade manager there is no *Impl: ServerSpyManager is itself concrete (no purecall slots) and has no derived class. A second vftable 0x00a30728 sits at sub-object +4 with 3 slots",
"status": "verified"
},
{
"name": "ServerTradeManagerImpl_vslot7",
"addr": "0x0088ad60",
"convention": "thiscall",
"prototype": "void (Game::ServerTradeManagerImpl* this) // vftable 0x00a31b74 slot 7 (+0x1c). Reached ONLY virtually, from 0x007d97ea in StrategyServer::OnAllCombatDone_Tail phase 23 (call 6 of 8). Zero direct call sites. Body not read; its direct+tail-jump closure is 189 functions and contains no RNG entry point and none of the eleven inlined-draw functions",
"status": "mapped"
},
{
"name": "ServerTradeManagerImpl_vslot8",
"addr": "0x0088e8d0",
"convention": "thiscall",
"prototype": "void (Game::ServerTradeManagerImpl* this, int arg1) // vftable 0x00a31b74 slot 8 (+0x20). Reached ONLY virtually, from 0x007d97b6 in OnAllCombatDone_Tail phase 23 (call 2 of 8); the site pushes one argument. Closure 86 functions, draw-free",
"status": "mapped"
},
{
"name": "ServerTradeManagerImpl_vslot9",
"addr": "0x00868060",
"convention": "thiscall",
"prototype": "void (Game::ServerTradeManagerImpl* this) // vftable 0x00a31b74 slot 9 (+0x24). Reached ONLY virtually, from 0x007d97dd (phase 23, call 5 of 8). Closure 185 functions, draw-free",
"status": "mapped"
},
{
"name": "ServerTradeManagerImpl_vslot11",
"addr": "0x00848570",
"convention": "thiscall",
"prototype": "void (Game::ServerTradeManagerImpl* this) // vftable 0x00a31b74 slot 11 (+0x2c). Reached ONLY virtually, from 0x007d97d0 (phase 23, call 4 of 8). Closure 188 functions, draw-free",
"status": "mapped"
},
{
"name": "ServerTradeManagerImpl_vslot12",
"addr": "0x0088e920",
"convention": "thiscall",
"prototype": "void (Game::ServerTradeManagerImpl* this) // vftable 0x00a31b74 slot 12 (+0x30). Reached ONLY virtually, from 0x007d97c3 (phase 23, call 3 of 8). Closure 193 functions, draw-free",
"status": "mapped"
},
{
"name": "ServerTradeManagerImpl_vslot13",
"addr": "0x0088ef80",
"convention": "thiscall",
"prototype": "void (Game::ServerTradeManagerImpl* this) // vftable 0x00a31b74 slot 13 (+0x34). Reached ONLY virtually, from 0x007d97f7 (phase 23, call 7 of 8). REACHES THE STRATEGIC GENERATOR: -> 0x00820ca0, NextFloat at 0x00820e18 with the generator loaded as [reg+0x16c] then `lea ecx,[ecx+4]`; and -> 0x0088b440, NextInt at 0x0088b613 with `mov ecx,[ecx+0x16c]; add ecx,4`. Neither was ever observed firing: lane Z measured 0 tail words on 8 turns",
"status": "mapped"
},
{
"name": "ServerTradeManagerImpl_vslot14",
"addr": "0x008590d0",
"convention": "thiscall",
"prototype": "void (Game::ServerTradeManagerImpl* this) // vftable 0x00a31b74 slot 14 (+0x38). Reached ONLY virtually, from 0x007d97a7 (phase 23, call 1 of 8). Closure 143 functions, draw-free",
"status": "mapped"
},
{
"name": "ServerTradeManagerImpl_vslot15",
"addr": "0x0082cca0",
"convention": "thiscall",
"prototype": "void (Game::ServerTradeManagerImpl* this) // vftable 0x00a31b74 slot 15 (+0x3c). Reached ONLY virtually, from 0x007d9804 (phase 23, call 8 of 8). REACHES THE STRATEGIC GENERATOR: Chance at 0x0082cdb8, generator loaded at 0x0082cda4 as `mov eax,[eax+0x16c]` then `mov ecx,eax`. Never observed firing",
"status": "mapped"
},
{
"name": "ServerSpyManager_vslot13",
"addr": "0x008877b0",
"convention": "thiscall",
"prototype": "void (Game::ServerSpyManager* this) // vftable 0x00a3073c slot 13 (+0x34). Reached ONLY virtually, from 0x007d9811 in OnAllCombatDone_Tail phase 23 -- the ninth call of the block, and the only one whose receiver is StrategyServer+0x15c rather than +0x158. REACHES THE STRATEGIC GENERATOR: Chance at 0x00887c8a on `mov ecx,[ecx+0x16c]`, and through 0x008408e0 Chance at 0x00840929 and 0x00840a3c plus NextInt at 0x008409c7, all on [reg+0x16c]. Never observed firing",
"status": "mapped"
},
{
"name": "ServerSpyManager_vslot14",
"addr": "0x0088db80",
"convention": "thiscall",
"prototype": "void (Game::ServerSpyManager* this) // vftable 0x00a3073c slot 14 (+0x38). Reached ONLY virtually, from 0x007d989b in OnAllCombatDone_Tail phase 33 (call 1 of 2). REACHES THE STRATEGIC GENERATOR: Chance at 0x0088dc43 on `mov ecx,[eax+0x16c]`. Never observed firing",
"status": "mapped"
},
{
"name": "ServerSpyManager_vslot15",
"addr": "0x00887f30",
"convention": "thiscall",
"prototype": "void (Game::ServerSpyManager* this) // vftable 0x00a3073c slot 15 (+0x3c). Reached ONLY virtually, from 0x007d98a8 in OnAllCombatDone_Tail phase 33 (call 2 of 2). Closure 230 functions, draw-free",
"status": "mapped"
},
{
"name": "SVScriptObject_EmptyOverride",
"addr": "0x0080c5a0",
"convention": "thiscall",
"prototype": "void (void* this) // the do-nothing body every Game::SVScriptObject-derived class inherits in the hook slots it does not override. Occupies most of slots 4/11/25/27/30 across the 30 SVSO classes, which is what makes the non-stub overrides countable: 14 at slot 4, 7 at slot 11, 8 at slot 25, 1 at slot 30, 0 at slot 27",
"status": "mapped"
}
]
}