552 lines
31 KiB
Markdown
552 lines
31 KiB
Markdown
# Sword of the Stars 1 (SOTS1) — Save-File Struct Reference (Community RE)
|
||
|
||
Harvested from two community save-game editors for cross-checking against binary-recovered
|
||
serializable types. SOTS1 uses a self-describing "Streamable" serialization system: **field
|
||
order + type matter**, and most values are preceded by their own name string.
|
||
|
||
Target game version: **SOTS1 v1.8+** (both editors target the 1.8.x line; repo2 says "1.80 +19").
|
||
|
||
> **Tag-name note (2026-09-07):** this document preserves the community editors' (R1) field spellings. The real on-disk tags, byte-confirmed on our saves, differ in a few places: `otnF` (not `ontF`) in Odes/Owep/Otch, `nextid` (not `nextId`) in NdGr2, Design = `FAIDes/DHide/DWep/DName`, and `ords`/`wpts`/`paths` are real tags. See `verify/save-reader/SAVE_FORMAT.md` §10.
|
||
|
||
## Sources
|
||
- **[R1] BardezAnAvatar/Sots.Sots1.SavedGameEditor** — C#. A *complete, ordered, typed*
|
||
Streamable parser. Migrated from SourceForge `sots-sge`. This is the authoritative layout
|
||
evidence (read/write methods reproduce exact on-disk order). Key files under
|
||
`Bardez.Project.SwordOfTheStars.DataStructures/`:
|
||
- `BaseSaveStructures.cs` (primitives + framing), `SharedSaveStructures.cs` (coords/colors),
|
||
`SaveGameDataStructure.cs` (top level), `SummarySaveStructures.cs`,
|
||
`CreateParametersSaveStructures.cs`, `SimulationSaveStructures.cs` (11,974 lines — the bulk),
|
||
`CdTableSaveStructures.cs` (combat/AI table). IO: `Bardez.Project.SwordOfTheStars.IO/{Gzip,SaveFileIO}.cs`.
|
||
- **[R2] ghbplayer/SOTSedit** — C#/WPF. A *name-tag scanner* (does NOT model layout; it searches
|
||
the decompressed blob for length-prefixed field-name strings and reads the value that follows).
|
||
Value comes from `Parse.cs` + the field catalog `SOTSEdit.cfg` (friendly-name ⇄ serialized-name
|
||
⇄ type mapping) and race/semantic hacks. Written 2011 "to learn C#"; author calls the parser weak.
|
||
|
||
Both editors independently confirm the same primitives, framing markers, race IDs, and color IDs.
|
||
|
||
---
|
||
|
||
## 1. File format & serialization mechanics
|
||
|
||
### 1.1 Container
|
||
- **Whole `.sav` file = gzip stream.** Decompress first (`GZipStream`, standard gzip). [R1 Gzip.cs, R2 gzip.cs]
|
||
- Editors work on the **decompressed** byte stream (R1 names its test artifacts `*.sav.inflate.dat`).
|
||
- No separate magic/version header is decoded by either editor beyond the top-level Summary block;
|
||
version is implied by scenario/field presence, not a numeric version field. Endianness: **little-endian**
|
||
throughout (`BitConverter` on x86).
|
||
|
||
### 1.2 Decompressed top-level order [R1 SaveGameData.ReadFromStream]
|
||
```
|
||
SaveGameData:
|
||
1. SummarySaveStruct summary
|
||
2. CreateParametersSaveStruct createParams
|
||
3. SimSaveStruct sim <-- the giant one (players/systems/fleets/etc.)
|
||
4. CdTable cdTable <-- combat / AI state table
|
||
```
|
||
|
||
### 1.3 Primitive encodings [R1 BaseSaveStructures.cs]
|
||
Text encoding is **windows-1252** (R1) / ASCII (R2).
|
||
|
||
- **StringStruct** (raw string): `Int32 length` + `length` bytes (no NUL terminator counted).
|
||
R2 calls this a "BStr": 4-byte little-endian length prefix + ASCII bytes.
|
||
- **Padding rule (critical):** every *basic* value struct is **NUL-padded to a 4-byte boundary**.
|
||
`PaddingSize = 4`. Padding is computed over `(sizeof(Int32 desc-length) + description.Length + valueBytes)`.
|
||
- **Named value fields** (`BasicSaveStruct` subclasses) are each laid out as:
|
||
`StringStruct description` (a field-name tag, often the non-descriptive `"."`) → then the value →
|
||
then NUL padding to 4 bytes. Concrete leaf types:
|
||
| Struct | Payload after description tag |
|
||
|---|---|
|
||
| `Int32SaveStruct` | 4-byte Int32 |
|
||
| `Int64SaveStruct` | 8-byte Int64 |
|
||
| `FloatSaveStruct` | 4-byte IEEE Single |
|
||
| `BooleanSaveStruct` | 1 byte (0/1), padded to 4 |
|
||
| `StringSaveStruct` | nested StringStruct (len+bytes) |
|
||
| `ByteArraySaveStruct` | raw bytes (length externally known) |
|
||
|
||
So the on-disk shape of a named scalar is: `[len][name-ascii][pad] [value] [pad]`. R2 exploits exactly
|
||
this: it locates a field by searching for `[len][name]` and reads the value immediately after.
|
||
|
||
### 1.4 Complex-struct framing (the "BEEFBEEF" envelope) [R1 ComplexSaveStruct]
|
||
Every **complex** structure is framed:
|
||
```
|
||
StringStruct description (NUL-padded to 4)
|
||
UInt32 0xBEEFBEEF (begin marker)
|
||
... body (ordered child fields) ...
|
||
UInt32 0x41104110 (end marker = bitwise NOT of 0xBEEFBEEF)
|
||
```
|
||
`0xBEEFBEEF` / `~0xBEEFBEEF (0x41104110)` bracket every complex object — a reliable resync/validation
|
||
signature when scanning the binary. (`ISotsStructure` leaf types are NOT framed; only `ComplexSaveStruct`.)
|
||
|
||
### 1.5 Array conventions [R1 BaseSaveStructures.cs]
|
||
- **ComplexArraySaveStruct<T>**: framed (has description + BEEFBEEF), body = `Int32SaveStruct count`
|
||
then `count` × T.
|
||
- **NonComplexArraySaveStruct<T>**: NOT framed; body = `Int32SaveStruct count` then `count` × T.
|
||
(Distinguishing which arrays are framed vs. not is itself layout evidence — see per-struct notes.)
|
||
|
||
### 1.6 Conditional & polymorphic reads (watch for these in the binary)
|
||
- **Optional-by-flag:** a boolean/int gate precedes an optional sub-object.
|
||
- `SimPlayerColorSaveStruct`: `Int32 colorIndex`; **iff `colorIndex == -1`**, an `RgbColorInt32`
|
||
(custom RGB) follows. Otherwise palette index only.
|
||
- `SimPlayerDesignDw2SaveStruct` (weapon slot): `Boolean bId`; if true → `Int32 wId`, else →
|
||
`StringSaveStruct wfn` (weapon full resource path); then `Int32 dId`.
|
||
- `SimFleetShipDetails`: `Boolean hbq` gates `bq`; `Boolean hsp` gates `sp`. Fleet flight-plan/lay
|
||
gated by `hfPlan` / `hLay` booleans.
|
||
- `SimSystemDetailNvo.isInd` gates independent-colony sub-block; `SimSystemDetailsIndi.hindi`,
|
||
`SimSystemDetailsVonNeumann.vnh` similar boolean gates.
|
||
- **Polymorphism by string tag:** `SimSvSctObXscn` reads `StringSaveStruct xcsn`, then switches:
|
||
`"crowdefs"`, `"gmtrigger"`, `"traps"`, `"indsys"`/default → different body subclass.
|
||
- **Polymorphism by fixed position:** `SimScSctObEncObjArray` (grand-menace/encounter objects) reads a
|
||
count then dispatches subclass **by index 0..8** in fixed order:
|
||
`0 Infest, 1 Dsn, 2 AsteroidMonitor, 3 TD, 4 WD, 5 Hives, 6 Rsuc, 7 Dfts, 8 Ini2`.
|
||
|
||
### 1.7 Write-time quirks worth knowing (R2)
|
||
- R2 edits in place and cannot safely change string length (it truncates/space-pads to the original
|
||
length). R1 rewrites the whole stream and re-pads. If the binary stores string lengths, the game
|
||
reads them dynamically (R1 proves round-trip works when re-padded).
|
||
- R2 planet OID/PID hack: on-disk **`OID = PID * 16`** (R2 divides by 16 to show a "PlayerID").
|
||
i.e. the raw owner id field is the player index shifted left 4 bits.
|
||
|
||
---
|
||
|
||
## 2. Enums / ID tables (agreed by both editors)
|
||
|
||
### 2.1 Species / race ID [R2 Parse.cs addRace(); R1 PlayerSlot.FxSp comment]
|
||
| ID | Species |
|
||
|---|---|
|
||
| 0 | Human |
|
||
| 1 | Hiver |
|
||
| 2 | Tarka(s) |
|
||
| 3 | Liir |
|
||
| 4 | `_NPC` / AI-rebellion / grand-menace player (R1: "??? AI Rebellion") |
|
||
| 5 | Zuul |
|
||
| 6 | Morrigi |
|
||
|
||
### 2.2 Player color ID (palette index) [R1 PlayerSlot.FxCrId & SimPlayerColor]
|
||
`01 Red, 02 Yellow, 03 Blue, 04 Pink/Magenta, 05 Orange, 06 Green, 07 Aqua, 08 Gray,
|
||
09 Dark Green, 10 Purple`. Value **-1 ⇒ custom RGB triplet follows** (see §1.6).
|
||
|
||
### 2.3 Difficulty [R1 PlayerSettings.Difficulty]
|
||
`0 Easy, 1 Normal, 2 Difficult`.
|
||
|
||
### 2.4 Sentinel values seen in fields
|
||
`0x7FFFFFFF (Int32.MaxValue)` used as "tag"/unset (PlayerSlot.tag);
|
||
`team = -1 (0xFFFFFFFF)` = no team; R2: value `-1` = field absent in this save.
|
||
No named C# enums exist for tech IDs / weapon families — techs and weapons are **string resource
|
||
names** (e.g. tech `tNm`, weapon `wfn`), not numeric enums. Weapon *family* enumeration lives only in
|
||
the CdAi combat block as `aiSitWepFams` (opaque int set).
|
||
|
||
---
|
||
|
||
## 3. Summary block [R1 SummarySaveStructures.cs] (complex)
|
||
|
||
### SummarySaveStruct (fields in on-disk order)
|
||
1. `StringSaveStruct gameName`
|
||
2. `Int32 turn`
|
||
3. `Int32 numSys` (system count)
|
||
4. `Int32 checkSum`
|
||
5. `ComplexArray<PlayerSlotWrapper> players`
|
||
6. `SessionSaveStruct session`
|
||
7. `Int32 mapShape`
|
||
8. `Int32 incMod` (income modifier)
|
||
9. `Int32 resMod` (research modifier)
|
||
10. `Boolean alliances`
|
||
11. `Boolean teams`
|
||
12. `Boolean encounters`
|
||
13. `StringSaveStruct scenario`
|
||
|
||
### PlayerSlotWrapper (complex): `{ PlayerSlotSaveStruct slot; Int32 rank; }`
|
||
|
||
### PlayerSlotSaveStruct (complex) — new-game slot definition, ordered:
|
||
`Boolean isPlay, isDead, isReq, isRec, isFxNm` → `String fxNm` (fixed name) →
|
||
`Boolean isFxSp` → `Int32 fxSp` (species, §2.1) → `Boolean isFxCr` →
|
||
`NestedInt32 fxCrId` (color, §2.2) → `Boolean isFxBd` → `String fxBd` (badge) →
|
||
`Boolean isFxAv` → `String fxAv` (avatar) → `Int32 tag` (often 0x7FFFFFFF) →
|
||
`Int32 pwd` (password) → `Int32 team` (-1=none) → `PlayerSettingsSaveStruct settings`.
|
||
|
||
### PlayerSettingsSaveStruct (complex):
|
||
`Int32 initialTreasury, initialColonies, initialTechnologies, difficulty (§2.3)`.
|
||
|
||
### SessionSaveStruct (complex) → `TmrsSaveStruct tmrs`:
|
||
`Int32 tstl (0x7F7FFFFF), tctl (0x42700000), tqtl (0x7F7FFFFF), tqtle (0)` — timer limits (float bit-patterns stored as int).
|
||
|
||
---
|
||
|
||
## 4. CreateParameters block [R1 CreateParametersSaveStructures.cs] (complex)
|
||
|
||
### CreateParametersSaveStruct (ordered):
|
||
`String name; Int32 id; Int32 rSeed (random seed); Int32 aid; String key;`
|
||
`MapPSaveStruct mapP;` `Int32 mapS; Int32 mapF; Int32 nSys;` `Float rEnc (random-encounter rate);`
|
||
`Int32 sDist;` `Float sSize; Float sRes;` `Int32 sSuit; Int32 maxP; Int32 aSpec;`
|
||
`Boolean bAlly; Int32 nTeam; Boolean tmgrp;`
|
||
`Int32 pSav (start savings); Int32 pCol (start colonies); Int32 pTech (start techs);`
|
||
`Float incM; Float resM; ScrpSaveStruct scrp;`
|
||
|
||
### MapPSaveStruct (complex) — initial map/galaxy generation:
|
||
1. `Int32 unknown1`
|
||
2. `ComplexArray<PlanetSaveStruct> planetArray`
|
||
3. `NonComplexArray<ComplexArray<Int32>> players` (per-player int arrays; "non-complex array of players")
|
||
4. `ComplexArray<MapPNpc> npcArray` (≈ players − 1; independents/NPCs)
|
||
|
||
### PlanetSaveStruct (complex) — initial star node geometry:
|
||
`SpatialCoordinate coordinates (x,y,z floats)`, `Int32 unknown1..4`
|
||
(values seen: `0x7FFFFFFF`, `0x7F7FFFFF`). NOTE: this is the *map-generation* planet record; the
|
||
*live* planet/colony state lives in `SimSystemDetailsSaveStruct` (§8).
|
||
|
||
### MapPNpc (complex): `Int32 unknown1, unknown2`. ### ScrpSaveStruct (complex): `Int32 spc`.
|
||
|
||
### Shared value types [R1 SharedSaveStructures.cs]
|
||
- `SpatialCoordinateSaveStruct` (complex): `Float x, y, z`.
|
||
- `RgbColorFloat` (leaf): `Float r,g,b`. `RgbaColorFloat` (leaf): `RgbColorFloat rgb; Float a`.
|
||
- `RgbColorInt32` (leaf): `Int32 r,g,b`.
|
||
|
||
---
|
||
|
||
## 5. Simulation block — top level [R1 SimulationSaveStructures.cs]
|
||
|
||
### SimSaveStruct (complex) — the master world state, ordered:
|
||
```
|
||
String keyPath
|
||
Int32 nMsz, nMlc, nMnx (next-id / size counters)
|
||
NonComplexArray<Int32> playerIds, designIds, systemIds, fleetIds, shipIds, tradeIds
|
||
Int32 modCount, frame, gameId
|
||
AttributeSaveStruct attribute
|
||
RngSaveStruct rng (ByteArray unknownData ~2500 bytes: RNG state)
|
||
String gameName
|
||
Int32 map, incMod, resMod, enAl, enTm, gOTurn
|
||
NestedInt32 gOWinPly
|
||
Int32 npcm, npco, npci, npcv, npca, szad, rsad, suad
|
||
ComplexArray<ResearchSaveStruct> sprjs (shared/special research projects)
|
||
Float randEncAdjustment
|
||
Int32 cmbtid (next combat id)
|
||
ComplexArray<TurnPly> turnstats (per-turn per-player history)
|
||
NonComplexArray<SimCrepSaveStruct> creps (combat reports)
|
||
Int32 ninv, allExc1, allExc2, allExcCF
|
||
NonComplexArray<SimPlayerSaveStruct> players <-- EMPIRES (§6)
|
||
SimSpeciesArraySaveStruct species (galaxy species list)
|
||
NonComplexArray<SimSystemSaveStruct> systems <-- STAR SYSTEMS (§8)
|
||
SimNodeGrid2 ndgr2 (node-line / warp graph)
|
||
SimTradeManager trdmgr (§9)
|
||
SimSpyManager spymgr (Int32 xsid, nspy)
|
||
NonComplexArray<SimFleet> flt <-- FLEETS (§10)
|
||
NonComplexArray<Int32> acts
|
||
SimSvSctOb svSctOb (scenario/encounter objects, §11)
|
||
Int32 zdsc, zdsi, zdst (Zuul/system-destroyer counters)
|
||
```
|
||
|
||
### Small shared sim types
|
||
- `SimPopGSaveStruct` (complex): `Int32 popT; Int32 popS; Int64 popC` (pop type / species / civ count).
|
||
- `ResearchSaveStruct` (leaf): `NonComplexArray<ResearchOptionalSaveStruct> us; String nm; String ntg`.
|
||
`ResearchOptionalSaveStruct`: `Int32 usc, usp`.
|
||
- `TurnPly` (leaf): `Int32 ply; PlyHistSaveStruct hist`.
|
||
- `PlyHistSaveStruct` (complex): `Int32 ply; PlyHistStatsSaveStruct[] stats`.
|
||
- `PlyHistStatsSaveStruct` (complex): `Int64 pop; ComplexArray<PlyHistStatsSacq> sacq, slost;`
|
||
`Int32 trn, almem, inc, tdinc, sav, col, bat, tch; NonComplexArray<PlyHistClsSaveStruct> cls`.
|
||
- `PlyHistStatsSacq` (complex): `Int32 set, ses, seop, senp; NonComplexArray<Int32> seo`.
|
||
- `PlyHistClsSaveStruct` (leaf): `Int32 cls, shpt, shpl, shpk, satt, satl, satk` (ship/sat built/lost/killed by class).
|
||
|
||
---
|
||
|
||
## 6. Player / Empire [R1 SimulationSaveStructures.cs]
|
||
|
||
### SimPlayerSaveStruct (leaf wrapper): `Int32 playerId; SimPlayerDetailsSaveStruct details`.
|
||
|
||
### SimPlayerDetailsSaveStruct (complex) — the empire record, on-disk order:
|
||
```
|
||
SimPlayerTechTree techTree <-- TECH TREE (§7)
|
||
Int32 homeSystem, playerIndex
|
||
String playerName
|
||
Int32 species (§2.1)
|
||
SimPlayerColorSaveStruct colorId (palette idx or -1 + RGB, §1.6/§2.2)
|
||
String badge, avatar
|
||
Int32 team, sav (savings)
|
||
Float idealSuit, suitTolerance, maxOH
|
||
Float resRate, resModifier, resScl (research)
|
||
Int32 trm, trp, tra
|
||
Float outMod, rebOutMod, scOutMod, incMod, popMod, terraMod (economy multipliers)
|
||
Boolean aMine; Float minPure, minRate; Int32 ngts, prGtTrf, gTraf
|
||
Int32 cstR, cstE, cstT, maint, shrm, status, elim
|
||
Boolean npc, rebAi, reqCL
|
||
SimPlayerTeamSaveStruct teamStruct (Int32 alid, al, na, cf)
|
||
Int32 hasVac, hasImm, npTrak, hasDisc, hasDiscSp, hasDiscCl, hasEnc, hasEng
|
||
SimPlayerEventsSaveStruct events (Int32 evNxId; ComplexArray<SimPlayerEvent>)
|
||
NestedInt32 fngNum
|
||
Int32 pvSav, pvMA, aibN
|
||
Boolean cnTrd, cnRad, hgs, hadvs, harcc, cnVItl
|
||
Float pddm
|
||
Int32 bankWrn, bankTrn, bankPr, bankEl
|
||
SimPlayerShipRecsEventsSaveStruct shipRecs
|
||
Int32 nextPrjId, plcy, pswd, lret, nmeid
|
||
Boolean cdp
|
||
SimPlayerSpySaveStruct spy2 (Int32 defc2, rtc, evc, ttc)
|
||
SimPlayerCivrSaveStruct civR (Float smx; ComplexArray<SimPlayerCivrSpeSpVa {Int32 sp, va2}>)
|
||
Int32 aidf
|
||
Boolean srn; Int32 srcTo, lboid, lcid2
|
||
String resTnm
|
||
Boolean resErrRoll
|
||
SimPlayerModsSaveStruct conMods (3× SimPlayerConModSaveStruct {Float conMod, savMod})
|
||
NonComplexArray<Int32> ownerIds
|
||
NonComplexArray<SimPlayerDesignEntrySaveStruct> designs <-- SHIP DESIGNS (§7.2)
|
||
NonComplexArray<SimPlayerDesignEntrySaveStruct> droneDesigns
|
||
NonComplexArray<SimPlayerNote> notes (Int32 ntSys; String ntTxt; Int32 ntTrn)
|
||
NonComplexArray<SimPlayerPr> pr (Float prm; Int32 prbt)
|
||
Boolean hasAiRebellion, cta
|
||
NestedInt32 aienf
|
||
NonComplexArray<SimPlayerDetailsSpecialProjectT> nSprj
|
||
Int32 nexp, nWeapXcl
|
||
ComplexArray<SimPlayerDetailsOjv> ovjs (objectives: Int32 id; Bool cmp; Int32 spr; String dsc; Int32 nid)
|
||
ComplexArray<SimPlayerDipStat> dipStats (diplomacy; see below)
|
||
ComplexArray<SimPlayerComm> comms (Int32 msgt; SimPlayerCommMsg msg)
|
||
ComplexArray<SimPlayerPrepSaveStruct> preps
|
||
ComplexArray<SimPlayerOdesSaveStruct> odes
|
||
ComplexArray<SimPlayerOwepSaveStruct> owep
|
||
ComplexArray<SimPlayerOtchSaveStruct> otch
|
||
NestedInt32 aid
|
||
Int32 ndeflay, rdtc, tnc
|
||
```
|
||
- `SimPlayerDipStat` (complex): `Int32 other; SimPlayerDipStatDetail nap, ally, cf; Int32 deadhome`.
|
||
`SimPlayerDipStatDetail` (leaf): `Int32 last_, last_bty, bkn_, bty_`.
|
||
- `SimPlayerCommMsg` (complex): `Int32 cid2, snd, rcp, exp, sent, rcpt, sys`.
|
||
- `SimPlayerPrepSaveStruct` (complex): `Int32 oid, pid, flds, sav, home, ncol, mpwr, mcls, mmsl, nshp, nsat`.
|
||
- `SimPlayerOdes/Owep/Otch` (complex, "old design/weapon/tech" build history):
|
||
`Odes {Int32 ontF, otnL, odid, opid}`, `Owep {Int32 ontF, otnL, odet; String owep; Int32 owith}`,
|
||
`Otch {Int32 ontF, otnL, odet; String otch; Int32 owith}`.
|
||
- `SimPlayerDetailsSpecialProjectT` (leaf): `Int32 sprjT; SimPlayerDetailsSpecialProjectDetails sprj`.
|
||
`...Details` (complex): `Int32 stp; SpecialProjectSpi spi; tail (polymorphic AsMon | Tech)`.
|
||
`...Spi` (complex): `Int32 sPid, sts; Float cst; Int32 mxC; String name; Int32 trns`.
|
||
tail `AsMon`: `Int32 rDn, sys, rMn, rMx; Float rMd`; tail `Tech`: `Int32 rDn; Float aOdd, aInc; String tch; Int32 rCst`.
|
||
|
||
---
|
||
|
||
## 7. Tech tree & ship designs
|
||
|
||
### 7.1 Tech tree [R1]
|
||
- `SimPlayerTechTree` (complex): `NonComplexArray<SimPlayerTechTreeBranch> tree; NonComplexArray<SimPlayerTechTreeTech> techs`.
|
||
- `SimPlayerTechTreeBranch` (leaf): `String tNm` (tech name); `NonComplexArray<String> branches` (child tech names).
|
||
- `SimPlayerTechTreeTech` (leaf), ordered:
|
||
`String tNm` (tech name) → `Int32 st` → `Int32 tResCost` (research cost) →
|
||
`Int32 tResDone` (progress) → `Int32 tAcq` (turn acquired) → `Int32 tiAcq` (turns-to-acquire) →
|
||
`Int32 tbd` → `Boolean tfc` → `Int32 tUnlck` (unlocked flag).
|
||
**Techs are identified by string name, not a numeric enum.**
|
||
|
||
### 7.2 Ship designs [R1]
|
||
- `SimPlayerDesignEntrySaveStruct` (leaf): `Int32 designId; SimPlayerDesignSaveStruct design`.
|
||
- `SimPlayerDesignSaveStruct` (complex), ordered:
|
||
`Boolean faiDes; Boolean dHide; Int32 dWep; String dName;` `SimPlayerDesignSectionArray sections;`
|
||
`Int32 dtc; NonComplexArray<SimPlayerDesignDwg> dwgv`.
|
||
- `SimPlayerDesignSectionArray` (leaf): `Int32 count` + `count` × `SimPlayerDesignSectionEntrySaveStruct`.
|
||
- `SimPlayerDesignSectionEntrySaveStruct` (complex): `SimPlayerDesignSectionSaveStruct dsec` (Int32 unknown1, unknown2);
|
||
`ComplexArray<SimPlayerDesignUnknown1SaveStruct> dgbnk2;` `ComplexArray<String> dOpts`.
|
||
- `SimPlayerDesignUnknown1SaveStruct` (complex) → `SimPlayerDesignDw2SaveStruct dw2` (weapon slot,
|
||
conditional — see §1.6): `Boolean bId; (bId? Int32 wId : String wfn); Int32 dId`.
|
||
- `SimPlayerDesignDwg` (complex): `NonComplexArray<SimPlayerDesignGng> wgng`;
|
||
`SimPlayerDesignGng` (leaf): `Int32 wgid; SimPlayerDesignDwgWgb wgb` (complex: `Int32 unknown1..3`).
|
||
|
||
---
|
||
|
||
## 8. Star systems & planets [R1]
|
||
|
||
- `SimSystemSaveStruct` (leaf): `Int32 sysId; SimSystemDetailsSaveStruct details`.
|
||
- **`SimSystemDetailsSaveStruct` (complex)** — the live system+colony record, on-disk order:
|
||
```
|
||
SpatialCoordinate pos
|
||
RgbaColorFloat starColor
|
||
Int32 idx
|
||
Int32 size (1-10)
|
||
Float suit (climate hazard)
|
||
Int32 res, aRes, mRes (resources / asteroid / extra)
|
||
Boolean noRebAi
|
||
Int32 tRes, pop
|
||
ComplexArray<SimPopGSaveStruct> popG
|
||
Float infra
|
||
Int32 pvPop; ComplexArray<SimPopG> pvPopG; Float pvInfra, pvSuit; Int32 pvRes, pvARes2, pvMRes; Bool pvNoRebAi (previous-turn snapshot)
|
||
SimSystemDetailRtsSaveStruct rts (Float sRs, sRt, sRsc, sRtf, sRi, sRoh, sRnr — IO allocations)
|
||
Int32 abdn; Boolean dstyd; Int32 tnsOh
|
||
Float outMod, repCur, repMax
|
||
Int32 ntdev, pbon
|
||
ComplexArray<SimPopG> pbon2
|
||
Float ibon
|
||
Int32 ltis, rbfl, rbtn, rbfr, rbwn, hsrg
|
||
NonComplexArray<SimSystemDetailHalt> halt (Int32 haltt; Bool haltv)
|
||
SimSystemDetailsVonNeumann vnm (Bool vnh gate → details: Bool vnd, vnex3, vnpex3)
|
||
String name
|
||
SimSystemDetailFlags1 flags1 (Int32 vFlags, eFlags, aFlags, fFlags, gFlags)
|
||
Int64 bats2 (recent battles; larger=more recent)
|
||
Int64 rcex
|
||
SimSystemDetailFlags2 flags2 (Int32 mnRFlags, rfRFlags, clkFlags)
|
||
Int32 eggScio, terrFl, tAcq, tfAcq, tDst
|
||
ComplexArray<SimPopG> dcs; Int32 dsu
|
||
ComplexArray<SimSystemDetailCm> cm, pvcm (SimSystemDetailCm: Int32 msp, mv)
|
||
ComplexArray<SimSystemDetailCme2> cme2 (Int32 mid, mtrT, mn, mtp; ComplexArray<Cm> mfx; String mdsc)
|
||
ComplexArray<SimSystemDetailSpy> spies
|
||
Int32 pid (owner player id), defF, defSf
|
||
SimSystemDetailBq bq (ComplexArray<SimSystemDetailBqOrd> ords; Ord: Int32 desId, con, conleft, sav, ordId)
|
||
NonComplexArray<SimSystemDetailAdct> adct (Int32 ads, adt)
|
||
Int32 numPlgs2
|
||
NonComplexArray<Int32> flts, gfs, snF, mnF (fleets / gates / stations / monitors present)
|
||
NonComplexArray<SimSystemDetailNvo> nvos (colonies; see below)
|
||
NonComplexArray<SimSystemDetailVe> nve (Int32 ePid, ets, eid)
|
||
NonComplexArray<SimSystemDetailVs> nvs (Int32 pid; SimSystemDetailVsPView pview)
|
||
SimSystemDetailsIndi indi (Bool hindi gate → indsp, SimPlayerColor indcl, String indnm/indav/indba)
|
||
```
|
||
- `SimSystemDetailNvo` (leaf): `Int32 pid, tShn, oId; Boolean isInd; SimSystemDetailNvoIndi indi`
|
||
(independent-colony sub-block gated by `isInd`).
|
||
- `SimSystemDetailVsPView` (complex — per-player *seen* snapshot of a colony): `Int32 vTrn, pop;`
|
||
`ComplexArray<SimPopG> pop2; Int32 infra; Float suit; Int32 res, aRes2, mRes; Bool noRebAi;`
|
||
`Int32 pbon; ComplexArray<SimPopG> pbon2; Float ibon; Int32 terrFl; Bool footer`.
|
||
|
||
### 8.1 R2 ⇄ R1 cross-map for planet/colony fields (verifier gold)
|
||
R2 finds these serialized tags anywhere in the "Planets" region (between markers `NumSys`…`NdGr2`).
|
||
They correspond to fields inside R1's `SimSystemDetailsSaveStruct` / `...VsPView`:
|
||
|
||
| R2 serialized tag | type | meaning | R1 field |
|
||
|---|---|---|---|
|
||
| `Idx` | int | planet/system id | `idx` |
|
||
| `Name` | string | name | `name` |
|
||
| `Size` | int | 1-10 | `size` |
|
||
| `Suit` | float | climate hazard | `suit` |
|
||
| `Res` / `ARes2` / `MRes` | int | resources | `res` / `aRes` / `mRes` |
|
||
| `Infra` | float | infrastructure | `infra` |
|
||
| `ibon` | float | infra bonus | `ibon` |
|
||
| `Pop` | int | imperial pop | `pop` |
|
||
| `pbon` | int | imperial pop bonus | `pbon` |
|
||
| `PopC` | long | civilian pop | (SimPopG `popC` Int64) |
|
||
| `OID` | int | owner id (**= PID×16**) | `pid` (owner) |
|
||
| `PID` | int | derived player id | (OID/16) |
|
||
| `SRt/SRsc/SRtf/SRi/SRoh` | int | IO trade/ship/terraform/infra/overharvest | `rts.sRt/sRsc/sRtf/sRi/sRoh` |
|
||
| `Abdn` | short | abandon order | `abdn` |
|
||
| `Dstyd` | short | star annihilated | `dstyd` |
|
||
| `ltis` | short | last-time-seen | `ltis` |
|
||
| `VFlags/EFlags/AFlags/FFlags/GFlags` | int | state flags | `flags1.*` |
|
||
| `Bats2` | int | recent combat | `bats2` (Int64 in R1) |
|
||
| `nadct` | int | addicted (1=yes) | (in `adct` array) |
|
||
| `NumFlts/NumGFs/NumSnF/NumMnF` | int | fleets/gates/stations/monitors | `flts/gfs/snF/mnF` counts |
|
||
|
||
Note the **type disagreements** (verifier flags): R2 reads `Abdn`, `Dstyd`, `ltis` as **short (Int16)**
|
||
while R1 models them as framed `Int32SaveStruct`; R2 reads `Bats2` as int while R1 uses Int64. R2's
|
||
name-scan reads the value bytes directly after the tag+pad, so R2's width is the more literal
|
||
on-value-bytes claim for those specific fields; treat as "value is small, low bytes are the datum."
|
||
|
||
---
|
||
|
||
## 9. Trade & node grid [R1]
|
||
- `SimNodeGrid2` (complex): `ComplexArray<SimNodeGridPath> paths; Int32 nextId`.
|
||
`SimNodeGridPath` (complex): `Int32 npt, npid, npfr(from), npto(to), npctm, npcby, npdtn, npdtf, npenp, npuse, nptf`.
|
||
- `SimTradeManager` (complex): `NonComplexArray<SimTradeSector> tradeSectors; Float sctSize; List<SimTradeSectorRt> rt`.
|
||
`SimTradeSector` (leaf): `Int32 tradeId; SimTradeSectorTradeSaveStruct trade`.
|
||
`SimTradeSectorTradeSaveStruct` (complex): `SpatialCoordinate pos; Int32 tradeSectorGridId;`
|
||
`SimTradeSectorTradeCtrSaveStruct tsctr(3 floats); Int32 tssec, tsct, tscr, ptssec, ptsct, ptscr;`
|
||
`ComplexArray<...Fwarn {Int32 pId, ntrns}> fwarn; NonComplexArray<Int32> systems, tsflt`.
|
||
`SimTradeSectorRt` (complex): `Int32 tro, trfow, trfr, trfrs, trtow, trto, trtos, trtc`.
|
||
|
||
---
|
||
|
||
## 10. Fleets & ships [R1]
|
||
- `SimFleet` (leaf): `Int32 fltId; SimFleetDetails flt`.
|
||
- **`SimFleetDetails` (complex)**, ordered:
|
||
`SpatialCoordinate pos; Int32 pId, locId; FlightPlanContainer fplan (Bool hfPlan gate);`
|
||
`String ftName; Int32 ftTrans; SimFleetOrigin ftOrig (Int32 ×3);`
|
||
`Int32 ftFlag, ftae, ftpae, ftEnc, ftMs, perm; SpatialCoordinate prvPos;`
|
||
`LayContainer lay (Bool hLay gate); NonComplexArray<SimFleetShip> ships`.
|
||
- `SimFleetDetailsFlightPlan` (complex): `ComplexArray<Wpt> wpts; Float fPsp2; Int32 fPeta2;`
|
||
`Fpogn2 (3 floats); SpatialCoordinate fPdpos; Int32 pnd`. `Wpt`: `Int32 wpt, tp; Nrt {Int32 nrp,nrf,nrt}`.
|
||
- `SimFleetShip` (leaf): `Int32 shipId; SimFleetShipDetails ship`.
|
||
- **`SimFleetShipDetails` (complex)**, ordered:
|
||
`Int32 desId (design id), fltId, plrId; Float range; SimFleetShipHealth health (3 floats: command/mission/drive);`
|
||
`Int32 conCap, refCap, repCap, mineCap, plg, act; Boolean dep, atq; Int32 encId;`
|
||
`SimFleetShipPrish prish (Int32 prMax; NonComplexArray<Int32> prSp); Int32 lct, tsd, atsp, tblt;`
|
||
`Boolean hbq; SimFleetShipDetailsBq bq (gated by hbq);`
|
||
`Boolean hsp; SimFleetShipDetailsSp sp (gated by hsp; two SpPop pop/pPop);`
|
||
`NonComplexArray<SimFleetShipDetailsTh> th (Float th, thm)`.
|
||
`SimFleetShipDetailsBqOrd`: `Int32 desId, con, conleft, sav, ordId` (same shape as system BqOrd).
|
||
|
||
---
|
||
|
||
## 11. Combat reports & scenario/encounter objects [R1]
|
||
- `SimCrepSaveStruct` (combat report, complex): `Int32 cid, trn; SpatialCoordinate pos; Int32 sid, auto, dur, cow, cdst, cpk, cpt, cdt, cdi;`
|
||
`ComplexArray<SimCrepPrepSaveStruct> prep; ComplexArray<SimCrepWrepSaveStruct> wrep`.
|
||
`SimCrepPrepSaveStruct`: `Int32 plr; Bool ai; Int32 ally, status, mxeng, mxcls, mxmsl;`
|
||
`NonComplexArray<Cls> cls; NonComplexArray<Sec> sec; Int32 ndam; ComplexArray<Srep> srep`.
|
||
`SimCrepPrepSrepSaveStruct`: `String name; Int32 did, cls; Int64 caps2; Int32 nshp, nfld, nlst, dtak; SimDamsSaveStruct dams`.
|
||
`SimDamsSaveStruct`: `Int32 dams, damp, dami, damt`. `SimCrepWrepSaveStruct`: `String wep; SimDams dams`.
|
||
- `SimSvSctOb` (complex): `ScnObjStruct scn; NonComplexArray<SimSvSctObXscn> xscn; SimScSctObEncObjArray encObjs`.
|
||
`SimSvSctObXscn` = polymorphic-by-string (§1.6): `traps` (ComplexArray<TrapDetails {Int32 sys,pid,trenc,trgenc}>),
|
||
`gmtrigger` (Int32 gmch), `crowdefs` (Int32 sys; NonComplexArray<Int32> dsys,des; Float drad), `indsys`/default (empty).
|
||
- **`SimScSctObEncObjArray`** = grand-menace/encounter table, dispatched **by fixed index 0-8** (§1.6).
|
||
Each subclass carries that menace's state, e.g.:
|
||
- `EncInfest` (Hiver infestation): `NonComplexArray<Asg> asg; ComplexArray<Infest> infests; Int32 deshive, deslarva`.
|
||
- `EncHives`: `Int32 qDesignId; ComplexArray<Hive {Int32 hiveId,queenId,nextQ}> hives; ComplexArray<Queen {Int32 queenId,qDstId}> queens; ComplexArray<NestedInt32> sysMem`.
|
||
- `EncAsteroidMonitor`, `EncTD`, `EncWD`, `EncRsuc`, `EncDfts` (Von Neumann; large), `EncIni2`.
|
||
|
||
---
|
||
|
||
## 12. CdTable — combat / AI persistence block [R1 CdTableSaveStructures.cs]
|
||
|
||
### CdTable (leaf, top of the 4th file-section): `ComplexArray<String> cdt; CdPlayer cdplayer; CdAi[] cdai`.
|
||
|
||
### CdPlayer (complex) — **entirely reverse-unlabeled** (fields named `unknown1..35`); shape is known:
|
||
`Int32 unknown1(=16); Bool unknown2; Float unknown3; Bool unknown4; Int32 unknown4p5; Bool unknown5..8;`
|
||
`Int32 unknown9,10; NonComplexArray<CdPlayerUnknown11Item {Int32 unknownId, const1, const2, value1}> unknown11;`
|
||
`Int32 unknown12..14; NonComplexArray<Int32> unknown15,16; NonComplexArray<Unknown17Item{Int32 ×2}> unknown17;`
|
||
`NonComplexArray<Int32> unknown18; Int32 unknown19..21;`
|
||
`NonComplexArray<Unknown22Item{Int32×2,Bool}> unknown22; Int32 unknown23..35`.
|
||
|
||
### CdAi (complex) — per-AI-player planner state, ordered:
|
||
`AttributeSaveStruct aiAttr; NestedInt32 aiTurnPris; CdAiSit aiSit; NestedInt32 aiPlyHat;`
|
||
`ComplexArray<CdAiPrsUnknown {Int32 pid,trn}> prs2; Int32 dsh, nbStab, nmBlst; CdAiAidng aidng;`
|
||
`Int32 aiHivJ, sdFlT; NestedInt32 nalat; Int32 lnat, lat;`
|
||
`NonComplexArray<CdAiSys> aiSys; NonComplexArray<CdAiCmbr> cmbR; NonComplexArray<CdAiCl {Int32 clTn,clSyId,clPlId}> cl;`
|
||
`NonComplexArray<CdAiPrv {Int32 nPrvId; Float nPrvVa}> prv; NonComplexArray<Int32> tecs; Int32 fct;`
|
||
`ComplexArray<CdAiApr {Int32 sid,tn0,tn1}> apr`.
|
||
- `CdAiSit` (complex): `NestedInt32 aiSitSecs; NestedInt32 aiSitWepFams` (**weapon-family set** lives here — opaque ints).
|
||
- `CdAiCmbr` (complex): `Int32 crTrnK; Bool crPce; NestedInt32 crSys; CdAiCmbrCrplSv2 crplSv2`.
|
||
`...CrplSv2`: `Int32 rpBon, rpBonT, savBonus; Bool maintHf; ComplexArray<TacReport> tacReports`.
|
||
`TacReport`: `TrStruct trBy, trTo; TacReportDamage damageStruct; NonComplexArray<TacReportShips> ships`.
|
||
`TrStruct`: `Int32 treHd, treHi, treD, treDp, treDi, treDt, treB`.
|
||
`TacReportDamage` (leaf): `Int32 tRid, tRal, tRbal, tRlas, tRmis, tRmin, tRnrg, tRbio, tRbrd; Bool tRsld, tRsldd, tRsldc, tRsldi, tRsldr`
|
||
— **damage-by-weapon-family breakdown**: bal(listic)/las(er)/mis(sile)/min(e)/nrg(=energy)/bio/brd(=boarding); sld=shields.
|
||
`TacReportShips` (leaf): `Int32 tRships, tRsldr, tRshipL`.
|
||
|
||
---
|
||
|
||
## 13. Coverage gaps & contradictions
|
||
|
||
**Coverage (what's decoded):**
|
||
- R1 decodes essentially the *entire* file top-to-bottom: summary, create-params/map-gen, full sim
|
||
(players, tech, designs, systems/colonies incl. per-player fog-of-war snapshots, fleets, ships,
|
||
trade, node grid, combat reports, grand-menace/encounter objects) and the CdTable AI block. This is
|
||
the most complete community model and the primary Rosetta source.
|
||
- R2 decodes only Summary, Player Settings, Players, Species, and Planets (colony) fields, by tag
|
||
search — but adds *friendly semantics* and confirms the value-bytes width of several planet fields.
|
||
|
||
**Known-unknown fields (labelled `unknown*` in R1 — do NOT treat names as authoritative):**
|
||
- All of `CdPlayer` (`unknown1..35`) and `CdAiAidngDnId`, `SimFleetOrigin`, `SimFleetDetailsFlightPlanFpogn2`,
|
||
`SimTradeSectorTradeCtr`, `SimPlayerDesignDwgWgb`, `SimSystemDetailNvoIndcl*` bodies.
|
||
- `SimFleetShipHealth` three floats guessed as command/mission/drive.
|
||
- `PlanetSaveStruct.unknown1..4` (map-gen) unexplained.
|
||
|
||
**Not decoded / thin:**
|
||
- Tactical/real-time combat geometry: only *reports/summaries* are stored (SimCrep*, CdAiCmbr TacReport).
|
||
Per-ship in-battle positions/velocities are not in these editors (likely not in the sim save at all).
|
||
- `SimSystemDetailSpy` body is empty in R1 (marked "needs to be populated"); spy detail unresolved.
|
||
- RNG state (`RngSaveStruct.unknownData`) is an opaque ~2500-byte blob.
|
||
- R1 source comments flag `SimSystemDetailSpiesArray`, `SimSvSctObXscnXsc` and
|
||
`SimScSctObEncObjDetails` as incomplete/"wrong" in places — verify encounter bodies against binary.
|
||
|
||
**Contradictions between R1 and R2 (reconcile against binary):**
|
||
1. **Field widths on planet flags:** R2 reads `Abdn`, `Dstyd`, `ltis` as Int16 and `Bats2` as Int32;
|
||
R1 models `abdn/ltis` as Int32 and `bats2` as Int64. → The datum is small; check the true stored
|
||
width in the binary struct.
|
||
2. **PID vs OID:** R2 asserts `OID = PID*16` (owner id is player index << 4); R1 stores a single `pid`
|
||
owner field and does not model the ×16 relationship. → Confirm whether the binary owner field is a
|
||
raw index or a shifted/tagged handle.
|
||
3. **`_NPC` species id 4:** R2 names it `_NPC`; R1 comment guesses "AI Rebellion". Same numeric id 4,
|
||
different label — likely a shared "non-player/rogue" species slot.
|
||
4. R2 treats Players and PlayerSettings as separate flat tab regions bounded by marker strings
|
||
(`HomeSys`…`ISsp`, `Slot`…`Session`); R1 shows these are actually nested (settings inside the
|
||
Summary PlayerSlot, live player data inside SimPlayerDetails). R2's region boundaries
|
||
(`Summary`,`Slot`,`Session`,`HomeSys`,`ISsp`,`NumSys`,`NdGr2`,`PlayerIDs`,`DesignIDs`) are useful
|
||
**section-marker strings** to locate blocks in the raw binary.
|
||
|
||
**High-value binary-scan signatures:**
|
||
`0xBEEFBEEF` / `0x41104110` complex-struct brackets; length-prefixed ASCII field-name tags
|
||
(`[int32 len][name]`) preceding every named scalar; section marker strings above.
|