Skip to content

Renderer Display and Widescreen

LordBlacksun edited this page Sep 9, 2026 · 1 revision

Renderer, Display & Widescreen

The render path is Surrender (SR) → srddraw.dll → DirectDraw7 / Direct3D7, with WinVFX providing the 2D overlay (HUD, menus, sprites). All facts below were read in the decompilation ([verified]) unless marked [open].

The device/context struct

A single global pointer holds the live renderer/device context; almost all render and HUD code dereferences it:

  • DAT_00588730 → the SR device/context struct (assigned = FUN_004C3830() during device init). [verified]
  • DAT_005E6B50 → the actual struct storage (~0x580C bytes). [verified]

Screen-geometry fields (offsets relative to DAT_00588730): [verified]

Offset Meaning Default
+0x1666 render width 0x280 = 640
+0x166A render height 0x1E0 = 480
+0x160A / +0x160E surface bit-depth / bytes-per-pixel 0x10 / 2 (16-bit)
+0x166E / +0x1686 projection X-scale / Y-scale (world→screen) set by render-init
+0x167A / +0x1692 projection X-centre / Y-centre set by render-init
+0x169E / +0x16A2 near / far clip 100.0f / 100000.0f
+0x15FA gamma from INI
+0x40,+0x54,+0x78,+0x84,+0x88 device vtable callbacks installed by SR_driver_init inside srddraw.dll (+0x40 = device render-init/buffer+viewport setup — consumes the projection, does not write it; then gamma, flip, surface-lock, render-loop)

Startup & device bring-up

Addr Role
0x004D1210 OEP / MSVC mainCRTStartup. [verified]
0x004A8B10 WinMain: "StarlancerRunning" single-instance mutex; SR_MEM init; calls the DX probe; requires DirectX ≥ 7 (if (ver < 0x700) → fatal). Parses cmdline (-credits, -greyscale, -m<n>). Reads INI [Device] → width DAT_00595D84 (def 640), height DAT_00595D94 (def 480), Windowed DAT_005D54E4, Gamma DAT_005D6080, Tdetail/Gdetail/Lmaps/Transitions. [verified]
0x004778C0 DirectX capability probe (not the real device-create): dynamically LoadLibrarys DDRAW.DLL/DINPUT.DLL, QIs DirectDraw2/4/7, probes DirectDrawCreateEx, returns an escalating version code. Confirms DDraw is dynamically loaded, not in the IAT. [verified]
0x004A8880 Display-mode enumeration — reads dmodes.bin (header 0x102, a device count, then device records of 0x144C bytes into DAT_00595DA0), and WritePrivateProfileString("Device","renderdevice%d",…). Ends by resetting width/height to 640×480. [verified]
0x004ACBE0 Real per-mode device initDAT_00588730 = FUN_004C3830(); writes clip planes and hard-codes +0x1666 = 0x280 (640) and +0x166A = 0x1E0 (480); copies the selected mode record; loads the colour-cube; sets the viewport via FUN_004C3A60(0,0,1,1,0.6,0.8); invokes the set-projection vtable (*(…+0x40))(). This is what nails the back buffer to 640×480. [verified]
0x004A8600 Apply-mode wrapper; on repeated failure falls back to a fixed 640×480 windowed mode. Persists detail/gamma to [Device]. [verified]
0x0042E9B0 In-game video-options menu — steps through the enumerated mode list and reads width/height straight from the mode table. The engine only ever offers resolutions present in dmodes.bin (no free-form entry). [verified]

Mode-table layout (base DAT_00595DA0; device stride 0x513 dwords; per-mode stride 0x12 dwords = 0x48 bytes): mode count (&DAT_00595FE8)[d*0x513]; per mode m: width (&DAT_00595FEC)[d*0x513 + m*0x12], height (&…FF0)[…], bpp (&…FF4)[…]. [verified]

Surrender (SR) core

Addr Role
0x004C3830 SR_init — allocates the device struct + ~10 scratch buffers (tagged surrenderlib). [verified]
0x004C3A60 projection scale/centre writer — from width _DAT_005E81B6 (+0x1666) and height _DAT_005E81BA (+0x166A) and 6 viewport args it writes scale_x=(w−K)·sX+0x166E, scale_y=(h−K)·sY+0x1686, centres w·0.5/h·0.5+0x167A/+0x1692, and the 5 clip planes. Args pass sX=0.6,sY=0.8 (= 480/640 ⇒ 4:3). This is the Hor+ FOV patch point, not the +0x40 callback. [verified]
0x004C9A40 texture cache (file magic 0x66, 1000 × 0xF0 descriptors); registers pixel formats ARGB8888 / ARGB4444 / RGB565. [verified]
0x004CB9D0 / 0x004CBBD0 colour-cube (CCB) load / save — 256-entry palette + LUT for the palettized blit path. [verified]
0x004BFF40 DirectDraw DDERR_* → string decoder (diagnostics). [verified]

