-
Notifications
You must be signed in to change notification settings - Fork 0
Renderer Display and 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].
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) |
| 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 init — DAT_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]
| 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] |
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.
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.
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 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.
Two .text-slack code-caves (size unchanged, 61 bytes total; verified by capstone disassembly; the
exe is never executed):
-
Hor+ FOV — cave at the entry of
FUN_004C3A60forcessX := sY · height / width, soscale_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 exactly0.6⇒ byte-identical to stock (regression-safe). -
Force resolution — cave at
0x004ACBE0bakes the chosen size into the flight globalsDAT_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 restoreRepresentative result (vertical FOV fixed ≈ 64°): 16:10 → ~90° H, 16:9 → ~96° H, 21:9 → ~112° H.
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.
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).
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.)