sots-re/findings/subsystems/strings-and-config.md

153 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SOTS1 — String Intelligence (Sword of the Stars.exe)
**Binary:** `/srv/re-lab/sots-game/Sword of the Stars.exe` (7,898,624 bytes)
**Type:** PE32 i386, GUI, 5 sections, MSVC-2010 (MSVCP100/MSVCR100), DirectX-9.
**Extraction:** `strings -n 5` (ASCII) + `strings -e l -n 5` (UTF-16).
Counts: **28,719 unique ASCII strings**, only **8 unique UTF-16** (this build stores nearly everything as ASCII/UTF-8; localized text lives in the `.gob` archives, not the exe).
---
## 0. TL;DR — highest-value findings
- **Battle-load / multicore bug is right here in the open:** the exe ships a **`ForceSingleCore`** config key (also literal `FORCESINGLE`) that drives **`SetProcessAffinityMask`**, logging **`Limiting process affinity to CPU-%i...`**. These three strings sit adjacent in `.rdata` (offsets 6,510,208 / 6,511,852 / 6,523,472). This is the classic SOTS1 fix for the combat-load hang on multi-core CPUs.
- **Build stamp:** version **`1.8.1`**, compiled **`Wed Dec 13 03:38:31 2017`** (this is the GOG 2018 rerelease of the original 2006 game).
- **PDB leak:** `E:\sots.clean\Distribution\Demo\Sword of the Stars - Retail.pdb` — confirms build tree layout and that this is the "Retail" config off the `sots.clean` branch.
- **No embedded scripting VM** (no Lua/Python). Game logic is **data-driven** via `Data/**/*.txt` + `*.csv` + `*.tech`; `*.script` files are GUI/HUD layout definitions, not a bytecode language.
- **Namespaces:** engine code lives under **`Mars`** (the engine) and game code under **`Game`** (RTTI decorations like `.?AV...@Game@@` / `@Mars@@` are everywhere and are a goldmine for the class map).
- **Networking = GameSpy SDK** (IRC-style Chat/Peer + NatNeg + master server) over **WSOCK32**, plus two in-house message families: **`SNM*`** (Strategy Net Messages, ~60 types) and **`FNM*`** (File Network Messages — chunked reliable transfer used for host migration / savegame sync).
---
## 1. Config keys / sots.ini / registry
SOTS does **not** embed literal `[Section]` header strings; it reads config through the Win32 profile API (`GetPrivateProfileStringA`, `GetPrivateProfileSectionA`, `WritePrivateProfileStringA/SectionA`) and the registry (`RegOpenKeyExA` on `Software\Sword of the Stars`).
**Config files / stores (samples):**
- `sots.ini`, `sots.cfg`
- Registry key `Software\Sword of the Stars` (also `Software\GameSpy\GameSpy 3D\Registration`)
- `Profiles/Profile_*.ini`, `Profiles/*.profile`, `Profiles/lastprofile.dat`
- `CurrentProfile`, `ProfileID`, `ProfileName`, `lastprofile`
**Full user-facing knob list (mined config keys):**
*CPU / performance (BUG-relevant):*
- `ForceSingleCore` / `FORCESINGLE`
*Video / render:*
- `adapter`, `width`, `height`, `fullscreen`, `windowed`, `fullscreenWidth`, `fullscreenHeight`, `antialiasing`, `brightness`, `vertexColors`
*Audio:*
- `MusicVolume` (plus per-sound `*_sound_minrange`/`*_sound_maxrange`, `SOUND_START_DELAY`)
*Gameplay defaults (game-setup options):*
- `MaxPlayers` ("must be an integer between 2 and 8"), max colonies (1–10)
- `EnableAlliances`, `EnablePlayerPasswords`, `EnableTeams`, `EnableTeamsGrouped`
- `DefaultAutoRefuel`, `DefaultAutoResolveCombat`, `DefaultHideFleetLines`, `DefaultShowAxes`, `DefaultShowTacticalInfo`, `DefaultShowTacticalOverlay`, `DefaultSubdueStars`
- `AutoPause`
*Networking:*
- `HostName`, `HostPort`, `hostname`, `hostport`, `host_species`, `MaxTxMessageSize`
---
## 2. File paths, extensions & asset pipeline
**Extension histogram (top): ** `.X` (DirectX mesh, dominant model format), `.fx` (D3D9 effect/shader), `.tga`/`.dds`/`.bmp`/`.jpg` (textures), `.txt` + `.csv` (data tables), `.wav`/`.bik` (audio/Bink video), `.tech` (tech tree), `.script`/`.def`/`.effect`/`.shipsection` (custom defs), `.gob` (archive), `.sav`/`.dat`/`.ini`/`.cfg`.
**Directory layout (from ~261 path strings):**
- `Data/` — `globals.txt`, `Species.txt`, `encounters.txt`, `music.csv`, `Combat/*.txt` (`ship.txt`, `planet.txt`, `terrain.txt`, `sensors.txt`, `camera.txt`, `drone.txt`, `spyship.txt`, `gravboat.txt`, `shipai.txt`, `damfx.txt`, `ctechvars.txt`…), `Strategy/playercolors.txt`, `Strategy/ai/affinity_weapon.csv`, `Strategy/ai/affinity_section.csv`
- `Species/<Race>/...` — Human, Hiver, Tarkas, Liir, Zuul, Morrigi, `_NPC` (art/debris, sections, `_Spy.shipsection`)
- `Models/` — `Planets/`, `Shields/` (`sld_*` per hull class/tech), `Asteroids/Asteroids.csv`, `Trade/`, `Skysphere/`
- `Weapons/` — `_weapons.txt`, `_turrets.txt`, `_defaultweapons.txt`, `art/*.X`
- `TechTree/` — `MasterTechList.tech`, `tech.fx`, `techBases.fx`, `TechTreeDisplay.script`
- `Scenarios/` — `Gathering`, `ProgressionWars`, `UpstartApes*`, `BrokenEmpire`, `CivilWar`, `EndFlesh`, `HiverInvasion*`, `CrownJewels`, `Tournament`, `Antiquarians` (each a `.txt` + supporting `.csv`s)
- `GUI/` — `Combat/*.script` (HUD), `StarMap/Lines.script`, `Mouse/mouse_*.tga`, `SplashArt/`, `LoadScreen_Anim0*.tga`
- `Render/` — many `.fx` (`DefaultScene`, `StandardMaterial`, `GasCloud`, `CloakBadges`, `NodeDrive`, `StratStars`, `CombatStars`, `Miniship`, `StarMapBlobs_*`…)
- `Sounds/`, `Movies/` (`sotsintro.bik`), `Locale/<lang>/Desc*.txt`, `Avatars/AvatarTable.txt`, `Badges/BadgeTable.txt`
**`.gob` archive format:** the `gobio` namespace (`gobio::IFileSystem`, `NativeFileSystem`, `ZipFileSystem`, `Buffer`/`IBuffer`, `gobio_D3DXInclude`) shows **.gob is a ZIP-based virtual filesystem** with a native-directory fallback (so loose files can override archived ones — useful for asset extraction/modding). Runtime archives: `sots.gob` (1.5 GB) and `sots_local_en.gob` (628 MB, localization).
---
## 3. Scripting
**No embedded Lua/Python/bytecode VM.** Grep for `lua_`/`luaL_`/`Py_`/`.pyc`/`tolua` returned only incidental substrings. The engine is fully data-driven:
- `*.script` files are **GUI/HUD layout descriptions** parsed by the engine (`GUI/Combat/CombatHUD.script`, `SensorHUD.script`, `NoHUD.script`, `Models/Planets/PlanetResources.script`, `TechTree/TechTreeDisplay.script`, `GUI/StarMap/Lines.script`). Errors: `Error creating extra script %s.`, `Failed to open script %s!`, `StringTable: %s not found in script.`
- Scenario/AI behavior is described in `.txt`/`.csv` tables (`ScenarioKeyFile: Unrecognized script object id: %s`, `Script described %d slots, but max players is %d.`). "Script" here = declarative data, not code.
---
## 4. Format strings & error/log messages (subsystem map)
~991 printf-style and ~2,498 error/assert strings. They name subsystems cleanly:
- **Combat sim:** `CombatPlayerAI::Update`, `(Combat Player %i) ...`, `Reserves::PickReserve`, `ShipBase::Damage/Load`, `CombatFieldTemplate`, `SpawnShips`, `ICarrier`/`Carrier runway`, `MissileBase`, `Bolt`/`Mine`/`ChainLightning`, `@SYNC ERROR DETECTED: Frame %d` (determinism check).
- **Strategy sim:** `StrategyClient`/`StrategyServer`/`StrategyApp`, `BuildQueue::*`, `HandleEvent: Cannot init/term/sync fleet|ship|design ...`, `Alliances:`, `SpecialProject:`, `PLAGUE:`, `Sensors: %f seconds to update sensors for all players.`
- **Tech:** `MasterTechTree`, `TechTree::GetSync()/GetInit()`, `Unable to find mesh for tech %s.`
- **Renderer / D3D9:** `Could not create D3D Device!`, `Created D3D device with full features` / `with fallback (%i)`, `*** Losing/Restoring D3D Device Objects ***`, `Adapter: %d: %s`, `Resolution: %dx%d @ %d-Hz`, `Antialiasing: %s`, `D3D available (SDK version %08x).`
- **Serialization:** the `Mars::StreamableHelper<...>` / `VectorHelper<...>` templates (see §8) wrap every `*Params` struct — this is the save/network serialization layer.
- **Crash handling:** `MiniDumpWriteDump` (via `DBGHELP.DLL`) — the game writes minidumps.
---
## 5. Network
**Transport:** `WSOCK32.dll`; `IPHLPAPI.DLL` (NAT/adapter discovery).
**GameSpy SDK** (matchmaking + chat):
- Master/query servers: `%s.master.gamespy.com`, `%s.available.gamespy.com`, `%s.ms%d.gamespy.com`; `GameSpyHTTP/1.0`; registry `Software\GameSpy\GameSpy 3D\Registration`; `gamespy_splash_A.tga`.
- **NAT negotiation:** `NatNeg error: %d (progress %d).`, `NatNeg init error: %d.`, extensive `Network: ... NAT negotiation ...` logging.
- **IRC-style Chat/Peer protocol** (format strings): `PRIVMSG %s :%s`, `JOIN %s %s`, `PART %s :%s`, `MODE %s ...`, `NICK %s`, `TOPIC`, `NOTICE`, `USER`, `LOGIN`, plus GameSpy key exchange (`GETKEY`, `SETCKEY`, `CRYPT des %d %s`) and browser tokens (`\hostname\gamemode`, `#GSP!%s`).
**In-house P2P group layer** ("Network:" logger): host/client group model with **host migration** — `Network: New group created. %s is host.`, `DoHost()/DoConnect()/DoDisconnect()`, `HostMigration: Begin/Setup migration...`, `HostMigration failed: ...`, `Rejoin: ...`.
**`SNM*` — Strategy Net Message types** (~60; the strategy-layer protocol enum). Samples: `SNMJoin`/`SNMJoinReply`, `SNMEndTurn`, `SNMHostCombat`/`SNMLaunchCombat`/`SNMJoinCombat`, `SNMEncounterQuery*`/`SNMEncounterResults`, `SNMChat`/`SNMLobbyChat`, `SNMAlliance*`, `SNMSlot*` (lobby slot mgmt + passwords: `SNMSlotReqPW`/`SNMSlotPWAttempt`/`SNMSlotPWAccepted`), `SNMSetPlayerStatus`, `SNMRunAI`/`SNMKillAI`/`SNMMakePlayerAI`, `SNMSetTimers`, `SNMRefreshSlots`.
**`FNM*` — File Network Messages** (`Mars` namespace): `FNMBase`, `FNMChunk`/`FNMChunkReply`, `FNMDone`/`FNMDoneReply`, `FNMSendNotify`/`FNMSendReply`, `FNMError` — a chunked reliable **file-transfer** protocol (drives `FileTransfer: Cannot open %s to send to %s.` and the host-migration savegame push).
---
## 6. Version / build
- **`1.8.1`** (game version; `Invalid version number string (must conform to '0.0.0'...)`).
- Build timestamp: **`Wed Dec 13 03:38:31 2017`**.
- Window class / mutex: `Kerberos_SwordOfTheStars_WndCls`, `Kerberos_SwordOfTheStars_Mutex`.
- Title format: `Sword of the Stars%s (%s %s)`; MOTD URL `www.kerberos-productions.com/motd`.
- **PDB:** `E:\sots.clean\Distribution\Demo\Sword of the Stars - Retail.pdb`.
- Toolchain: MSVC 2010 (`MSVCP100.dll`, `MSVCR100.dll`). Middleware DLLs: `d3d9.dll`, `d3dx9_42.dll`, `DSOUND.dll`, `binkw32.dll` (Bink video), `WINMM.dll`, `DBGHELP.DLL`.
- Editions gated in-exe: `SOTS_COLLECTORSEDITION_ENABLED_TITLE`, `SOTS_COMPLETECOLLECTION_ENABLED_TITLE`, `SOTS_ARGOSNAVALYARD_TITLE`.
---
## 7. BUG watch — CPU / thread / core / affinity (battle-load hang)
Smoking-gun strings, with byte offsets in the file:
| Offset | String | Meaning |
|---|---|---|
| 6,510,208 | `Limiting process affinity to CPU-%i...` | log emitted when single-core mode engages |
| 6,511,852 | `ForceSingleCore` | ini/cfg key that triggers it |
| 6,523,472 | `FORCESINGLE` | uppercased variant (likely token/registry) |
| 7,172,098 | `SetProcessAffinityMask` | KERNEL32 import performing the pin |
| 5,266,079 / 5,266,317 | `F ;Cpu` | fragment near the CPU-detect block |
Supporting threading imports/strings: `CreateThread`, `CreateMutexA`, `GetCurrentThreadId`, `InterlockedExchange`, `InterlockedCompareExchange`, `IsProcessorFeaturePresent`, `SetThreadPriority`, `_configthreadlocale`, `Initializing Streaming sound update thread...`, `std::_Lockit`. The **audio streaming thread** and the combat sim are the likely contenders in the multi-core race; `ForceSingleCore`→`SetProcessAffinityMask(CPU-i)` is the shipped mitigation. Determinism guard `@SYNC ERROR DETECTED: Frame %d` shows the sim expects lockstep — worth correlating with the load path.
*(Note: most `.?AV...Affinity@Game@@` and `AITAttack*` hits are gameplay "diplomatic affinity" / AI, NOT CPU affinity — don't confuse them.)*
---
## 8. Data model (tables / params structs)
No SQL/SQLite. Two shapes:
**A. CSV/TXT tables** with a schema-ish loader: `[%s,%i] CSV row parsing failed.`, `Field '%s' precedes 'name' field and will be ignored in %s.`, `AsteroidEdictDB: Table is empty!`, `Sprite Table: Sprite %s multiply defined/not found.`, `StringTable`/`GlobalConsts: %s being registered after loading const table.`, `Weapon not found: "%s" - Was it added to the index file?`. There is also XML/DTD validation (MSXML) for some data.
**B. Binary-serialized `*Params` structs** (save/network via `Mars::StreamableHelper<T>` / `VectorHelper<T>`). The full RTTI list gives the effective "record types":
`StrategyGameParams`, `StrategyGameCreateParams`, `StrategyGameLoadParams`, `StrategyPlayerParams`, `StrategySessionParams`, `StrategySimParams`, `StrategyTimerParams`, `StrategyScriptParams`, `StrategyHostParams`, `StrategyLaunchParams`, `SystemParams`, `StarMapParams`, `NodePointParams`, `ShipParams` (with nested `Section`, `GunBank`), `CombatEncounterParams`, `CombatLaunchParams`, `EncounterParams`, `LaunchParams`, `SyncParams`, `LocustEncounterParams`, `RefugeesEncounterParams`, `SwarmEncounterParams`, `VNHomeEncounterParams`, `VonNeumannEncounterParams`. These are the serialized game-state schema — the right targets for savegame/netpacket structure work.
**Engine/game split:** RTTI shows engine classes under **`@Mars@@`** (Frame, ModelFrame, PhysicalEntity, ListFramework, gobio, FNM*, Streamable) and game logic under **`@Game@@`** (all the strategy/combat classes). Use this to partition the code map for the Ghidra agent.
---
*Extraction scripts left in CT111: `/tmp/extract.py`, `/tmp/g2.sh`, `/tmp/g3.sh`, `/tmp/g4.sh`.*