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

11 KiB

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…).