# Ghidra Recon — Sword of the Stars (2006), "Sword of the Stars.exe" ImageBase 0x00400000, 32-bit MSVC-2010 DX9. Program `sots` in `/srv/re-lab/ghidra-projects`. All addresses below are Ghidra/virtual addresses (VA = ImageBase + RVA). Method: headless GhidraScripts (ReVa MCP was down this run). reva-server stopped for runs, restarted after. --- ## 1. Battle-load / many-core lead: ForceSingleCore ### Key strings | String | Address | |---|---| | `ForceSingleCore` | `0x00a36eec` (key literal), config-tag blob at `0x00a36ee8` | | `Limiting process affinity to CPU-%i...` | `0x00a36880` | | `SetProcessAffinityMask` (data ref name) | `0x00ad8202` | ### The affinity worker — `FUN_0089ee70` @ `0x0089ee70` ```c void FUN_0089ee70(void) { // core index arrives in ESI from caller int unaff_ESI; FUN_008ba1e0("Limiting process affinity to CPU-%i...", unaff_ESI + 1); HANDLE hProcess = GetCurrentProcess(); BOOL ok = SetProcessAffinityMask(hProcess, 1 << ((byte)unaff_ESI & 0x1f)); // pin to ONE core if (ok) FUN_008ba1e0(" OK.\n"); else FUN_008ba1e0(" FAILED!\n"); } ``` This is the whole mitigation: it pins the entire process to a single logical core via a one-bit affinity mask. `SetProcessAffinityMask` is the ONLY affinity/topology API the binary uses (see below). ### Where the config is read & branches — `FUN_008a0e50` @ `0x008a0e50` This is the application startup/init routine (also creates the window, D3D device, reads display.cfg/audio.cfg, spawns a thread). Relevant excerpt: ```c // ref site 0x008a0ee6 uVar3 = FUN_00898bc0(&DAT_00a36ee8, "ForceSingleCore", local_64c, local_630); // read config key FUN_008e7160(uVar3); ... iVar4 = FUN_008e7140(local_64c); // convert the read value -> int if (0 < iVar4) { // <-- the many-core branch: any value > 0 FUN_0089ee70(); // pin process to a single core } ``` `FUN_00898bc0` = generic config/ini lookup (key -> value string); `FUN_008e7140` extracts an integer from the parsed value. If the integer is > 0 the process is bound to one core (the core index is the value carried in ESI into `FUN_0089ee70`, and the `"CPU-%i"` log uses index+1). ### Read on the many-core slowdown The binary performs **no runtime CPU-topology detection at all** — there is no `GetSystemInfo`, `GetNativeSystemInfo`, `GetLogicalProcessorInformation(Ex)`, `GetActiveProcessorCount`, `GetProcessAffinityMask`, or `SetThreadAffinityMask` import anywhere. So there is **no adaptive "if many cores do X else Y" branch** in the code. `ForceSingleCore` is a blunt, opt-in override read once at startup that forces the whole process onto one core. Implication for the battle-load bug: the slowdown is not gated by a core-count code path we can flip; it is emergent multi-core behavior (thread contention / lock convoy / false sharing / scheduler thrash, or the D3D9 driver's own worker threads) that gets worse as the OS spreads the process's threads across more cores. Pinning to one core removes the cross-core traffic and masks it. Next place to dig for the actual contention is the thread creation sites: ### Threading entry points (CreateThread callers) — follow-ups | Caller | Address | Note | |---|---|---| | `FUN_00902350` | `0x00902350` | creates a thread at `0x0090242d` | | `FUN_008a0e50` | `0x008a0e50` | startup routine, creates a thread at `0x008a14ef` | | `FUN_00736e30` | `0x00736e30` | creates a thread at `0x00736e84` — likely a game/loader worker; prime suspect for the battle-load path | `SetProcessAffinityMask` external thunk: `EXTERNAL:00000052`; `CreateThread`: `EXTERNAL:00000019`. --- ## 2. vftable dumps (primary vftable per class) MSVC layout note: each class has a `..::vftable_meta_ptr` label (0 methods — that slot holds the RTTI Complete-Object-Locator pointer, sitting one word *before* the real table) immediately followed by `..::vftable`, the actual function-pointer array. Classes with multiple `vftable` groups use multiple inheritance (one sub-table per polymorphic base). Only the *primary* (largest / class-named) table is listed here; secondary tables are small MI thunks (`IStreamable` etc.). Repeated addresses = shared/thunk stubs: `0x0080c5a0` and `0x005f8ac0` are stub/return-nullptr style thunks reused across many slots; `0x00496e00` a shared pure/empty stub; `0x0085b290` a common getter. ### Game::ClientPlayer — vftable @ `0x00a32b68`, 8 methods ``` [0]0x00882630 [1]0x008f6630 [2]0x008188f0 [3]0x00818910 [4]0x0085b290 [5]0x008801d0 [6]0x0080c5a0 [7]0x0080c5a0 ``` ### Game::AIPlayer — vftable @ `0x00a199c4`, 11 methods ``` [0]0x004f7230 [1]0x005f8ac0 [2]0x005f8ac0 [3]0x00723ed0 [4]0x00692350 [5]0x00496e00 [6]0x00496e00 [7]0x00496e00 [8]0x00496e00 [9]0x00496e00 [10]0x006922e0 ``` (secondary MI tables: VectorHelper @0x00a1a700, StreamableHelper @0x00a1a730, each 3 methods.) ### Game::StarSystem — primary vftable @ `0x00a200e4`, 8 methods ``` [0]0x00753160 [1]0x00725130 [2]0x00751b30 [3]0x00751b40 [4]0x00751b40 [5]0x005f8ac0 [6]0x0085b290 [7]0x004f41d0 ``` (also a 3-method table @0x00a200d4; StreamableHelper @0x00a23cc4.) ### Game::Planet — primary vftable @ `0x009ef144`, 36 methods ``` [0]0x008a1860 [1]0x004e8960 [2]0x004e72b0 [3]0x0080c5a0 [4]0x004d75a0 [5]0x004d75d0 [6]0x004e16e0 [7]0x004d9cb0 [8]0x004e5e80 [9]0x0080c5a0 [10]0x005f8ac0 [11]0x00423710 [12]0x004229a0 [13]0x004f59c0 [14]0x005f8ac0 [15]0x004ddbf0 [16]0x0085b290 [17]0x00425770 [18]0x00425340 [19]0x00424b70 [20]0x004267f0 [21]0x00426810 [22]0x00426830 [23]0x00422bd0 [24]0x0080d1f0 [25]0x004242b0 [26]0x00496e00 [27]0x00496e00 [28]0x00496e00 [29]0x004d9c20 [30]0x0080c5a0 [31]0x0080c5a0 [32]0x005f8ac0 [33]0x0085b290 [34]0x00422b70 [35]0x005dbb90 ``` (Planet also: Creator vftable @0x009ee234 (1); two 2-method tables @0x009ef12c/0x009ef138. DOPlanet is a separate class @0x00a28cfc, 7 methods.) ### Game::ShipDesignDef — vftable @ `0x009fef64`, 3 methods ``` [0]0x00827240 [1]0x00827390 [2]0x0057c250 ``` (no deep hierarchy — RTTI shows 0 bases; the many `Mars::StreamableHelper` and `VectorHelper<...GunBank>` tables are serialization helpers for its member containers.) ### Game::Actor — primary vftable @ `0x009e2114`, 36 methods ``` [0]0x008a1860 [1]0x00426d10 [2]0x004267d0 [3]0x0080c5a0 [4]0x00426c80 [5]0x00426c90 [6]0x0080c5a0 [7]0x005f8ac0 [8]0x0080c5a0 [9]0x0080c5a0 [10]0x005f8ac0 [11]0x00423710 [12]0x004229a0 [13]0x004f59c0 [14]0x00422af0 [15]0x004267e0 [16]0x0085b290 [17]0x00425770 [18]0x00425340 [19]0x00424b70 [20]0x004267f0 [21]0x00426810 [22]0x00426830 [23]0x00422bd0 [24]0x0080d1f0 [25]0x004242b0 [26]0x00496e00 [27]0x005dbbc0 [28]0x00496e00 [29]0x00422830 [30]0x0080c5a0 [31]0x0080c5a0 [32]0x005f8ac0 [33]0x0085b290 [34]0x00422b70 [35]0x005dbb90 ``` (Actor also: Creator @0x009e1fa8 (1); two 2-method tables @0x009e20f8/0x009e2104. Interfaces IActorReference @0x009e604c (4), IActorEvents @0x009e90ac (2).) **Note:** Planet's primary table shares slots 11-13,16-25,30-35 verbatim with Actor's, and both are 36-wide — confirming Planet : Actor (Planet overrides slots 1-8,14,15,27-29). ### Mars::EntityBase — primary vftable @ `0x00a36fd0`, 10 methods ``` [0]0x008a1860 [1]0x008a1da0 [2]0x008a1c30 [3]0x0080c5a0 [4]0x0080c5a0 [5]0x0080c5a0 [6]0x0080c5a0 [7]0x005f8ac0 [8]0x0080c5a0 [9]0x0080c5a0 ``` (Creator @0x00a36fb0 (1); two 2-method MI tables @0x00a36fb8/0x00a36fc4.) ### Mars::PhysicalEntity — primary vftable @ `0x00a3710c`, 12 methods ``` [0]0x008a1860 [1]0x00423340 [2]0x008a24d0 [3]0x0080c5a0 [4]0x0080c5a0 [5]0x0080c5a0 [6]0x0080c5a0 [7]0x005f8ac0 [8]0x0080c5a0 [9]0x0080c5a0 [10]0x005f8ac0 [11]0x004227a0 ``` (Creator @0x00a36f50 (1); two 2-method MI tables @0x00a370f4/0x00a37100.) **Note:** slot [0]=0x008a1860 shared with EntityBase's slot [0] → PhysicalEntity : EntityBase (12 = 10 + 2 added slots [1],[2],[11] with 1 EntityBase slot overridden). --- ## 3. RTTI inheritance graph (from Base_Class_Array structures) RTTI is **fully populated** — namespaces are NOT empty; the "known empty-namespace issue" does **not** apply to this program. Chains read directly from each class's `RTTI_Class_Hierarchy_Descriptor` -> `RTTI_Base_Class_Array` -> BaseClassDescriptors -> TypeDescriptors. Listed most-derived → root; `mdisp` = byte offset of that base subobject. **ClientPlayer** (3 bases): `ClientPlayer` → `StrategyPlayer` → `Mars::NetworkObject`. **AIPlayer** (3): `AIPlayer` → `AIObject` → `Mars::IStreamable`. **StarSystem** (5): `StarSystem` → `StarMapNode`; + `Mars::NetworkObject`(@0), `Mars::HandleObject`(@12), `Mars::IStreamable`(@8). **ShipDesignDef** (1): `ShipDesignDef` — no base classes (leaf/standalone data-def class). **Mars::EntityBase** (7): `EntityBase` → `IScheduledEvent` → `ListDLI::Link`(@4) → `DLink`(@4); + `IScriptable`(@16), `NetworkObject`(@20), `HandleObject`(@28). **Mars::PhysicalEntity** (11): `PhysicalEntity` → `EntityBase` → `IScheduledEvent` → `ListDLI<...>::Link`(@4) → `DLink`(@4); + `IScriptable`(@16), `NetworkObject`(@20), `HandleObject`(@28), `RigidBody`(@44) → `Body`(@44), `SceneObject::Owner`(@212). **Game::Actor** (13): `Actor` → `TargetableEntity` → `Mars::PhysicalEntity` → `Mars::EntityBase` → `IScheduledEvent` → `ListDLI<...>::Link`(@4) → `DLink`(@4); + `IScriptable`(@16), `NetworkObject`(@20), `HandleObject`(@28), `RigidBody`(@44) → `Body`(@44), `SceneObject::Owner`(@212). **Game::Planet** (14): `Planet` → `Actor` → `TargetableEntity` → `PhysicalEntity` → `EntityBase` → `IScheduledEvent` → `ListDLI<...>::Link`(@4) → `DLink`(@4); + `IScriptable`(@16), `NetworkObject`(@20), `HandleObject`(@28), `RigidBody`(@44) → `Body`(@44), `SceneObject::Owner`(@212). ### Consolidated single-inheritance spine (Mars physics/entity → Game gameplay): ``` Body → RigidBody (Mars physics; mixed in at offset 44) DLink → ListDLI::Link (Mars intrusive list node; offset 4) IScheduledEvent IScriptable NetworkObject HandleObject IStreamable (Mars interfaces) \______________ all mixed into ______________/ Mars::EntityBase → Mars::PhysicalEntity → Game::TargetableEntity → Game::Actor → Game::Planet StrategyPlayer → ... , ClientPlayer : StrategyPlayer, NetworkObject AIObject → AIPlayer : AIObject, IStreamable StarMapNode → StarSystem : StarMapNode (+NetworkObject/HandleObject/IStreamable) ``` --- ## Artifacts / reproduction Scripts (pushed to CT111 `/root/`): `CoreRecon.java`, `VtRecon.java`, `RttiHier.java`. Run pattern: `systemctl stop reva-server; sleep 3;` then `analyzeHeadless /srv/re-lab/ghidra-projects sots -process "Sword of the Stars.exe" -noanalysis -scriptPath /root -postScript