sots-re/findings/subsystems/running-the-game.md

125 lines
11 KiB
Markdown

# SotS (2006, GOG v1.8.1) running on GPU-less Win10 VM — save-game capture
Date: 2026-09-07. VM140 `sots-re-win10` (Win10 22H2, 16 vCPU, 12 GB, `vga: std`, no GPU, no audio device).
Game: `C:\SOTS\Sword of the Stars.exe` (GOG build, in-game version string `ver.1.8.1 Wed Dec 13 03:38:31 2017`).
Result: **game reaches the main menu, a custom game was created, End Turn was pressed twice (turn 1 -> 3), and the
game wrote autosaves + a manual save.** Rendering is DXVK 3.1 (d3d9 -> Vulkan) on Mesa lavapipe (CPU Vulkan).
## Save files (this directory)
| File | Size | What |
|---|---|---|
| `Autosave EndTurn - turn2.sav` | 64,967 | written by End Turn #1; dialog lists it as "Turn 1" (pre-turn state) |
| `Autosave - turn2.sav` | 66,739 | written by End Turn #1; dialog lists it as "Turn 2" (post-turn state) |
| `verify1.sav` | 66,732 | manual save from the Save Game dialog on turn 2 (copy of `MyGameverify1verify1.sav`, see gotchas) |
| `MyGameverify1verify1.sav` | 66,732 | same file under the name the game actually wrote |
| `Autosave EndTurn - turn3.sav` | 66,732 | written by End Turn #2 |
| `Autosave - turn3.sav` | 67,219 | written by End Turn #2 (turn 3 state) |
| `Autosave Backup - turn2.sav` | 66,739 | `(Autosave Backup).sav` — previous `(Autosave).sav` rotated by End Turn #2 |
| `Profile_re.ini` | 4,181 | player profile the game created (`C:\SOTS\Profiles\Profile_re.ini`) |
Game facts for the verifier: 2 players (human profile `re`, random race -> **Morrigi**, homeworld **Gamma Cephei**,
plus 1 AI "Computer"), map shape Sphere, **28 stars**, distance 7 ly, size 0 %, resources 0 %, initial treasury
50,000, 1 colony, 0 techs, econ/research 100 %, random encounters 100 %, AI Normal, strategic turn unlimited,
combat turn 240 s. Game name string inside the saves is `MyGameverify1`. Turn-2 planet panel: Size 10,
Resources 5,000, Infrastructure 200.00, Imperial pop 2,000,000,000, Civilian 520,000,000, Industrial Output 16,818,
Income 239,189, Imperial Savings 289,688 (turn 3: Civilian 540,000,000, IO 16,842, income 239,785, savings 532,369).
Saves live in **`C:\SOTS\SavedGames\`** (path is `SavedGames/` relative to the game's working dir; nothing is
written under Documents / AppData / registry). Naming format in the exe: `%s%s%02i.sav`; autosave names come from
string ids `SOTS_GAME_AUTOSAVE`, `SOTS_GAME_AUTOSAVEBACKUP`, `SOTS_GAME_ENDTURN_AUTOSAVE`.
`recipe/` holds the exact files used: `display.cfg`, `launch.cmd`, `lvp_icd.x86.json`, `click_helper.ps1`,
`session.log` (the game's own log), `dxvk_d3d9.log`, `run.txt` (stdout of the game). `screenshots/` has the
console captures described at the bottom.
## Recipe (reproducible)
All downloads were done on the Linux side and pushed with `scp`, installs run over SSH (`re@192.168.10.139`,
PowerShell). Hashes are sha256 prefixes.
1. **Runtime deps the GOG install does not provide** (game exited with `0xC0000135 STATUS_DLL_NOT_FOUND`):
- VC++ 2010 x86 redist `vcredist_x86.exe` (99dce3c8…): `Start-Process vcredist2010_x86.exe -ArgumentList /q,/norestart -Wait`
-> `SysWOW64\msvcr100.dll` / `msvcp100.dll` 10.00.40219.325. Imports: `MSVCR100.dll`, `MSVCP100.dll`.
- DirectX End-User Runtime June 2010 `directx_Jun2010_redist.exe` (053f76dc…): extract with `/Q /T:C:\Users\re\dxredist`
then `DXSETUP.exe /silent`. Needed for `d3dx9_42.dll` (import) **and** the legacy shader compiler: the game's
`.fx` effects compile `vs_1_1`/`ps_1_1` and D3DX 42 falls back to `d3dx9_31.dll` for those. With only
`d3dx9_42.dll` + `d3dcompiler_42.dll` app-local, every effect failed (`Warning: [Render\SimplePainter2.fx]
Failed to create D3DXEffect`) and the game crashed with "Mars: Application error encountered" writing `C:\SOTS\Mars.dmp`.
The full redist fixed it. (Copies of `d3dx9_42.dll` f8b9cfab… and `d3dcompiler_42.dll` 6c976311… are also in `C:\SOTS`; harmless.)
2. **Vulkan loader**: LunarG `vulkan-runtime.exe` (58a9edee…, SDK 1.4.357) installed with `/S` ->
`SysWOW64\vulkan-1.dll` + `System32\vulkan-1.dll` (and `vulkaninfo.exe`). Nothing on the VM provided a loader before.
3. **CPU Vulkan ICD**: mesa-dist-win 26.2.0 `mesa3d-26.2.0-release-msvc.7z` (dcb2719e…) -> `x86\vulkan_lvp.dll`
(f3234859…, 48 MB) + `x86\lvp_icd.x86.json` copied to `C:\SOTS\lvp\`. Registered system-wide for 32-bit:
`HKLM\SOFTWARE\WOW6432Node\Khronos\Vulkan\Drivers`, DWORD value named `C:\SOTS\lvp\lvp_icd.x86.json` = 0.
Verified: `C:\Windows\SysWOW64\vulkaninfo.exe --summary` shows `GPU0 llvmpipe (LLVM 22.1.8, 256 bits)`, Vulkan 1.4.354, `PHYSICAL_DEVICE_TYPE_CPU`.
`launch.cmd` additionally sets `VK_DRIVER_FILES`/`VK_ICD_FILENAMES` to the same json (belt and braces).
4. **DXVK**: `dxvk-3.1.tar.gz` (30f9cc32…) -> `x32/d3d9.dll` (2efb4c00…, 7.5 MB) copied to `C:\SOTS\d3d9.dll`.
No `dxvk.conf` needed. DXVK log confirms: `Found device: llvmpipe … D3D9: Hiding actual GPU, reporting AMD Radeon RX 6700 XT`;
game log then says `Adapter: 0: AMD Radeon RX 6700 XT, Vertex/Pixel Shader 3.0, 3055 MB texture mem`.
5. **Windowed 1024x768**: the video settings file is **`C:\SOTS\display.cfg`** (NOT `sots.ini`/`sots.cfg`, and not the
registry — those strings are for other things). Format is one `key value` per line, keys matched with `_stricmp`,
written back with `fprintf("%s %d")` by the Options screen. Keys (from the exe): `adapter width height
fullscreenWidth fullscreenHeight refresh windowed objectDetail shaderLevel textureQuality filtering antialiasing
maxLights numBackgroundStarsModifier(%f) specularEnabled badgesEnabled decalsEnabled brightness(%.2f)
particleLODScale(%.2f)`. File used is in `recipe/display.cfg` (`windowed 1`, `width 1024`, `height 768`, low detail).
The file does not exist until Options are saved, so the first run was fullscreen (also worked, but any focus
change minimizes an exclusive-fullscreen D3D9 window, which broke UI automation).
6. **Launch as the interactive user** (SSH sessions have no desktop): `recipe/launch.cmd` (cd `C:\SOTS`, sets
`DXVK_LOG_LEVEL=info`, `DXVK_LOG_PATH=C:\SOTS\dxvklog`, `VK_DRIVER_FILES`, runs the exe with stdout to
`dxvklog\run.txt`), started through a scheduled task:
`schtasks /Create /TN SOTS /TR "cmd.exe /c C:\SOTS\launch.cmd" /SC ONCE /ST 00:00 /RL HIGHEST /IT /F` then
`schtasks /Run /TN SOTS`. (`Register-ScheduledTask` kept rejecting the XML; `schtasks /IT` just works.)
Intro movies (Bink) render fine; skip with 3x `qm sendkey 140 esc`. Main menu in ~30 s. Process sits at
~100-220 % CPU (llvmpipe), ~350-400 MB RSS. Game runs silent: `Failed to initialize DirectSound manager` (no audio device) — harmless.
7. **UI automation** (`recipe/click_helper.ps1`): a second interactive scheduled task `SOTSUI`
(`powershell -WindowStyle Hidden -File C:\SOTS\ui\click_helper.ps1`, also `/IT /RL HIGHEST`) reads
`C:\SOTS\ui\cmd.txt` lines `click X Y | move X Y | key <SendKeys> | type <text> | sleep ms | fg` and drives
`SetCursorPos` + `mouse_event`, logging the game window rect to `C:\SOTS\ui\log.txt`. Screen coordinates map 1:1 to
the QEMU screendump (window rect -8,-39..1032,749, client 1024x749 at 0,-8 — the game still hit-tests fine).
Screenshots: `ssh spicy 'echo "screendump /tmp/x.ppm" | qm monitor 140'` + ImageMagick `convert`.
QEMU `mouse_move`/`mouse_button` did NOT register in the game (no USB tablet in the VM config); `SendKeys` text
input works but **Backspace/Esc are ignored** by the game's text fields (hence the concatenated game/save names).
The in-game menu (Save / Options / Quit) is the round button at the bottom-right of the turn bar (1000,714 at 1024x768).
Click path used: Single Player -> (forced Profile Manager: create profile `re`) -> Done -> Single Player -> Custom ->
OK -> Number of Stars slider to min (28), Players slider to min (2) -> Create Game -> name -> OK -> Launch ->
End Turn -> menu -> Save -> Save/OK/Done -> End Turn.
## What failed / gotchas
- `sots.cfg` with `windowed 1` did nothing — wrong file; `display.cfg` is the one (found via xrefs in the exe:
writer at 0x8f0e40, reader at 0x8f0f90 parsing `[esi+0x1c]=windowed`, filename string at 0xa3bed8).
- Effect-compile failure was **not** DXVK/lavapipe: identical failure under Microsoft's own `d3d9.dll`
(which, note, also gives a working SM3.0 HAL device on this VM via `d3d10warp.dll` "Microsoft Basic Render Driver";
DXVK+lavapipe was kept because it is the stack we want for later, but plain d3d9/WARP is a viable fallback).
- Crash artefacts: the engine ("Mars") pops "Application error encountered. A diagnostic file will be saved." and
writes a minidump to `C:\SOTS\Mars.dmp` — useful for later dynamic work.
- The game exe is 32-bit; imports: `IPHLPAPI d3dx9_42 KERNEL32 USER32 ADVAPI32 SHELL32 WINMM binkw32 MSVCR100
WSOCK32 MSVCP100 SHLWAPI d3d9 DSOUND GDI32 ole32`. `sots.gob`/`sots_local_en.gob` are plain ZIP archives
(8352 / 2035 entries; text data under `Data/`, shaders under `Render/*.fx`).
- Game window class names from strings (`Kerberos_SwordOfTheStars_WndCls`, `Kerberos_Mars_WndCls`) did not match
`FindWindow`; use the process `MainWindowHandle` from the interactive session.
## Screenshots (`screenshots/`)
- `01-main-menu.png` — main menu rendered via DXVK+lavapipe (first run, fullscreen 1024x768, ver.1.8.1 bottom right).
- `02-custom-game-setup.png` — Custom Game Setup with defaults (200 stars, 8 players) before adjusting.
- `03-lobby.png` — lobby: `re` (random) vs `Computer`.
- `04-strategy-map-turn1.png` — strategy map, Turn 1, Gamma Cephei panel.
- `05-turn2-after-endturn.png` — Turn 2 after the first End Turn (autosaves written at this moment).
- `06-save-dialog.png` — Save Game dialog listing the two autosaves.
- `07-game-saved.png` — "Game Successfully Saved" for the manual save.
- `08-turn3.png` — Turn 3 after the second End Turn.
State left behind: the game is still running windowed on VM140 (task `SOTS`), profile `re`, game on turn 3.
## Hash note (useful for the verifier)
sha256 (16-hex prefix): `Autosave EndTurn - turn3.sav` = `verify1.sav` = `MyGameverify1verify1.sav` = bb4fd9ac89f41e3b;
`Autosave - turn2.sav` = `Autosave Backup - turn2.sav` = ab4ac2d7e2977260; `Autosave EndTurn - turn2.sav` =
a3f9dc4b49fc669c; `Autosave - turn3.sav` = 978041acd168b56e. So: serialization is byte-deterministic for the same
game state (no timestamp/random salt in the file), the "EndTurn" autosave is the *pre*-turn state (same as a manual
save made on that turn) and `(Autosave)` is the *post*-turn state. Three distinct states are available: turn 1
(a3f9…), turn 2 (bb4f…/ab4a… — note these two differ: 66,732 vs 66,739 bytes, i.e. the manual/EndTurn save on turn 2
is not identical to the post-turn autosave of turn 2 — good diff pair), turn 3 (9780…).