From db9b5619646f77f79b3de1f970ad20fe35071c1d Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 8 Sep 2026 00:01:04 -0400 Subject: [PATCH] b2 static findings: tech effects corrections, full g_TechIdNames dump; contract 176->211 --- findings/subsystems/tech-effects.md | 83 ++++++++ ghidra/addresses.json | 288 +++++++++++++++++++++++++++- ghidra/generated/sots_addresses.h | 74 ++++++- 3 files changed, 439 insertions(+), 6 deletions(-) diff --git a/findings/subsystems/tech-effects.md b/findings/subsystems/tech-effects.md index 9e028b4..5a66755 100644 --- a/findings/subsystems/tech-effects.md +++ b/findings/subsystems/tech-effects.md @@ -190,3 +190,86 @@ an inferred field meaning; LOW = consumer identified but purpose inferred from c Not done: values of the 6-entry AI tech table (0x00a17888), the `+0x224/+0x228/+0x22c` player multipliers' producer (0x0077b620 copies them from a 0x50-byte per-player options record `+0x48/+0x4c/+0x50` — likely the game-setup difficulty/handicap block, MEDIUM), and the design-side consumers of the §3 masks. + +--- + +## 9. B2 corrections (2026-09-08) — read from the instruction stream, ReVa + `objdump -b binary -m i386` + +Nine things this note had wrong, missing, or at lower confidence than the binary supports. +Where the two disagree, the binary wins. + +1. **Every modifier is a float32 and every constant is a widened float literal.** The shape is + `fld dword [field]; fadd/fmul qword [k]; fstp dword [field]`. The doubles at the `.rdata` + addresses listed in §1 are *float32 values widened to double*, not the decimals: 0x009e5060 = + 0x3FA99999A0000000 = `(double)0.05f`, 0x009e2318 = `(double)0.1f`, 0x009ed188 = `(double)0.15f`, + 0x009e20d8 = `(double)0.2f`, 0x009e5df0 = `(double)0.3f`, 0x009f15b0 = `(double)0.35f`, + 0x00a33358 = `(double)0.45f`, 0x009e5068 = `(double)0.6f`, 0x00a33360 = `(double)0.06f`, + 0x009e23a0 = `(double)0.9f`. Exact: 0x009e5d70 = 0.25, 0x009e20a0 = 0.5, 0x009e8d28 = 0.75, + 0x009e90b8 = 1.5, 0x009e1ef0 = 1.0. `MaxOH`'s 0x009e22f0 is a **float32** 0.1f loaded with + `fld dword` and passed to `std::max`. `DRV_FarCast` writes float32 10.0 (0x00a2bed0), + 2.0 (0x009e5840) and `FLD1`. A reimplementation must round through float32 at *every* step. +2. **`g_AITechValueTable` (0x00a17888) is dumped.** Six 12-byte rows `{int techId, float odds, + float value}`, count at 0x00aea2ec = 6: + `{10021,0.1,0.5} {10022,0.1,0.5} {10023,0.1,0.5} {10083,0.2,0.0} {10024,0,0} {10025,0,0}`. + So the CCC_AI / AIAdmin / AIFac bonus is **0.5** into ResMod / IncMod(+0x30c) / OutMod, and the + AI-rebellion odds column is 0.1 for those three and 0.2 for **CCC_AIFRCON (10083)** — which is + in the table for its odds only; `ApplyAITechBonus` has no branch for it. `ApplyAITechBonus` + multiplies the value by `AIBn ? 1.0f : (float)[0x009e1fac] = -1.0f` and narrows the product to + float32 before adding. +3. **`PrGtTrf` (+0x148) is an int.** `mov ecx,[PTR]; mov edx,[esi+0x148]; cmp edx,[ecx]; jl` — a + signed integer max against an integer config word, not a float compare. §1's "PrGtTrf = + max(...)" is right, the type was not stated. +4. **`ServerPlayer+0x308` is a pointer, not three inline words.** `ServerPlayer:: + UpdateNodeBoreParams` (0x008182c0, called unconditionally in the tail) builds a local 3-word + struct via `SelectNodeBoreParams` (0x006e18e0); on success it `operator new(0xc)`s the block if + +0x308 is null and copies the three words in; on failure it `operator delete`s the block and + nulls +0x308. `SelectNodeBoreParams(out, player)` tests **RAD (0x2752) first, then REND + (0x2751), then RIP (0x2750)**, first hit wins — "highest wins" confirmed, HIGH not MEDIUM — + returning {95,60,5} / {65,35,4} / {45,15,3}, and false with out = {INT_MAX, INT_MAX, 0} when + none. **There is no species check**: these are not Zuul-gated in code. +5. **`RebuildSpeciesTechFlags` (0x0082bf10) has an undocumented second pass.** After filling + `flags[7]` at +0x348 (and writing the count word 7 at +0x364 — the 0x20 bytes are assigned as a + unit), it loops species 0..6 and, for each `sp != 4` (_NPC) whose `flags[sp] & 1` (translation + level 1) is set, ORs `1 << sp` into **`ServerPlayer+0x1a4`**. Sticky: only ever ORed, never + cleared, so it survives the first pass clearing a flag. Not previously recorded. +6. **Three tail steps are not effects of their tech.** `cdp` is a tail check — + `if (!cdp) { if HasResearched(10034) && HasResearched(10035) then cdp = 1 }` — evaluated on + every completion, not only on those two techs'. So are the +0x190/+0x194 masks and the + node-bore selection. §1's table reads as if they belong to the completing tech. +7. **The callback has no already-researched guard**, because `SetResearched` sets node state 4 + before invoking it. +8. **`*(int*)TechDef` is NOT the TechId.** `MasterTechTree::IsTech(def, id)` (0x0057d5d0) does + `if (id == 0xc5) fail; idx = id - 10000; if ((unsigned)idx > 0xc3) fail; return def && + master[0][idx] && def == master[0][idx]`. `TechTree::HasResearched(id)` (0x0057d810) does the + same map through `this+4` (the MasterTechTree) to reach a `TechDef`, and *then* uses that def's + first word as the index into `this+0x10` (the node vector), testing `node->state == 4`. So the + def's first word is a node index in a larger key space, the TechId key space is exactly + 10000..10195, and 197 is the "none" sentinel special-cased before the subtraction. `TechTree+4` + = `MasterTechTree*` is worth adding to the object map. +9. **`g_TechIdNames` (0x009ff9e4) is dumped in full.** 196 × `{const char* name, int}`; the + second word is `10001 + index` and is not the id (the id is `10000 + index`, per `IsTech`). + The names, in position order, are now in `sots-engine`'s `src/game/effects/tech_id.h`. Anchors + all agree: 0 CCC_AdvSens (0x2710), 30 IND_HrdStrct (0x272e), 38..43 the six vaccines + (0x2736..0x273b), 64..66 DRV_RIP/REND/RAD (0x2750..0x2752). Consequences for §4's filler-order + note: the block species order is **Human, Hiver, Tarkas, Liir, [Zuul,] Morrigi**; the families + that omit the Zuul are Incorporate (132-136), Addict (137-141), Temperance (142-146), + Accommodate (153-157) **and Proliferate (158-162)** — proliferate is five entries, not six. + Index 130, the Zuul slot of the level-3 translation block, is `XNC_DOMZUUL`. **Ids 163 and 164 + are `CCC_NDTRKHUM` and `CCC_NDTRKZUL`**, the node-track techs of §1 — previously unlocated. + §5's "no code reads them" list can now be stated by name rather than by index. +10. **`g_TechBitmaskTableA/B` are `{int techId, uint bit}`.** Joined with §3's bit-ordered name + lists every id matches the name table above, so §3's two lists are confirmed and are keyed by + id, not by name: A = 10092, 10170, 10091, 10090, 10171, 10169, 10084, 10111, 10172, + 10173-10176, 10088, 10073, 10071, 10106, 10166, 10167, 10099, 10168, 10177, 10110, 10109, + 10178, 10085, 10086, 10179, 10000, 10180, 10181, 10182; B = 10056, 10057, 10058, 10061, + 10062, 10063, 10059, 10183, 10060, 10184, 10185, 10083, 10064, 10065, 10066, 10042, 10186, + 10187, 10188, 10189, 10190, 10094, 10067, 10068, 10069, 10191, 10192, 10036, 10037. + + +Also confirmed unchanged: the chain is a strict if/else-if over `push 0x2710`..`push 0x272f` +(10000..10031) with no gaps and at most one branch taken; the tail then tests 0x2730/0x2731 +(CruisCon → GetTechDef(BrdPod) → SetResearched(def,1,1,0), gated on `Species == 5`) and +0x2732/0x2733 via HasResearched. `TechDef::GetPlagueCureMask` loops i = 0..5 over ids +10038..10042 with a NULL at i == 5, returning `1 << i` on the first hit, then falls through to +10043 returning 0x0f — and the per-id test is `0x0057d220(def, target)`, i.e. **def is the target +or a descendant of it**, not an exact match. Every magnitude in §1 is confirmed. diff --git a/ghidra/addresses.json b/ghidra/addresses.json index ef38f28..d0159c4 100644 --- a/ghidra/addresses.json +++ b/ghidra/addresses.json @@ -1214,9 +1214,9 @@ "name": "g_AITechValueTable", "addr": "0x00a17888", "convention": "data", - "prototype": "struct { int techId; int a; float value; }[6] /* values not dumped */", - "status": "unverified", - "source": "handoff/tech-effects.md#1" + "prototype": "struct { int techId; float rebellionOdds; float bonus; }[6] /* rows: CCC_AI/CCC_AIAdmin/CCC_AIFac odds 0.1f bonus 0.5f, CCC_AIFRCON odds 0.2f bonus 0, CCC_AIVrus/CCC_AISlv odds 0 bonus 0 */", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" }, { "name": "g_PERGATETRAFFIC_DRV_TpGate", @@ -1409,6 +1409,286 @@ "prototype": "std::vector (3 words; entry 0x18 B, {+0x8 int researchPercent, +0xc int researchActive, +0x10 int savings, +0x14 int savingsActive})", "status": "verified", "source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3" + }, + { + "name": "ServerPlayer_off_SuitTol", + "offset": "0xb4", + "convention": "offset", + "prototype": "float SuitTol -- raised by the two adaptation techs; also caps the hazard money cost", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_MaxOH", + "offset": "0xb8", + "convention": "offset", + "prototype": "float MaxOH -- max over-harvest slider, raised by a float32 max() against 0.1f", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_TechTree", + "offset": "0xf4", + "convention": "offset", + "prototype": "TechTree* the player's own tech tree; its +4 is the MasterTechTree the IsTech chain uses", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_AIBn", + "offset": "0xfe", + "convention": "offset", + "prototype": "bool AIBn -- AI benefit active; gates ApplyAITechBonus and flips the bonus sign", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_CnTrd", + "offset": "0xff", + "convention": "offset", + "prototype": "bool CnTrd -- trade routes allowed (set by CCC_FtlEcon unless RebAI)", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_CnRad", + "offset": "0x100", + "convention": "offset", + "prototype": "bool CnRad -- commerce raiding allowed (CCC_ComRaid)", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_CnVItl", + "offset": "0x101", + "convention": "offset", + "prototype": "bool CnVItl -- may view other empires' intel (CCC_DatCor)", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_hgs", + "offset": "0x102", + "convention": "offset", + "prototype": "bool hgs -- gravitic-syncing drive researched (DRV_GrvSyn); client-synced only", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_hadvs", + "offset": "0x103", + "convention": "offset", + "prototype": "bool hadvs -- advanced sensors (CCC_AdvSens)", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_harcc", + "offset": "0x104", + "convention": "offset", + "prototype": "bool harcc -- arcologies (IND_ArcCon)", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_pddm", + "offset": "0x108", + "convention": "offset", + "prototype": "float pddm -- multiplied by 0.25 by IND_HrdStrct", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_ConMod", + "offset": "0x10c", + "convention": "offset", + "prototype": "float ConMod[3] -- construction cost per hull class; techs subtract from all three, IND_OrbDry only from [1] and [2]", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_SavMod", + "offset": "0x118", + "convention": "offset", + "prototype": "float SavMod[3] -- IND_OrbFound subtracts 0.05f from all three", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_OutMod", + "offset": "0x124", + "convention": "offset", + "prototype": "float OutMod -- industrial output multiplier; several techs add, IND_HrdStrct multiplies by 0.9f", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_PopMod", + "offset": "0x130", + "convention": "offset", + "prototype": "float PopMod", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_TerraMod", + "offset": "0x134", + "convention": "offset", + "prototype": "float TerraMod", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_AMine", + "offset": "0x138", + "convention": "offset", + "prototype": "bool AMine -- asteroid mining (IND_AstMine)", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_MinRate", + "offset": "0x140", + "convention": "offset", + "prototype": "float MinRate -- mining rate; IND_MsMine adds 1.0", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_PrGtTrf", + "offset": "0x148", + "convention": "offset", + "prototype": "int PrGtTrf -- per-gate traffic capacity; the two gate techs raise it with a SIGNED INTEGER max against a config int, not a float compare", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_CstR", + "offset": "0x150", + "convention": "offset", + "prototype": "float CstR, CstE, CstT at +0x150/+0x154/+0x158 -- set to 10.0f / 2.0f / 1.0f (FLD1) by DRV_FarCast", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_TechMaskA", + "offset": "0x190", + "convention": "offset", + "prototype": "uint32 design-option mask A; +0x194 is mask B. Rewritten wholesale by ComputeTechBitmasks on every completion; not serialised", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_TranslationKnown", + "offset": "0x1a4", + "convention": "offset", + "prototype": "uint32 sticky 'level-1 translation researched for species sp' bit mask, one bit per species, NPC (4) excluded. Second pass of RebuildSpeciesTechFlags; only ever ORed", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_HasVac", + "offset": "0x288", + "convention": "offset", + "prototype": "uint32 HasVac; +0x28c HasImm. Both get |= the plague-cure mask of the completing tech", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_NPTrk", + "offset": "0x290", + "convention": "offset", + "prototype": "uint32 NPTrk -- bit per species whose node-space traffic is visible; set by that species' node-track tech", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_NodeBore", + "offset": "0x308", + "convention": "offset", + "prototype": "int (*)[3] -- pointer to a separately allocated 3-word node-bore parameter block, NULL while no bore drive is researched (the updater allocates and frees it)", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_CaptureDesigns", + "offset": "0x330", + "convention": "offset", + "prototype": "bool cdp -- set once CCC_SpyBm and IND_SlvgTech are both researched; the test is in the completion tail, not in either tech's branch", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_SpeciesTechFlags", + "offset": "0x348", + "convention": "offset", + "prototype": "uint32 flags[7] xenotech bits per target species, followed by a count word 7 at +0x364 (0x20 bytes assigned as a unit)", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_ResearchRollPending", + "offset": "0x3b4", + "convention": "offset", + "prototype": "bool -- a pending plague-cure roll; run and cleared when the completing tech is the current research target", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_off_AIRebellion", + "offset": "0x3b8", + "convention": "offset", + "prototype": "AIRebellion* -- non-null while an AI rebellion object exists; the two AI techs notify it", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "TechTree_off_Master", + "offset": "0x4", + "convention": "offset", + "prototype": "MasterTechTree* -- the `this` MasterTechTree::IsTech / GetTechDef are called on", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_UpdateNodeBoreParams", + "addr": "0x008182c0", + "convention": "fastcall", + "prototype": "void (ServerPlayer* this) -- re-selects the node-bore parameters from the researched set and stores them in the +0x308 block, allocating it on first use and freeing it when no bore drive is researched", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "SelectNodeBoreParams", + "addr": "0x006e18e0", + "convention": "cdecl", + "prototype": "bool (int out[3], ServerPlayer* p) -- highest researched bore drive wins: DRV_RAD {95,60,5}, else DRV_REND {65,35,4}, else DRV_RIP {45,15,3}; false (and out left at {INT_MAX,INT_MAX,0}) when none. No species gate", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "ServerPlayer_SpeciesOfTranslationTech", + "addr": "0x0080e410", + "convention": "custom", + "prototype": "int (TechDef* def in EBX) -- index of the species whose SpeciesDef+0x74 node-track tech this def is, or -1", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "AITechRow", + "addr": "0x00690f70", + "convention": "cdecl", + "prototype": "struct {int techId; float rebellionOdds; float bonus;}* (TechDef* def) -- linear search of g_AITechValueTable, NULL when the def is none of them", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" + }, + { + "name": "g_AITechValueCount", + "addr": "0x00aea2ec", + "convention": "data", + "prototype": "int -- number of rows in g_AITechValueTable (6)", + "status": "verified", + "source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240" } ] -} +} \ No newline at end of file diff --git a/ghidra/generated/sots_addresses.h b/ghidra/generated/sots_addresses.h index 5bb917e..49e54a3 100644 --- a/ghidra/generated/sots_addresses.h +++ b/ghidra/generated/sots_addresses.h @@ -1,5 +1,5 @@ // GENERATED — do not edit. Facts about Sword of the Stars.exe (GOG 1.8.1). -// Source: sots-re ghidra/addresses.json @ 1e7428d, generated 2026-09-07 by tools/gen_addresses.py +// Source: sots-re ghidra/addresses.json @ ead46e2, generated 2026-09-08 by tools/gen_addresses.py // Runtime address = (uintptr_t)GetModuleHandle(NULL) + RVA (the exe is ASLR-relocated). #pragma once #include @@ -309,7 +309,7 @@ constexpr uint32_t g_SpeciesDefTable = 0x00710a00; constexpr uint32_t g_TechBitmaskTableA = 0x006df378; // data struct { int techId; uint bit; }[29] [verified] constexpr uint32_t g_TechBitmaskTableB = 0x006df478; -// data struct { int techId; int a; float value; }[6] /* values not dumped */ [unverified] +// data struct { int techId; float rebellionOdds; float bonus; }[6] /* rows: CCC_AI/CCC_AIAdmin/CCC_AIFac odds 0.1f bonus 0.5f, CCC_AIFRCON odds 0.2f bonus 0, CCC_AIVrus/CCC_AISlv odds 0 bonus 0 */ [verified] constexpr uint32_t g_AITechValueTable = 0x00617888; // data int (config storage; PTR slot 0x00aedfe0) [verified] constexpr uint32_t g_PERGATETRAFFIC_DRV_TpGate = 0x00723e2c; @@ -359,5 +359,75 @@ constexpr uint32_t ServerPlayer_off_ResearchTarget = 0x00000294; constexpr uint32_t ServerPlayer_off_IncMod = 0x0000030c; // offset std::vector (3 words; entry 0x18 B, {+0x8 int researchPercent, +0xc int researchActive, +0x10 int savings, +0x14 int savingsActive}) [verified] constexpr uint32_t ServerPlayer_off_Aid = 0x00000310; +// offset float SuitTol -- raised by the two adaptation techs; also caps the hazard money cost [verified] +constexpr uint32_t ServerPlayer_off_SuitTol = 0x000000b4; +// offset float MaxOH -- max over-harvest slider, raised by a float32 max() against 0.1f [verified] +constexpr uint32_t ServerPlayer_off_MaxOH = 0x000000b8; +// offset TechTree* the player's own tech tree; its +4 is the MasterTechTree the IsTech chain uses [verified] +constexpr uint32_t ServerPlayer_off_TechTree = 0x000000f4; +// offset bool AIBn -- AI benefit active; gates ApplyAITechBonus and flips the bonus sign [verified] +constexpr uint32_t ServerPlayer_off_AIBn = 0x000000fe; +// offset bool CnTrd -- trade routes allowed (set by CCC_FtlEcon unless RebAI) [verified] +constexpr uint32_t ServerPlayer_off_CnTrd = 0x000000ff; +// offset bool CnRad -- commerce raiding allowed (CCC_ComRaid) [verified] +constexpr uint32_t ServerPlayer_off_CnRad = 0x00000100; +// offset bool CnVItl -- may view other empires' intel (CCC_DatCor) [verified] +constexpr uint32_t ServerPlayer_off_CnVItl = 0x00000101; +// offset bool hgs -- gravitic-syncing drive researched (DRV_GrvSyn); client-synced only [verified] +constexpr uint32_t ServerPlayer_off_hgs = 0x00000102; +// offset bool hadvs -- advanced sensors (CCC_AdvSens) [verified] +constexpr uint32_t ServerPlayer_off_hadvs = 0x00000103; +// offset bool harcc -- arcologies (IND_ArcCon) [verified] +constexpr uint32_t ServerPlayer_off_harcc = 0x00000104; +// offset float pddm -- multiplied by 0.25 by IND_HrdStrct [verified] +constexpr uint32_t ServerPlayer_off_pddm = 0x00000108; +// offset float ConMod[3] -- construction cost per hull class; techs subtract from all three, IND_OrbDry only from [1] and [2] [verified] +constexpr uint32_t ServerPlayer_off_ConMod = 0x0000010c; +// offset float SavMod[3] -- IND_OrbFound subtracts 0.05f from all three [verified] +constexpr uint32_t ServerPlayer_off_SavMod = 0x00000118; +// offset float OutMod -- industrial output multiplier; several techs add, IND_HrdStrct multiplies by 0.9f [verified] +constexpr uint32_t ServerPlayer_off_OutMod = 0x00000124; +// offset float PopMod [verified] +constexpr uint32_t ServerPlayer_off_PopMod = 0x00000130; +// offset float TerraMod [verified] +constexpr uint32_t ServerPlayer_off_TerraMod = 0x00000134; +// offset bool AMine -- asteroid mining (IND_AstMine) [verified] +constexpr uint32_t ServerPlayer_off_AMine = 0x00000138; +// offset float MinRate -- mining rate; IND_MsMine adds 1.0 [verified] +constexpr uint32_t ServerPlayer_off_MinRate = 0x00000140; +// offset int PrGtTrf -- per-gate traffic capacity; the two gate techs raise it with a SIGNED INTEGER max against a config int, not a float compare [verified] +constexpr uint32_t ServerPlayer_off_PrGtTrf = 0x00000148; +// offset float CstR, CstE, CstT at +0x150/+0x154/+0x158 -- set to 10.0f / 2.0f / 1.0f (FLD1) by DRV_FarCast [verified] +constexpr uint32_t ServerPlayer_off_CstR = 0x00000150; +// offset uint32 design-option mask A; +0x194 is mask B. Rewritten wholesale by ComputeTechBitmasks on every completion; not serialised [verified] +constexpr uint32_t ServerPlayer_off_TechMaskA = 0x00000190; +// offset uint32 sticky 'level-1 translation researched for species sp' bit mask, one bit per species, NPC (4) excluded. Second pass of RebuildSpeciesTechFlags; only ever ORed [verified] +constexpr uint32_t ServerPlayer_off_TranslationKnown = 0x000001a4; +// offset uint32 HasVac; +0x28c HasImm. Both get |= the plague-cure mask of the completing tech [verified] +constexpr uint32_t ServerPlayer_off_HasVac = 0x00000288; +// offset uint32 NPTrk -- bit per species whose node-space traffic is visible; set by that species' node-track tech [verified] +constexpr uint32_t ServerPlayer_off_NPTrk = 0x00000290; +// offset int (*)[3] -- pointer to a separately allocated 3-word node-bore parameter block, NULL while no bore drive is researched (the updater allocates and frees it) [verified] +constexpr uint32_t ServerPlayer_off_NodeBore = 0x00000308; +// offset bool cdp -- set once CCC_SpyBm and IND_SlvgTech are both researched; the test is in the completion tail, not in either tech's branch [verified] +constexpr uint32_t ServerPlayer_off_CaptureDesigns = 0x00000330; +// offset uint32 flags[7] xenotech bits per target species, followed by a count word 7 at +0x364 (0x20 bytes assigned as a unit) [verified] +constexpr uint32_t ServerPlayer_off_SpeciesTechFlags = 0x00000348; +// offset bool -- a pending plague-cure roll; run and cleared when the completing tech is the current research target [verified] +constexpr uint32_t ServerPlayer_off_ResearchRollPending = 0x000003b4; +// offset AIRebellion* -- non-null while an AI rebellion object exists; the two AI techs notify it [verified] +constexpr uint32_t ServerPlayer_off_AIRebellion = 0x000003b8; +// offset MasterTechTree* -- the `this` MasterTechTree::IsTech / GetTechDef are called on [verified] +constexpr uint32_t TechTree_off_Master = 0x00000004; +// fastcall void (ServerPlayer* this) -- re-selects the node-bore parameters from the researched set and stores them in the +0x308 block, allocating it on first use and freeing it when no bore drive is researched [verified] +constexpr uint32_t ServerPlayer_UpdateNodeBoreParams = 0x004182c0; +// cdecl bool (int out[3], ServerPlayer* p) -- highest researched bore drive wins: DRV_RAD {95,60,5}, else DRV_REND {65,35,4}, else DRV_RIP {45,15,3}; false (and out left at {INT_MAX,INT_MAX,0}) when none. No species gate [verified] +constexpr uint32_t SelectNodeBoreParams = 0x002e18e0; +// custom int (TechDef* def in EBX) -- index of the species whose SpeciesDef+0x74 node-track tech this def is, or -1 [verified] +constexpr uint32_t ServerPlayer_SpeciesOfTranslationTech = 0x0040e410; +// cdecl struct {int techId; float rebellionOdds; float bonus;}* (TechDef* def) -- linear search of g_AITechValueTable, NULL when the def is none of them [verified] +constexpr uint32_t AITechRow = 0x00290f70; +// data int -- number of rows in g_AITechValueTable (6) [verified] +constexpr uint32_t g_AITechValueCount = 0x006ea2ec; } // namespace sots::addr