29 KiB
Struct recovery via save-field-name xrefs — Sword of the Stars (2006)
Program sots / "Sword of the Stars.exe", ImageBase 0x00400000, 32-bit MSVC. All addresses are VAs.
Method: every on-disk field is tagged with its name string; the name strings live in .rdata; the
functions that reference dozens of a struct's names in sequence are its IStreamable::Read/Write.
Decompiled those, mapped this+offset → name → type. Scripts (on CT111 /root/): SerFind.java
(string→xref→function ranking), SerDump.java/SerDump2.java (decompile with DAT_ → literal
substitution), VtOwner.java/VtOwner2.java (find the owning vftable + RTTI Complete-Object-Locator
offset), SubWrite.java (sub-struct Read/Write by class name). Raw decompiles: CT111 /tmp/serdump/*.c.
Reference cross-checked: save-editor-structs.md (R1 = Bardez editor, R2 = SOTSedit).
0. Serialization runtime facts (needed to read the tables)
IStreamable vftable shape
Every streamable class has a 3-slot vftable { [0] scalar-deleting dtor, [1] Read(Stream&), [2] Write(Stream&) }
(Mars::IStreamable::vftable @ 0x009e22bc = {0x4f7230, 0x924fb0, 0x924fb0}). Mars::StreamableHelper<T> /
Mars::VectorHelper<T> are thin adaptors: slot [1]/[2] call the object's virtual Read/Write (or, for
POD types like Vector3, a free function).
this-adjustment (IMPORTANT for offsets)
The serializer is called through the class's IStreamable sub-vftable, whose RTTI COL offset field
gives the sub-object offset. The vftable slots point straight at the functions (no adjustor thunks;
prologues verified in the exe: 55 8b ec 6a ff 68 …), so inside each function this = object + COL
offset. Absolute member offset = decompiled offset + COL offset. Tables below give both.
| Class | IStreamable vftable (COL offset) | Read | Write | Primary vftable |
|---|---|---|---|---|
Game::ServerSystem (: StarSystem : StarMapNode) |
0x00a2043c (+8) | FUN_0075d4b0 |
FUN_00749630 |
(StarSystem primary 0x00a200e4) |
Game::StarSystem / ClientSystem (StarMapNode part) |
0x00a200d4 / 0x00a20144 (+8) | FUN_00727790 |
FUN_00727820 |
0x00a200e4 / 0x00a20154 |
Game::StarMapNode |
0x00a1e620 (+8) | FUN_00727790 |
FUN_00727820 |
0x00a1e630 |
Game::ServerPlayer (: StrategyPlayer) |
0x00a32794 (+0x3a0 = 928) | FUN_008804d0 |
FUN_008563e0 |
0x00a327a4 (COL 0, 8 slots) |
Game::StarShip |
0x00a31408 (+8) | FUN_00853fa0 |
FUN_008291f0 |
0x00a31418 (2 slots) |
Game::StarFleet (: StarMapNode) |
0x00a1d5f8 (+8) | FUN_00702470 |
FUN_00701070 |
0x00a1d608 |
Game::StrategyServer (whole sim block) |
0x00a26084 (+0) | FUN_007d27a0 |
FUN_0079fa70 |
0x00a26034 (COL 4) |
Game::StarSystem::PlayerView |
0x00a201ac (+0) | FUN_00752af0 |
FUN_007492d0 |
— |
Game::StarSystem::OutputRates (POD, via helper) |
helper 0x00a1f884 | FUN_007472a0 |
FUN_00745190 |
— |
Game::Population |
0x009f90f0 (+0) | FUN_005390c0 |
FUN_00537ef0 |
— |
Game::PopulationGroup |
0x009f8d50 (+0) | FUN_00536a80 |
FUN_00536af0 |
— |
Game::IndependenceInfo |
0x00a2005c (+0) | FUN_00748df0 |
FUN_00748ee0 |
— |
Game::Morale / MoraleEvent |
0x00a1f7c8 / 0x00a2003c | FUN_00744dd0 / FUN_007490b0 |
FUN_00744ea0 / FUN_007491b0 |
— |
Game::ShipBuildOrder(Def) |
0x00a0c160 / 0x00a0ad08 | FUN_00813770 |
FUN_00813800 |
— |
Game::PlayerNotes |
0x00a21948 | FUN_00813250 |
FUN_008132b0 |
— |
Game::SpyReport |
0x00a32b2c | FUN_008843d0 |
FUN_00828ec0 |
— |
Game::PlayerReport (preps) |
0x00a21440 | FUN_008200a0 |
FUN_00817480 |
— |
Game::DiplomacyStats |
0x00a21430 | — | FUN_00818cb0 |
— |
Game::FlightPlan / ::Waypoint / NodeRoute |
0x00a1d50c / 0x00a1d39c / 0x00a1cbdc | FUN_00704c70 / FUN_00701860 / FUN_006e2260 |
FUN_00700f60 / FUN_00700ed0 / FUN_006e22e0 |
— |
Game::PrisonerHold |
0x009fe130 | FUN_0056eb00 |
FUN_0056ec00 |
— |
Game::EventStorage / PlayerAlliances / ShipHealth / PlayerColorID / Mars::Vector3 |
— | — | FUN_00825cc0 / FUN_006d2e10 / FUN_00813e50 / FUN_0053c080 / FUN_008a60d0 |
— |
Stream primitive API (writer side; Stream object vftable, this = stream)
| call | meaning | wrapper used by serializers |
|---|---|---|
vft+0x18 (name, std::string*) |
write string | FUN_008b9d70(stream,name,std::string*) |
vft+0x1c (name, byte) |
write bool | FUN_008b9c20(stream,name,bool*) |
vft+0x20 (name, float) |
write float | FUN_008b9be0(stream,name,float*) |
vft+0x24 (name, int, default=-1) |
write int32 | FUN_008b9d50(stream,name,int*); FUN_008b9d00(stream,name,int16*) (widens short→int) |
vft+0x28 (name, IStreamable-helper*) |
write nested object (BEEFBEEF frame) | inline StreamableHelper<T>{vft, 0, T*} |
vft+0x30 (name, ptr, nbytes) |
write raw bytes | used for 8-byte Int64s |
FUN_00816490(stream,name,obj*) |
write handle id = obj ? obj->id(+4) : 0 |
NetworkObject id at +4 |
FUN_008b9c60(stream,name,int64*) |
write int64 (PopC) | |
Reader side mirrors: FUN_008b9bc0 float, FUN_008b9d20 int, FUN_008b9c00 bool, FUN_008b9d90 string, |
||
FUN_008b9c40 int64, FUN_008b9cd0 short, FUN_008164d0(stream,name) handle→object* lookup, |
||
| stream vft+0x10 int-by-ref (returns found flag), vft+0x14 nested object (NULL helper = skip/legacy). | ||
Readers accept legacy tags (ISuit, Income, HPop, Bats, Builds, Clr, SensMod, ExPopSys, |
||
NShps, SysID, TrdID, Caps, GtTrf, FtSens, FtInc, Pris, NumPlgs, lcid, morev, cme) |
||
| by reading them into scratch/NULL — these are pre-1.8 fields, NOT members. |
Common Mars/MSVC layouts seen: std::string = 0x1c bytes (MSVC10 _Bx union@0, size@0x14, res@0x18;
FUN_008b9d70 does the res>=16 ? heap : sso check); std::vector<T> = {begin@0, end@4, cap@8};
std::map/set node = {left@0, parent@4, right@8, key@0xc, value@0x10, …, color/isnil bytes at tail};
std::list = {head*@0, size@4}. Mars::NetworkObject = {vptr@0, int id@4}.
1. Game::ServerSystem (= live star system + colony record; R1 SimSystemDetailsSaveStruct)
Serializers: Write FUN_00749630 @ 0x00749630 (3453 B), Read FUN_0075d4b0 @ 0x0075d4b0 (8320 B, has
legacy branches). Both begin with StarMapNode::Write/Read (FUN_00727820/FUN_00727790) which
emits Pos. this = obj+8. Base layout: +0 primary vptr (StarSystem 0x00a200e4), +4 NetworkObject id,
+8 IStreamable vptr, +0xc HandleObject vptr, +0x10 owner pointer (*(+0x10)->+0x50 = player-object
table indexed by map key), +0x18 Pos.
| abs off | rel(this+8) | type | save name | notes |
|---|---|---|---|---|
| 0x18 | 0x10 | Mars::Vector3 (3 floats) |
Pos |
via StarMapNode; on disk 3 unnamed floats |
| 0x4c | 0x44 | float | R |
starColor.r |
| 0x50 | 0x48 | float | G |
|
| 0x54 | 0x4c | float | B |
|
| 0x58 | 0x50 | float | A |
|
| 0x5c | 0x54 | int | Idx |
system index |
| 0x60 | 0x58 | int | Size |
1–10 |
| 0x64 | 0x5c | float | Suit |
climate hazard (legacy ISuit discarded) |
| 0x68 | 0x60 | int | Res |
|
| 0x6c | 0x64 | int | ARes2 |
(legacy ARes read then overwritten) |
| 0x70 | 0x68 | int | MRes |
|
| 0x74 | 0x6c | int | TRes |
|
| 0x78..0x7a | 0x70..0x72 | bool[3] | haltv |
written as haltc=3, then 3×(haltt=i, haltv=v[i]) |
| 0x7c | 0x74 | float | OutMod |
|
| 0x80 | 0x78 | int | TAcq |
|
| 0x84 | 0x7c | int | TFAcq |
|
| 0x88..0xa3 | 0x80 | StarSystem::OutputRates (0x1c) |
Rts |
see §1.1; nested object |
| 0xa4 | 0x9c | BuildQueue* |
BQ |
written only if owner (PID) non-null |
| 0xa8..0xc3 | 0xa0 | std::string |
Name |
|
| 0xc4 | 0xbc | bool | Abdn |
bool in memory (R2 "short" is just the value byte; R1 Int32 is the framing) |
| 0xc5 | 0xbd | bool | Dstyd |
|
| 0xc6 | 0xbe | bool | vnh |
gate: if true → vnd,vnex3,vnpex3 |
| 0xc7 | 0xbf | bool | vnd |
|
| 0xc8 | 0xc0 | bool | vnex3 |
|
| 0xc9 | 0xc1 | bool | vnpex3 |
|
| 0xcc | 0xc4 | int | VFlags |
written by value (int) |
| 0xd0 | 0xc8 | int | EFlags |
|
| 0xd4 | 0xcc | int | AFlags |
|
| 0xd8 | 0xd0 | int | FFlags |
|
| 0xdc | 0xd4 | int | GFlags |
|
| 0xe0 | 0xd8 | int | MnRFlags |
|
| 0xe4 | 0xdc | int | RfRFlags |
|
| 0xe8 | 0xe0 | int | ClkFlags |
|
| 0xf0 | 0xe8 | int64 | Bats2 |
raw 8 bytes (R1 correct; R2 Int32 reads low half). Legacy Bats int |
| 0xf8 | 0xf0 | int64 | rcex |
raw 8 bytes |
| 0x100 | 0xf8 | ServerPlayer* |
PID |
owner; written as handle id (->+4) |
| 0x104..0x117 | 0xfc | Population (0x14) |
dcs |
nested |
| 0x118 | 0x110 | float | dsu |
|
| 0x11c..0x13b | 0x114 | Morale (0x20: vptr + int[7]) |
cm |
nested |
| 0x13c..0x147 | 0x134 | vector<MoraleEvent> |
cme2 |
VectorHelper |
| 0x14c..0x16b | 0x144 | Morale |
PvCM |
|
| 0x16c..0x177 | 0x164 | vector<StarFleet*> |
NumFlts + n×Flt |
ids via handle |
| 0x17c | 0x174 | float | RepCur |
|
| 0x180 | 0x178 | float | RepMax |
|
| 0x184 | 0x17c | int | EggScio |
|
| 0x188 | 0x180 | bool | NoRebAI |
|
| 0x189 | 0x181 | bool | PvNoRebAI |
|
| 0x18c | 0x184 | int | Pop |
imperial pop |
| 0x190 | 0x188 | float | Infra |
|
| 0x194 | 0x18c | int | pbon |
|
| 0x198 | 0x190 | float | ibon |
|
| 0x19c | 0x194 | int | TerrFl |
|
| 0x1a0..0x1b3 | 0x198 | Population |
Pop2 |
civilian pop groups (R1 popG) |
| 0x1b4..0x1c7 | 0x1ac | Population |
pbon2 |
|
| 0x1c8 | 0x1c0 | IndependenceInfo* |
hindi + indi |
hindi = ptr!=NULL |
| 0x1cc..0x1d7 | 0x1c4 | vector<int> |
spies2 |
VectorHelper |
| 0x1dc | 0x1d4 | int | rbfl |
written by value |
| 0x1e0 | 0x1d8 | bool | hsrg |
|
| 0x1e4..0x1ff | 0x1dc | int[7] | nadct,(ads=i,adt=v) |
addiction table: count of non-zero entries then sparse (index,value) pairs |
| 0x200 | 0x1f8 | int | PvPop |
previous-turn snapshot block |
| 0x204 | 0x1fc | float | PvInfra |
|
| 0x208 | 0x200 | float | PvSuit |
|
| 0x20c | 0x204 | int | PvRes |
|
| 0x210 | 0x208 | int | PvARes2 |
|
| 0x214 | 0x20c | int | PvMRes |
|
| 0x218..0x22b | 0x210 | Population |
PvPop2 |
|
| 0x238 | 0x230 | StarFleet* |
DefF |
handle id |
| 0x23c | 0x234 | StarFleet* |
DefSF |
handle id |
| 0x240..0x24b | 0x238 | vector<obj*> |
NumGFs + n×GF |
gates |
| 0x250..0x25b | 0x248 | vector<obj*> |
NumSnF + n×SnF |
stations |
| 0x260..0x26b | 0x258 | vector<obj*> |
NumMnF + n×MnF |
monitors |
| 0x274 / 0x278 | 0x26c / 0x270 | std::map head / size |
NVO + entries |
colonies, see §1.2 |
| 0x284 / 0x288 | 0x27c / 0x280 | std::map head / size |
NVE + entries |
§1.2 |
| 0x294 / 0x298 | 0x28c / 0x290 | std::map head / size |
NVs + entries |
per-player pview, §1.3 |
| 0x2a8..0x2b3 | 0x2a0 | vector<Plague*> |
NumPlgs2 + n×(PlgT=plg->+4, Plg obj) |
|
| 0x2b8 | 0x2b0 | int | TnsOH |
|
| 0x2bc | 0x2b4 | int | TDst |
|
| 0x2c4 | 0x2bc | int | ntdev |
|
| 0x2c8 | 0x2c0 | int | ltis |
int (R2 "short" wrong width) |
| 0x2cc | 0x2c4 | int | rbtn |
|
| 0x2d0 | 0x2c8 | int | rbfr |
by value |
| 0x2d4 | 0x2cc | int | rbwn |
Object size ≥ 0x2d8. On-disk order = R1 §8 exactly (Pos, RGBA, Idx, Size, Suit, Res, ARes2, MRes, NoRebAI, TRes, Pop, Pop2, Infra, PvPop, PvPop2, PvInfra, PvSuit, PvRes, PvARes2, PvMRes, PvNoRebAI, Rts, Abdn, Dstyd, TnsOH, OutMod, RepCur, RepMax, ntdev, pbon, pbon2, ibon, ltis, rbfl, rbtn, rbfr, rbwn, hsrg, halt*, vn*, Name, *Flags, Bats2, rcex, Mn/Rf/ClkFlags, EggScio, TerrFl, TAcq, TFAcq, TDst, dcs, dsu, cm, PvCM, cme2, spies2, PID, DefF, DefSF, BQ, nadct/ads/adt, NumPlgs2…, NumFlts/GFs/SnF/MnF, NVO, NVE, NVs, hindi/indi).
Members that are only ever read with a NULL/scratch target (not stored): ISuit, Income, HPop, Builds
(+Con,Sav,ConLeft,OrID,DesID — old inline build queue), Slvs, dct, cme, Bats, NumPlgs.
1.1 Game::StarSystem::OutputRates (POD, 0x1c) — Write FUN_00745190
Memory order ≠ disk order: +0x00 float SRt, +0x04 SRsc, +0x08 SRtf, +0x0c SRi, +0x10 SRoh,
+0x14 SRs, +0x18 int SRnr. Disk order: SRs, SRt, SRsc, SRtf, SRi, SRoh, SRnr. Reader: if SRs tag is
absent, reads 5 unnamed floats (legacy).
1.2 Colony maps NVO / NVE (std::map keyed by player-table index)
Write emits NVO=size, then per node: PID = handle id of owner->+0x50[key] (player object table),
TShn = int16 at node+0x12 (value+2), OID = int at node+0x14 (value+4) by value, isind bool at
node+0x18 (value+8), indi = inline IndependenceInfo at node+0x1c (value+0xc, 0x70 bytes). Node isnil
byte at +0x8d ⇒ value size 0x7c. NVE nodes: EPid handle (key→player), ETS int16 @ node+0x12, Eid
int @ node+0x14 (isnil @ +0x19 ⇒ value 8 bytes).
OID is a stored int, distinct from PID; the R2 claim "OID = PID×16" is an id-allocation pattern, not
a derivation in this code (open question — check the HandleObject id allocator).
1.3 Game::StarSystem::PlayerView (per-player seen snapshot) — Write FUN_007492d0, Read FUN_00752af0
Stored inline as map value at node+0x10 (NVs; node isnil @ +0xad ⇒ value ≈ 0x9c).
+0 vptr (0x00a201ac), +8 int VTrn, +0xc int Pop, +0x10 Population Pop2 (0x14), +0x24 float Infra,
+0x28 float Suit, +0x2c int Res, +0x30 int ARes2, +0x34 int MRes, +0x38 bool NoRebAI, +0x3c int pbon,
+0x40 Population pbon2, +0x54 float ibon, +0x58 int TerrFl; trailer bool footer=1. Reader also accepts
legacy ARes, PvPop/PvInfra/PvSuit/PvRes/PvARes/PvARes2/PvMRes/PvNoRebAI into the same slots.
(R1 lists Int32 infra — it is a float.)
1.4 Game::Population (0x14) / Game::PopulationGroup (0x18 stride)
Population: +0 vptr, +4/+8/+0xc vector<PopulationGroup>; Write emits PopNG = count of groups with
PopC>0 (or ≥0 with low word ≠0), then each as nested PopG. PopulationGroup: +4 int PopT, +8 int PopS,
+0x10 int64 PopC (R1 popT,popS,popC ✓).
1.5 Game::IndependenceInfo (0x70) — Write FUN_00748ee0
+4 int indsp, +8 PlayerColorID indcl (nested), +0x1c string indnm, +0x38 string indav, +0x54 string indba.
1.6 Game::Morale / Game::MoraleEvent
Morale (0x20): +0 vptr, +4 int[7]; disk: mnsp=n then n×(msp=index, mv=value) (reader tolerates
missing mnsp → 7 fixed entries, skipping index 4). MoraleEvent: +4 mid, +8 mtr, +0xc mn, +0x10 mtp
(ints), +0x14 Morale mfx, +0x34 string mdsc.
1.7 Game::ShipBuildOrder (build-queue entry) — Write FUN_00813800
+4 int desID, +8 int con, +0xc int sav, +0x10 int conleft, +0x14 int ordID; disk order desID, con,
conleft, sav, ordID (R1 ✓).
Where is "Planet"?
Game::Planet : Actor (vft 0x009ef144) is a render/scene actor and is not streamed. The colony/planet
state the save calls "planet" (R2's Idx/Name/Size/Suit/Res/Infra/Pop/OID…) is entirely in ServerSystem
above plus PlayerView. The CreateParameters PlanetSaveStruct (x,y,z + 4 ints) is map-gen input
(StarMapParams), not touched here.
2. Game::ServerPlayer (empire; R1 SimPlayerDetailsSaveStruct)
Serializers: Write FUN_008563e0 @ 0x008563e0 (4040 B), Read FUN_008804d0 @ 0x008804d0 (7647 B).
IStreamable sub-object at +0x3a0 (COL offset 928); this = obj+0x3a0, so decompiled offsets are
negative for most members. Primary vptr @+0 (0x00a327a4, StrategyPlayer shape), NetworkObject id @+4.
| abs off | rel(this+0x3a0) | type | save name | notes |
|---|---|---|---|---|
| 0x28 | -0x378 | int | PlyrIdx |
|
| 0x2c | -0x374 | ServerSystem* |
HomeSys |
handle id |
| 0x30/0x34 | -0x370/-0x36c | vector<ServerPlayer*> |
NumOwn + n×OwnId |
handle ids (R1 ownerIds) |
| 0x40..0x5b | -0x360 | std::string |
PlryName |
|
| 0x5c | -0x344 | int | Species |
0 Human … 6 Morrigi |
| 0x60 | -0x340 | PlayerColorID (4 B) |
ClrID |
nested; §2.1 (legacy Clr int skipped) |
| 0x74..0x8f | -0x32c | std::string |
Bdg |
badge |
| 0x90..0xab | -0x310 | std::string |
Avt |
avatar |
| 0xac | -0x2f4 | int | Team |
|
| 0xb0 | -0x2f0 | float | IdealSuit |
|
| 0xb4 | -0x2ec | float | SuitTol |
|
| 0xb8 | -0x2e8 | float | MaxOH |
|
| 0xbc | -0x2e4 | float | ResRate |
|
| 0xc0 | -0x2e0 | float | ResMod |
|
| 0xc4 | -0x2dc | float | ResScl |
|
| 0xd0 | -0x2d0 | float | TRM |
R1 says Int32 — it is float |
| 0xd4 | -0x2cc | int | TRA |
note memory order TRA before TRP |
| 0xd8 | -0x2c8 | int | TRP |
|
| 0xe4/0xe8 | -0x2bc/-0x2b8 | vector<ShipDesign*> |
NumDes + n×(DesID=d->+0xa4, Des obj) |
current designs |
| 0xf4 | -0x2ac | TechTree* |
TechTree |
first field on disk |
| 0xf8 | -0x2a8 | bool | Elim |
|
| 0xfb | -0x2a5 | bool | NPC |
|
| 0xfc | -0x2a4 | bool | RebAI |
|
| 0xfd | -0x2a3 | bool | ReqCL |
|
| 0xfe | -0x2a2 | bool | AIBn |
|
| 0xff | -0x2a1 | bool | CnTrd |
|
| 0x100 | -0x2a0 | bool | CnRad |
|
| 0x101 | -0x29f | bool | CnVItl |
|
| 0x102 | -0x29e | bool | hgs |
|
| 0x103 | -0x29d | bool | hadvs |
|
| 0x104 | -0x29c | bool | harcc |
|
| 0x108 | -0x298 | float | pddm |
|
| 0x10c..0x117 | -0x294 | float[3] | ConMod ×3 |
interleaved on disk as 3×(ConMod[i], SavMod[i]) |
| 0x118..0x123 | -0x288 | float[3] | SavMod ×3 |
|
| 0x124 | -0x27c | float | OutMod |
|
| 0x128 | -0x278 | float | RebOutMod |
|
| 0x12c | -0x274 | float | ScOutMod |
|
| 0x130 | -0x270 | float | PopMod |
(legacy SensMod float, ExPopSys int skipped between IncMod/PopMod/TerraMod) |
| 0x134 | -0x26c | float | TerraMod |
|
| 0x138 | -0x268 | bool | AMine |
|
| 0x13c | -0x264 | float | MinPure |
|
| 0x140 | -0x260 | float | MinRate |
|
| 0x144 | -0x25c | int | NGts |
|
| 0x148 | -0x258 | int | PrGtTrf |
|
| 0x14c | -0x254 | int | GTraf |
|
| 0x150 | -0x250 | float | CstR |
R1 Int32 → float |
| 0x154 | -0x24c | float | CstE |
|
| 0x158 | -0x248 | float | CstT |
|
| 0x15c | -0x244 | int | Maint |
(legacy NShps skipped) |
| 0x160 | -0x240 | float | shrm |
|
| 0x164 | -0x23c | int | Status |
by value |
| 0x168..0x177 | -0x238 | PlayerAlliances {int ALid, AL, NA, CF} |
Team (2nd) |
nested (R1 teamStruct) |
| 0x178/0x17c | -0x228/-0x224 | vector<ShipDesign*> |
NumLeg + n×(DesID,Des) |
legacy/drone designs (R1 droneDesigns) |
| 0x188 | -0x218 | int | PvSav |
|
| 0x18c | -0x214 | bool | PvMA |
|
| 0x19c | -0x204 | int | HasDisc |
by value |
| 0x1a0 | -0x200 | int | HasDiscSp |
|
| 0x1a4 | -0x1fc | int | HasDiscCl |
|
| 0x1a8 | -0x1f8 | int | HasEnc |
|
| 0x1ac | -0x1f4 | int | HasEng |
|
| 0x1b0 | -0x1f0 | ShipRecords (inline) |
ShipRecs |
Write FUN_008176a0 |
| 0x1f4 | -0x1ac | vector<Objective> |
Ojvs |
VectorHelper |
| 0x204/0x208 | -0x19c/-0x198 | vector<{int xid,xmin,xmax; float xper}> (16 B) |
Nexp + n×(xid,xmin,xmax,xper) |
R1 misses the per-entry body |
| 0x214/0x218 | -0x18c/-0x188 | vector<int> |
NWeapXcl + n×WeapXcl |
|
| 0x230 | -0x170 | vector<DiplomacyStats> |
dipstats |
§2.2 |
| 0x240 | -0x160 | CommMessageContainer* |
comms |
|
| 0x244 | -0x15c | vector<PlayerReport> |
preps |
§2.3 |
| 0x254 | -0x14c | vector<ObservedDesign> |
odes |
|
| 0x264 | -0x13c | vector<ObservedWeapon> |
owep |
|
| 0x274 | -0x12c | vector<ObservedTech> |
otch |
|
| 0x284 | -0x11c | int | Sav |
savings |
| 0x288 | -0x118 | int | HasImm |
by value |
| 0x28c | -0x114 | int | HasVac |
|
| 0x290 | -0x110 | int | NPTrk |
|
| 0x294 | -0x10c | Tech* (current research) |
ResTNm |
writes tech ? tech->name(+4) : "" |
| 0x298 | -0x108 | FleetNameGenerator* |
FNG |
|
| 0x29c | -0x104 | EventStorage (inline) |
Events |
{EvNxID@+0x14, Events vector@+4} |
| 0x2b8/0x2bc | -0xe8/-0xe4 | std::list<PlayerNotes> head/size |
NumNotes + n×Nts |
node value at +0x10: NtSys@+4,NtTxt str@+8,NtTrn@+0x24 |
| 0x2c4 | -0xdc | int | BnkWrn |
by value |
| 0x2c8 | -0xd8 | int | BnkTrn |
|
| 0x2cc | -0xd4 | int | BnkEl |
|
| 0x2d0 | -0xd0 | int | BnkPr |
|
| 0x2d8 | -0xc8 | int | plcy |
by value |
| 0x2dc..0x2f7 | -0xc4 | std::string |
pswd |
R1 says Int32 — it is a string |
| 0x2f8 | -0xa8 | bool | Srn |
|
| 0x2fc | -0xa4 | obj* |
SrnTo |
handle id (R1 srcTo) |
| 0x300 | -0xa0 | int | lboid |
|
| 0x304 | -0x9c | int | lcid2 |
by value (legacy lcid) |
| 0x30c | -0x94 | float | IncMod |
|
| 0x310 | -0x90 | vector<PlayerAid> |
aid |
|
| 0x320/0x324 | -0x80/-0x7c | vector<DefenceLayout*> |
ndeflay + n×deflay |
|
| 0x330 | -0x70 | bool | cdp |
|
| 0x334 | -0x6c | SpyReport* |
spy2 |
§2.4 |
| 0x338/0x33c | -0x68/-0x64 | vector<RaidTargets> (0x20 stride) |
rdtc + n×rdt |
|
| 0x368 | -0x38 | int | aidf |
by value |
| 0x370 | -0x30 | CivilianRatios (inline) |
civr |
Write FUN_0082c740 |
| 0x39c | -4 | int | tnc |
written as max(v,1) |
| 0x3a0 | 0 | vptr | — | IStreamable sub-vftable 0x00a32794 |
| 0x3a4/0x3a8 | +4/+8 | vector<{float PRm; int PRBt}> |
NumPR + n×(PRm,PRBt) |
|
| 0x3b4 | +0x14 | bool | ResErrRoll |
|
| 0x3b5 | +0x15 | bool | cta |
|
| 0x3b8 | +0x18 | AIRebellion* |
HasAIR + AIR |
gate = ptr!=NULL |
| 0x3bc | +0x1c | AIEncounterFlags* |
AIEnf |
|
| 0x3c0/0x3c4 | +0x20/+0x24 | vector<SpecialProjectImpl*> |
NSprj + n×(SprjT=p->+0x3c, Sprj) |
|
| 0x3d0 | +0x30 | int | NextPrjID |
|
| 0x3d4 | +0x34 | int | lret |
|
| 0x3dc | +0x3c | int | nmeid |
Object size ≥ 0x3e0. Disk order = R1 §6 (TechTree, HomeSys, PlyrIdx, PlryName, Species, ClrID, Bdg, Avt,
Team, Sav, IdealSuit, SuitTol, MaxOH, ResRate, ResMod, ResScl, TRM, TRP, TRA, OutMod, RebOutMod, ScOutMod,
IncMod, PopMod, TerraMod, AMine, MinPure, MinRate, NGts, PrGtTrf, GTraf, CstR/E/T, Maint, shrm, Status, Elim,
NPC, RebAI, ReqCL, Team{ALid,AL,NA,CF}, HasVac, HasImm, NPTrk, HasDisc, HasDiscSp, HasDiscCl, HasEnc, HasEng,
Events, FNG, PvSav, PvMA, AIBn, CnTrd, CnRad, hgs, hadvs, harcc, CnVItl, pddm, BnkWrn/Trn/Pr/El, ShipRecs,
NextPrjID, plcy, pswd, lret, nmeid, cdp, spy2, civr, aidf, Srn, SrnTo, lboid, lcid2, ResTNm, ResErrRoll,
3×(ConMod,SavMod), NumOwn/OwnId, NumDes/DesID/Des, NumLeg/DesID/Des, NumNotes/Nts, NumPR/PRm/PRBt, HasAIR/AIR,
cta, AIEnf, NSprj/SprjT/Sprj, Nexp/xid/xmin/xmax/xper, NWeapXcl/WeapXcl, Ojvs, dipstats, comms, preps, odes,
owep, otch, aid, ndeflay/deflay, rdtc/rdt, tnc). Note: on-disk Sav comes right after Team though it
lives at 0x284 in memory.
2.1 Game::PlayerColorID (4 bytes) — Write FUN_0053c080
+0 int8 index; +1,+2,+3 uint8 r,g,b; writer emits index via FUN_008b9cb0 (char→int on disk), and
iff index == -1 the three r,g,b bytes (matches R1 §1.6). Same struct used by IndependenceInfo.indcl.
2.2 Game::DiplomacyStats (0x24) — Write FUN_00818cb0
+4 int other; then int16 fields at +8 lastnap, +0xa lastnapbty, +0xc bknnap, +0xe btynap,
+0x10 lastally, +0x12 lastallybty, +0x14 bknally, +0x16 btyally, +0x18 lastcf, +0x1a lastcfbty,
+0x1c bkncf, +0x1e btycf, +0x20 deadhome (all widened to int32 on disk; R1 nap/ally/cf{last_,last_bty,bkn_,bty_} ✓).
2.3 Game::PlayerReport (preps, 0x30) — Write FUN_00817480
ints +4 oid, +8 pid, +0xc flds, +0x10 sav, +0x14 home, +0x18 ncol, +0x1c mpwr, +0x20 mcls, +0x24 mmsl, +0x28 nshp, +0x2c nsat (R1 ✓).
2.4 Game::SpyReport — Write FUN_00828ec0
Four std::lists: +4 list<SpyReportDefences> (count defc2@+8, items def), +0x10 list<SpyReportTrade>
(rtc@+0x14, strd), +0x1c list<SpyReportEvents> (evc@+0x20, evs), +0x28 list<SpyReportTechTree>
(ttc@+0x2c, tt). R1 only kept the four counts.
2.5 Game::TechTree — Write FUN_005890a0 (tags NumTechs, TNm, NumBrs; per-tech body in a
sub-writer not decompiled here; logs "TechTree: Tech %d not found saving tech tree").
3. Game::StarFleet (R1 SimFleetDetails) — Write FUN_00701070, Read FUN_00702470; this = obj+8
| abs | rel | type | save name | notes |
|---|---|---|---|---|
| 0x18 | 0x10 | Vector3 | Pos |
via StarMapNode |
| 0x4c | 0x44 | Vector3 | PrvPos |
|
| 0x58 | 0x50 | ServerPlayer* |
PID |
handle |
| 0x5c..0x77 | 0x54 | string | FtName |
|
| 0x78 | 0x70 | bool | Perm |
|
| 0x7c | 0x74 | FleetLayout (inline, ~0x24) |
HLay + Lay |
gate = either of its two vectors (+4/+8, +0x14/+0x18) non-empty |
| 0xa0 | 0x98 | StarSystem* |
LocID |
handle (legacy SysID,TrdID ints skipped) |
| 0xa4/0xa8 | 0x9c/0xa0 | vector<StarShip*> |
NShips + n×(ShipID handle, Ship obj) |
|
| 0xc4..0xfb | 0xbc | FlightPlan (inline, 0x38) |
HFPlan + FPlan |
gate = wpts non-empty; §3.1 |
| 0xfc | 0xf4 | int | FtTrans |
by value (legacy Caps int, GtTrf short, FtSens float, FtInc int skipped) |
| 0x100 | 0xf8 | Vector3 | FtOrig |
(R1 "3 ints" → 3 floats) |
| 0x10c | 0x104 | int | FtFlg |
|
| 0x110 | 0x108 | int | Ftae |
|
| 0x114 | 0x10c | int | Ftpae |
|
| 0x118 | 0x110 | int | FtEnc |
|
| 0x11c | 0x114 | int | FtMS |
3.1 Game::FlightPlan (0x38) / Waypoint / NodeRoute
FlightPlan: +0 vptr, +4 vector<Waypoint> wpts, +0x14 float FPsp2, +0x18 int FPeta2, +0x1c Vector3 FPogn2,
+0x28 Vector3 FPdpos, +0x34 int pnd. Waypoint: +4 int Wpt, +8 int Tp, +0xc NodeRoute nrt.
NodeRoute: +4 nrp, +8 nrf, +0xc nrt (ints). Reader also handles legacy NumWpt/path/FPognid.
4. Game::StarShip (R1 SimFleetShipDetails) — Write FUN_008291f0, Read FUN_00853fa0; this = obj+8
| abs | rel | type | save name | notes |
|---|---|---|---|---|
| 0x10 | 0x8 | ServerPlayer* |
PlrID |
handle |
| 0x14 | 0xc | ShipDesign* |
DesID |
writes design->+0xa4 (design id) |
| 0x20 | 0x18 | float | Range |
|
| 0x24..0x33 | 0x1c | ShipHealth {vptr; float[3]} |
Health |
3 unnamed floats (R1 guess command/mission/drive) |
| 0x34 | 0x2c | int | MineCap |
|
| 0x38/0x3c | 0x30/0x34 | vector<{float th, thm}> |
NTH + n×(TH,THM) |
|
| 0x48 | 0x40 | int | Plg |
|
| 0x4c | 0x44 | int | Act |
|
| 0x50 | 0x48 | bool | Dep |
|
| 0x51 | 0x49 | bool | Atq |
|
| 0x5c | 0x54 | int | LCT |
|
| 0x60 | 0x58 | int | tsd |
|
| 0x64 | 0x5c | StarFleet* |
FltID |
handle |
| 0x68 | 0x60 | int | ConCap |
|
| 0x6c | 0x64 | float | RefCap |
R1 Int32 → float |
| 0x70 | 0x68 | float | RepCap |
R1 Int32 → float |
| 0x7c | 0x74 | int | EncID |
|
| 0x80 | 0x78 | PrisonerHold (inline) |
PrisH |
+0x14 int* → [0]=PrMax, [2..8] per-species counts; disk PrMax,PrNSp,(PrSp=idx,PrNum) |
| 0x98 | 0x90 | BuildQueue* |
hbq + BQ2 |
gate = ptr!=NULL |
| 0x9c | 0x94 | Population* |
hsp + pop |
gate = ptr!=NULL |
| 0xa0 | 0x98 | Population* |
ppop |
|
| 0xa8 | 0xa0 | int | atsp |
by value |
| 0xac | 0xa4 | int | tblt |
5. Top-level sim block — Game::StrategyServer Write FUN_0079fa70 / Read FUN_007d27a0
Tag order confirms R1 §5: KeyPath, NMSz, NMLc, NMnx, ModCount, Frame, GameID, [AIDifficultyID legacy], Attrib, RNG, GameName, Map, IncMod, ResMod, EnAl, EnTm, GOTurn, GOWinPly, NPCm/o/i/v/a, szadj, rsadj, suadj, sprjs, RandEncAdj, cmbtid, turnstats, numcreps/crep, ninv/invs/inve/invt/invtb, AllExc×6, AllExcCF, AllExcCFp×2, NumPlrs/PlayerID/Player, ISsp, ISsu, NumSys/SysID/Sys, NdGr2, trdmgr, spymgr, NumFlts/FltID/Flt, NumActs/Act, SvSctOb, zdsc, zdsi, zdst. (Offsets not tabulated — out of scope this round.)
6. Corrections to the community reference (R1/R2)
- Types:
TRM,CstR/E/T,shrm, shipRefCap/RepCap, PlayerViewInfraare floats;pswdis a string;PvMA,AIBnare bools;FtOrigis a Vector3 (3 floats). - Widths:
Bats2/rcexare true int64 (R2 int is wrong);Abdn/Dstydare bools,ltisan int (R2 "short" is an artefact of reading value bytes);TShn,ETS, all DiplomacyStats counters are int16 in memory but int32 on disk. - Missing in R1:
Nexpentries carry (xid,xmin,xmax,xper); SpyReport lists have bodies; Morale is a sparse (msp,mv) table;nadctis followed by sparse (ads,adt) pairs over a 7-int table. Plg/Act/EncID/OID/flags are written by value → plain ints (not handles).
7. Confidence & open questions
- High: all offsets/types in §1–§4 (direct from Write functions; Read functions agree on every member address; COL offsets from RTTI; no adjustor thunks).
- Medium: nested struct sizes inferred from neighbouring offsets (Population 0x14, Morale 0x20,
FlightPlan 0x38, PlayerView ≈0x9c, IndependenceInfo 0x70); which members belong to
StarSystemvsServerSystem(serializer isServerSystem's;ClientSystemshares the StarSystem vftables and only streamsPos). - Open:
FUN_008b9cb0exact on-disk width forPlayerColorID(R1 says int32 — plausible);ServerSystem+0x10owner type (StrategyServer? its+0x50is a player-object table);OIDallocation (R2's ×16); per-tech body ofTechTree::Write;CdPlayerblock not attempted (names are R1'sunknownN, nothing to xref). No types were written back into Ghidra (notes only).