WinVFX 2D overlay

0x004A26D0 (init_vfx): loads winvfx8.dll (8-bit) or winvfx16.dll (16-bit) per the bit-depth field, resolves ~28 VFX_* entry points, and builds the full-screen overlay pane sized directly from the dimension fields: VFX_window_construct(*(…+0x1666), *(…+0x166A)) then VFX_pane_construct(…, 0, 0, width-1, height-1). [verified] — so the 2D layer already tracks the configured resolution.

HUD coupling

0x00483150 (hud.cpp) allocates fixed 0x19000-byte flip/work buffers and derives the HUD centre from the live resolution: DAT_00566628 = width >> 1, DAT_0056662C = height >> 1. [verified] 0x004934F0 loads cockpit/radar art; 0x00494040 is the in-flight frame loop (lock-ring/target cleanup). HUD element positions resolve against the +0x1666/+0x166A + projection centre fields.

Widescreen (Hor+) — SOLVED

No true-widescreen fix existed anywhere for Starlancer — this was the project's prime original target, and it is now implemented as a static EXE patch (tools/ws_patch.py in the repo).

The mechanism (corrected RE)

The earlier [open] hypothesis — that the projection fields are written behind the DAT_00588730 + 0x40 callback — was wrong. The projection scale/centre is written directly by FUN_004C3A60 (called at the end of 0x004ACBE0/0x004AD0A0/0x004AD2E0). The +0x40 callback is SR_driver_init inside the external srddraw.dll and merely consumes the projection (it sizes buffers / sets the D3D7 viewport). FUN_004C3A60 computes, from the live width/height:

scale_x (+0x166E) = (width  − K) · sX        sX = 0.6   (0x3F19999A)
scale_y (+0x1686) = (height − K) · sY        sY = 0.8   (0x3F4CCCCD)     K = 0.1
centre_x(+0x167A) = width  · 0.5             centre_y(+0x1692) = height · 0.5

sX/sY = 0.6/0.8 = 480/640 — a baked 4:3 encoding that gives equal X/Y scale (square pixels) only at 4:3. On a wider buffer, stock scale_x ≠ scale_y ⇒ the image is horizontally stretched.

The fix (tools/ws_patch.py)

Two .text-slack code-caves (size unchanged, 61 bytes total; verified by capstone disassembly; the exe is never executed):

  1. Hor+ FOV — cave at the entry of FUN_004C3A60 forces sX := sY · height / width, so scale_x == scale_y (square pixels) and the X clip-planes (derived from the same value) widen to match. Vertical FOV is preserved; horizontal FOV widens with the aspect — textbook Hor+. At any 4:3 resolution it recomputes to exactly 0.6byte-identical to stock (regression-safe).
  2. Force resolution — cave at 0x004ACBE0 bakes the chosen size into the flight globals DAT_005D6B2C (width) / DAT_005D6C88 (height), overriding INI/dmodes.bin. The front-end device stays 640×480, so menus/briefing pillarbox (centred, not stretched).
python tools/ws_patch.py --fov-table                              # FOV per aspect
python tools/ws_patch.py --width 1920 --height 1080 IN.exe OUT.exe
python tools/ws_patch.py --verify OUT.exe        # and --revert to restore

Representative result (vertical FOV fixed ≈ 64°): 16:10 → ~90° H, 16:9 → ~96° H, 21:9 → ~112° H.

Why the HUD survives

The core flight HUD already derives from the live resolution — 0x00483150 sets the HUD centre to width>>1,height>>1, WinVFX builds its overlay from +0x1666/+0x166A, and element positions resolve against the projection centres — so radar, reticle and gauges auto-centre on widescreen.

Deferred to v2

Native-widescreen menus/briefing (they pillarbox today; ~50–100 hard-coded 640×480 sites) and repositioning the few flight-HUD widgets that still use a 320×240 grid (0x00494040 region).

Caveats

Widths > ~1280 may hit the back/Z-buffer ceiling inside srddraw.dll (outside static view) — pair with dgVoodoo2/DDrawCompat and verify in-game. (In-game testing is the community's; the project builds and verifies statically.)

Clone this wiki locally