The DirectX runtime we never shipped
A tester ran 4.12.233 under Proton and it died on first mission load. The cause turned out to name itself, and to be a Windows bug we have been shipping the whole time. Wine implements d3dx9_43 itself, and 63 of its 329 exports are stubs. The game imports twenty of them and exactly one is stubbed: D3DXConcatenateMeshes, called once per session from ConsolidateStaticObjects on the first-mission-load latch - so every player hits it on their first race. Wine raised EXCEPTION_WINE_STUB naming the function in plain text, and our own crash filter caught it and wrote a minidump, which is how a self-describing failure arrived as a mystery. The fix is one file, and it was overdue on Windows. d3dx9_43.dll is a HARD import of the exe - only steam_api.dll is delay-loaded - and it is not part of Windows. It ships with the June 2010 DirectX End-User Runtime and nothing else. Any customer on a clean Windows 10 or 11 who never installed that redistributable cannot start the game at all: the loader fails at 0xC0000135 before WinMain, no window, no log line. Every machine this has been built or tested on had the SDK or some older game installed, which is precisely why nobody has ever seen it. pack-dist now extracts the DLL from the SDK's redist cab into dist. Wine prefers an application-directory DLL over its builtin, so the same file closes the Windows failure and the Linux blocker together, and it is verified working on a stock prefix with nothing installed. The SDK terms nominate DXSETUP as the delivery method for this redistributable; the loose DLL is what is verified and is near-universal practice, but that wants confirming before a public release. /LARGEADDRESSAWARE, because the same session reported VmSize 3193MB and an older Wine that died before video init with the address space exhausted. The game is not using that memory: measured here, a standalone mission peaks at 166MB committed against 795MB of address space. The gap is reservations, mapped files, and mostly the graphics driver mapping VRAM apertures into our process - under wined3d's OpenGL path that grows several times over. For a 32-bit process the 2GB address ceiling is a hard limit with nothing to do with RAM, so it is entirely possible to run out of addresses while using 170MB. The flag raises it to 4GB, frees nothing, and changes no behaviour. Worth having on Windows too: 795MB of 2GB is already 40% before 1080p, eight pods, and the 100MB buffer RP412RECORDSIZE takes when recording is armed. And two diagnostics so the next one costs a log line instead of an investigation. The crash filter now decodes 0x80000100 and writes the offending module.function before the minidump; startup probes wine_get_version in ntdll and logs the platform beside the version banner. Neither can appear on Windows - verified: the smoke run's log has the version line and no platform line. Not fixed here, deliberately: the durable version of this is to write the mesh merge by hand, since D3DXSimplifyMesh and D3DXSplitMesh are stubs too and any future mesh work hits the same wall. And the field test ran wined3d, not DXVK, so the child-window Present with GDI panes clipped over it - the risk the assessment led with - is still untested. The doc records both, along with the prediction it got wrong: the windowsapp.lib import was called a load-time failure risk under Wine, and it simply is not one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,15 @@ Committed under [lib/](lib/), no install needed: `OpenAL32.lib`, `libsndfile-1.l
|
||||
`assets/RP411/oalinst.exe` — or beside the exe) and `libsndfile-1.dll` (beside the
|
||||
exe; a copy ships in `assets/RP411/`).
|
||||
|
||||
It also needs **`d3dx9_43.dll`**, which is a hard import and is *not* part of
|
||||
Windows — it ships only with the June 2010 DirectX End-User Runtime. A dev box
|
||||
with the SDK installed has it system-wide and never notices; a clean Windows
|
||||
10/11 fails to start the exe at all (0xC0000135, before `WinMain`).
|
||||
[pack-dist.ps1](pack-dist.ps1) therefore extracts it from the SDK's
|
||||
`Redist\Jun2010_d3dx9_43_x86.cab` into `dist\`. That same file is also what
|
||||
makes the game run under Wine/Proton, whose builtin `d3dx9_43` stubs
|
||||
`D3DXConcatenateMeshes` — see [docs/RP412-LINUX.md](docs/RP412-LINUX.md).
|
||||
|
||||
## 2. Building
|
||||
|
||||
```powershell
|
||||
|
||||
@@ -83,6 +83,49 @@ HWND hWnd;
|
||||
//
|
||||
static LONG WINAPI RPL4CrashDumpFilter(EXCEPTION_POINTERS *exception)
|
||||
{
|
||||
//
|
||||
// Wine raises 0x80000100 (EXCEPTION_WINE_STUB) when the game calls an
|
||||
// API Wine declares but has never implemented, and puts the module and
|
||||
// function names in the first two exception parameters as plain ANSI
|
||||
// strings. Wine prints that to its own stderr - which a player running
|
||||
// through Steam never sees - and then this filter caught the exception
|
||||
// and wrote a minidump, so a perfectly self-describing failure arrived
|
||||
// as a mystery crash. That is exactly how D3DXConcatenateMeshes cost a
|
||||
// full investigation on 4.12.233: one stubbed function out of the
|
||||
// twenty d3dx9 imports, and nothing in our log named it.
|
||||
//
|
||||
// Unlike an access violation this is an orderly raise from Wine with
|
||||
// the heap intact, so the log stream is safe to use here. Written
|
||||
// before the dump, in case the dump is what fails.
|
||||
//
|
||||
if (exception != NULL &&
|
||||
exception->ExceptionRecord != NULL &&
|
||||
exception->ExceptionRecord->ExceptionCode == 0x80000100 &&
|
||||
exception->ExceptionRecord->NumberParameters >= 2)
|
||||
{
|
||||
const char *stub_module =
|
||||
(const char *) exception->ExceptionRecord->ExceptionInformation[0];
|
||||
const char *stub_function =
|
||||
(const char *) exception->ExceptionRecord->ExceptionInformation[1];
|
||||
|
||||
if (stub_module != NULL && stub_function != NULL &&
|
||||
!IsBadStringPtrA(stub_module, 128) &&
|
||||
!IsBadStringPtrA(stub_function, 128))
|
||||
{
|
||||
DEBUG_STREAM << "\n*** WINE STUB: " << stub_module << "."
|
||||
<< stub_function << " is not implemented by this Wine.\n"
|
||||
<< "*** The game called an API Wine only declares. Shipping the"
|
||||
<< " real DLL beside the exe overrides Wine's builtin and is"
|
||||
<< " usually the whole fix; see docs/RP412-LINUX.md.\n"
|
||||
<< std::flush;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_STREAM << "\n*** WINE STUB: an unimplemented API was called"
|
||||
<< " (names unreadable)\n" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
HMODULE dbghelp = LoadLibraryA("dbghelp.dll");
|
||||
if (dbghelp != NULL)
|
||||
{
|
||||
@@ -225,6 +268,34 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
|
||||
//
|
||||
DEBUG_STREAM << "Red Planet " << RP412_VERSION_LONG << std::endl << std::flush;
|
||||
|
||||
//
|
||||
// And whether this is Windows at all.
|
||||
//
|
||||
// Wine exports wine_get_version from ntdll and Windows does not, so
|
||||
// one GetProcAddress separates the two. It matters because the
|
||||
// failure modes differ: Wine implements Direct3D and the D3DX helpers
|
||||
// itself, and where a helper is only a stub the game dies at the call
|
||||
// with no hint in its own log (see the filter above). A tester's log
|
||||
// saying which platform it came from turns that from an investigation
|
||||
// into a lookup. Never true on Windows, where the lookup fails and
|
||||
// nothing is printed.
|
||||
//
|
||||
{
|
||||
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
|
||||
if (ntdll != NULL)
|
||||
{
|
||||
typedef const char * (__cdecl *WineGetVersionFn)(void);
|
||||
WineGetVersionFn wine_get_version =
|
||||
(WineGetVersionFn) GetProcAddress(ntdll, "wine_get_version");
|
||||
if (wine_get_version != NULL)
|
||||
{
|
||||
DEBUG_STREAM << "Platform: Wine " << wine_get_version()
|
||||
<< " (not Windows) - D3DX comes from Wine's own"
|
||||
<< " implementation\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// load up our environment variables
|
||||
//controls
|
||||
if(getenv("L4CONTROLS") == NULL)
|
||||
|
||||
@@ -77,6 +77,23 @@
|
||||
helper. -->
|
||||
<DelayLoadDLLs>steam_api.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
|
||||
<RandomizedBaseAddress>false</RandomizedBaseAddress>
|
||||
<!-- /LARGEADDRESSAWARE: a 32-bit process gets 2GB of user ADDRESS
|
||||
SPACE by default, which is a separate and much scarcer resource
|
||||
than memory. Measured standalone mission: 166MB committed
|
||||
against 795MB of address space - the graphics driver maps VRAM
|
||||
apertures and its own bookkeeping into our map, and managed-pool
|
||||
resources are shadowed in system memory as well as VRAM, so the
|
||||
map runs about five times the memory actually used. Under Wine
|
||||
the same mission mapped 3193MB, because wined3d routes through
|
||||
OpenGL and the NVIDIA driver maps far more aggressively; on an
|
||||
older Wine that exhausted the space outright and the game died
|
||||
before video init with nothing useful in the log. This raises
|
||||
the ceiling to 4GB on any 64-bit host. It frees no memory and
|
||||
changes no behaviour - it stops the process running out of
|
||||
numbers. Equally worth having on Windows: 795MB of 2GB is
|
||||
already 40% before 1080p, eight pods, and the 100MB spool
|
||||
buffer RP412RECORDSIZE takes when recording is armed. -->
|
||||
<LargeAddressAware>true</LargeAddressAware>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
<!-- rpl4build.h is generated, not committed: the patch number is the
|
||||
|
||||
+119
-11
@@ -1,12 +1,15 @@
|
||||
# RP 4.12 on Linux — compatibility assessment
|
||||
|
||||
Requirements exploration for making RP412 playable on Linux (desktop and Steam
|
||||
Deck). Status: assessment complete (2026-08-12), **parked until late
|
||||
playtesting** — nothing has been run on Linux yet; the Proton test matrix in
|
||||
§3 is the next concrete step when this resumes. A formatted copy of this
|
||||
report lives at
|
||||
Deck). A formatted copy of the original assessment lives at
|
||||
<https://claude.ai/code/artifact/47e4cf5f-a9a3-416e-bd5e-25c810983551>.
|
||||
|
||||
**Status: it runs.** The assessment below was written 2026-08-12 as prediction;
|
||||
on 2026-08-14 a field test on real hardware found and cleared the one blocker,
|
||||
and the game played a full mission on Linux. §0 is what the test found and what
|
||||
was done about it — read it first, it corrects several predictions in §2. The
|
||||
remaining work is the Deck-specific and DXVK items in §0's open list.
|
||||
|
||||
**Verdict: target Proton as the official Linux path; treat a native port as a
|
||||
separately-scoped future project.** The existing 32-bit Win32/D3D9 build is
|
||||
squarely on Proton's best-supported path, every Proton-blocking issue found is
|
||||
@@ -25,6 +28,107 @@ partner docs, the DXVK source, the steam-runtime repository, and Wine release
|
||||
notes as of August 2026, with unconfirmed items called out as needing live
|
||||
tests.
|
||||
|
||||
## 0. Field test, 2026-08-14 — one stubbed function, and a Windows bug behind it
|
||||
|
||||
Test environment: Linux Mint 22.3, RTX 5070 Ti (driver 595.84), Wine 10.0
|
||||
Staging from GE-Proton10-21, 32-bit prefix, **wined3d** renderer, windowed
|
||||
1920×1080, symbols from the shipped `rpl4opt.pdb`. Build 4.12.233.
|
||||
|
||||
**The blocker was `D3DXConcatenateMeshes`.** Wine has never implemented it.
|
||||
The game calls it exactly once per session, from `ConsolidateStaticObjects`
|
||||
([MUNGA_L4/L4VIDEO.cpp](../MUNGA_L4/L4VIDEO.cpp)) via
|
||||
[MUNGA/APP.cpp](../MUNGA/APP.cpp)'s first-mission-load latch, so every player
|
||||
hits it on their first race. Wine raised `EXCEPTION_WINE_STUB` (0x80000100)
|
||||
naming the function in plain text; our own crash filter caught it and wrote
|
||||
`rpl4crash.dmp`, so it presented as a mystery crash. Of Wine's 329 `d3dx9_43`
|
||||
exports, 63 are stubs; the game imports 20 of them; exactly one is stubbed.
|
||||
There is no second landmine on the current code path.
|
||||
|
||||
**The fix was already a Windows bug.** `d3dx9_43.dll` is a *hard* import of
|
||||
`rpl4opt.exe` — confirmed in the import table, and only `steam_api.dll` is
|
||||
delay-loaded — and it is **not part of Windows**. It ships solely with the
|
||||
June 2010 DirectX End-User Runtime. A customer on a clean Windows 10/11 who
|
||||
has never installed that redistributable cannot start the game at all: the
|
||||
loader fails at 0xC0000135 before `WinMain`, with no window and no log line.
|
||||
Every machine this was built or tested on had the SDK or some older game
|
||||
installed, which is why it went unnoticed. `pack-dist.ps1` now ships the DLL
|
||||
beside the exe, extracted from the SDK's redistributable cab. Wine prefers an
|
||||
application-directory DLL over its own builtin, so the same one file closes
|
||||
the Windows failure and the Linux blocker together — verified working on a
|
||||
stock prefix with no overrides and nothing installed.
|
||||
|
||||
> **Licensing:** the DirectX SDK terms nominate DXSETUP as the delivery
|
||||
> method for this redistributable. Shipping the loose DLL is near-universal
|
||||
> practice and is what is verified working on both platforms, but confirm the
|
||||
> terms before a public release.
|
||||
|
||||
**Address space, not memory.** The 32-bit process mapped **VmSize 3193 MB**
|
||||
under Wine, and on an older Wine that exhausted the address space outright —
|
||||
the game died before video init with nothing useful logged. It is not using
|
||||
that much memory: measured on Windows, a standalone mission peaks at **166 MB
|
||||
committed / 179 MB working set against 795 MB of address space**, already a
|
||||
~4.8× ratio. Address space is inflated by reservations, file mappings, and
|
||||
above all the graphics driver mapping VRAM apertures and its own bookkeeping
|
||||
into the process; under wined3d's OpenGL path with an NVIDIA driver that grows
|
||||
several-fold again. For a 32-bit process this is a hard 2 GB ceiling entirely
|
||||
separate from RAM, so it is possible to run out of *addresses* while using
|
||||
170 MB. `/LARGEADDRESSAWARE` is now set: it raises the ceiling to 4 GB on any
|
||||
64-bit host, frees no memory and changes no behaviour. Worth having on Windows
|
||||
too — 795 MB of 2 GB is already 40% before 1080p, eight pods, and the 100 MB
|
||||
buffer `RP412RECORDSIZE` takes when recording is armed.
|
||||
|
||||
**Diagnostics added, so the next gap names itself.** The crash filter now
|
||||
decodes 0x80000100 and writes the offending `module.function` to `rpl4.log`
|
||||
before the minidump, and startup probes `wine_get_version` in `ntdll` and logs
|
||||
the platform next to the version banner. Neither line can ever appear on
|
||||
Windows. Together they would have reduced this entire investigation to reading
|
||||
one line of a tester's log.
|
||||
|
||||
**Do NOT install the VC++ redistributable into a Wine prefix.** It *causes* a
|
||||
crash — a null dereference inside `std::mutex`, reached from
|
||||
`KeyLight_SetLogger` via `PadRIO::PadRIO`. Wine's builtin C++ runtime works
|
||||
correctly. This is a support-desk trap worth knowing, because installing the
|
||||
redistributable is a common first instinct. (On real Windows the VC++ runtime
|
||||
is genuinely required; Steam installs it as a depot prerequisite.)
|
||||
|
||||
### Predictions in §2 that the test corrected
|
||||
|
||||
- **The `windowsapp.lib` static-import worry was wrong.** §2 called it a
|
||||
load-time failure risk under Wine. The game boots, and the VC++ trace above
|
||||
runs *through* `KeyLight_SetLogger` — that code executes. Wine satisfies the
|
||||
WinRT API-set imports.
|
||||
- **`d3dx9` was the right area, the wrong function.** §2 named
|
||||
`D3DXLoadMeshFromXA` and `D3DXCreateTextureFromFile` as the heaviest leans on
|
||||
Wine's reimplementation. Both are fully implemented. The lesson is method:
|
||||
rank by *what Wine stubs*, not by what the game uses most — intersecting the
|
||||
import table against Wine's stub list yields the answer directly.
|
||||
- **Address-space exhaustion was not predicted at all**, and static analysis
|
||||
would not have found it.
|
||||
|
||||
### Still open after this test
|
||||
|
||||
- **DXVK is untested.** The test ran wined3d; Proton defaults to DXVK for
|
||||
D3D9. The §2 headline risk — `Present` into a child `STATIC` via
|
||||
`hDestWindowOverride` with GDI panes clipped over it — is specifically a
|
||||
DXVK question, and whether the run exercised cockpit split mode
|
||||
(`L4MFDSPLIT=1`) at all is not recorded. **This is the next test**, and the
|
||||
rig now exists to do it cheaply.
|
||||
- **Steam networking under Proton** — SDR/FakeIP through the lsteamclient
|
||||
bridge. The test's networking was almost certainly plain TCP.
|
||||
- **Everything Deck-specific**: gamescope with the plasma window and the
|
||||
exploded MFD view, the gamepad-complete front end, the 9 px font floor.
|
||||
- **Optional load-time polish, both platforms**: 303 `d3dx_load_pixels_from_pixels`
|
||||
calls (texture format conversion at load — shipping textures in device format
|
||||
skips it) and 150 `OptimizeInplace` calls (the `.X` loader; baking meshes to
|
||||
an in-house format at build time would retire the `.X` dependency).
|
||||
- **The durable fix for the blocker**: replace `D3DXConcatenateMeshes` with a
|
||||
hand-written mesh merge, ~60–80 lines using only helpers Wine implements
|
||||
(`D3DXCreateMesh`, `D3DXGetDeclVertexSize`, `D3DXVec3TransformCoordArray`,
|
||||
`D3DXMatrixInverse`, …). Worth doing eventually because `D3DXSimplifyMesh`
|
||||
and `D3DXSplitMesh` are stubs too, so any future mesh work hits the same
|
||||
wall. Verify by comparing vertex/face counts and attribute ranges against
|
||||
the D3DX output on Windows.
|
||||
|
||||
## 1. How the Windows dependency actually distributes
|
||||
|
||||
The architecture is far more portable than the mechanics. The engine is
|
||||
@@ -94,28 +198,32 @@ The multiplayer test matrix should pin that as the minimum version.
|
||||
|
||||
### Fix in the Windows build (confirmed in code)
|
||||
|
||||
> Written 2026-08-12 as prediction. The first row is **disproven** by the
|
||||
> field test and is kept only so the reasoning is on the record; rows 2–5
|
||||
> stand and remain Deck work. See §0.
|
||||
|
||||
| Item | Evidence | Candidate fix |
|
||||
|------|----------|---------------|
|
||||
| **WinRT lamp mirror is a static import.** `windowsapp.lib` via `#pragma comment` — unlike `steam_api.dll`, not delay-loaded. Wine has no `windows.devices.lights` at all; the in-code try/catch guards only run if the image loads. | [MUNGA_L4/L4KEYLIGHT.cpp](../MUNGA_L4/L4KEYLIGHT.cpp) line 33 | Delay-load it the way `steam_api.dll` is handled, or move activation behind a `LoadLibrary` probe. Cheap, and hardens the Windows build too. |
|
||||
| ~~**WinRT lamp mirror is a static import.**~~ **WRONG — see §0.** The reasoning was that `windowsapp.lib` is not delay-loaded and Wine has no `windows.devices.lights`, so the image would fail to load before the in-code guards could run. In practice Wine satisfies the WinRT API-set imports and the game boots; `KeyLight` code demonstrably executes. | [MUNGA_L4/L4KEYLIGHT.cpp](../MUNGA_L4/L4KEYLIGHT.cpp) line 33 | None needed. |
|
||||
| **Second top-level window fights gamescope.** The "Plasma Display" window plus the exploded `L4MFDSPLIT=2` mode (6 top-level panes) hit gamescope's known multi-window weakness on Deck. | [MUNGA_L4/L4PLASMASCREEN.cpp](../MUNGA_L4/L4PLASMASCREEN.cpp) line 271, [MUNGA_L4/L4MFDVIEW.cpp](../MUNGA_L4/L4MFDVIEW.cpp) line 297 | On Deck/gamescope, default the plasma glass into the cockpit window (the `SetParent` path already exists at `L4PLASMASCREEN.cpp:91`) and treat exploded view as desktop-only. |
|
||||
| **Implicit hit-test transparency.** Clicks fall through the viewscreen only because the stock STATIC proc returns `HTTRANSPARENT`; no explicit `WM_NCHITTEST` handler exists. If Wine's STATIC differs, all mouse input over the 3D view dies silently. | [MUNGA_L4/L4VB16.cpp](../MUNGA_L4/L4VB16.cpp) line 4659 | Handle `WM_NCHITTEST` explicitly in the viewscreen subclass. Removes the dependency on an undocumented control behavior everywhere. |
|
||||
| **Deck legibility floor.** The 1920×1080 canvas lands on Deck at 1280×720 — a 1.5× shrink. Valve's gate is 9 px minimum font height, so anything under ~14 px on the canvas fails. | Fixed canvas: [MUNGA_L4/L4APP.cpp](../MUNGA_L4/L4APP.cpp) line 269 | Audit the smallest cockpit and front-end type; bump or provide a Deck-scale preset. |
|
||||
| **Gamepad-complete front end.** The menu/lobby is mouse-driven GDI; callsign entry is a Win32 `EDIT` control. Deck Verified requires the full flow on pad alone, with the Steam on-screen keyboard for text. | [RP_L4/RPL4FE.cpp](../RP_L4/RPL4FE.cpp) line 2467 | Add pad navigation to the front end and invoke `ShowFloatingGamepadTextInput` for the callsign field. The largest Path-A work item. |
|
||||
|
||||
### Verify under Proton (needs live testing; no code change assumed)
|
||||
### Verify under Proton (status as of the 2026-08-14 field test)
|
||||
|
||||
- **Child-window Present**: `Present(…, hDestWindowOverride)` into a child
|
||||
STATIC under DXVK — supported per the DXVK source (per-HWND presenter map)
|
||||
and Wine 10's child-window Vulkan work, but this exact composition (GDI
|
||||
siblings clipped over the presented pane) is the least-exercised path in any
|
||||
D3D9 stack. First thing to smoke-test.
|
||||
D3D9 stack. **STILL OPEN — the field test ran wined3d, not DXVK.** Next test.
|
||||
- **Steam networking end-to-end**: repeat the three-machine SDR/FakeIP race
|
||||
([STEAM-3-MACHINE-TEST.md](STEAM-3-MACHINE-TEST.md)) with one or more peers
|
||||
on Proton ≥ 10.0-4b. No Proton-specific FakeIP defects are on record, but
|
||||
absence of reports is not confirmation.
|
||||
- **Wine's d3dx9**: `D3DXLoadMeshFromXA` (.x meshes) and
|
||||
`D3DXCreateTextureFromFile` (PNG) are the two heaviest leans on Wine's
|
||||
reimplementation.
|
||||
absence of reports is not confirmation. **STILL OPEN.**
|
||||
- ~~**Wine's d3dx9**: `D3DXLoadMeshFromXA` and `D3DXCreateTextureFromFile`~~ —
|
||||
**DONE, and both are fine.** The stubbed import was `D3DXConcatenateMeshes`;
|
||||
see §0.
|
||||
- **Input odds and ends**: the `IG_` RawInput device-path heuristic for XInput
|
||||
de-duplication (`L4JOY.cpp:72-146`) assumes Windows-shaped HID paths;
|
||||
`GetAsyncKeyState` + foreground-window focus gating under gamescope's focus
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
# TEST.EGG) - but not the arcade launch scripts or the old 4.10 exe
|
||||
# - libsndfile-1.dll beside the exe; OpenAL32.dll copied from the system
|
||||
# when installed, with oalinst.exe included as the fallback installer
|
||||
# - d3dx9_43.dll from the DirectX SDK redist cab: a hard import that is
|
||||
# NOT part of Windows, and also what makes the game run under Proton
|
||||
# (environ.ini is NOT shipped - the exe writes it on first run)
|
||||
# - start/joyconfig scripts, HANDBOOK.html, VTV-PRESETS.html, TRACKS.html,
|
||||
# CONTROLS.txt and a README
|
||||
@@ -192,6 +194,53 @@ $handbookPage
|
||||
"$dist\TRACKS.html",
|
||||
[System.IO.File]::ReadAllBytes((Join-Path $root 'docs\tracks.html')))
|
||||
|
||||
# --- D3DX runtime ----------------------------------------------------------
|
||||
# d3dx9_43.dll is a HARD import of the exe, and it is NOT part of Windows.
|
||||
# It ships only with the June 2010 DirectX End-User Runtime, so a customer
|
||||
# who has never installed that redistributable - which is most of them, on a
|
||||
# clean Windows 10 or 11 - cannot start the game at all: the loader fails at
|
||||
# 0xC0000135 before WinMain, with no window and no log line. Every machine
|
||||
# this has ever been built or tested on had the SDK or some older game
|
||||
# installed, which is exactly why it went unnoticed.
|
||||
#
|
||||
# The same file is also what makes the game work under Wine/Proton. Wine
|
||||
# implements 63 of d3dx9_43's 329 exports as stubs, and the game imports
|
||||
# exactly one of them - D3DXConcatenateMeshes, called once per session from
|
||||
# ConsolidateStaticObjects on first mission load. Wine loads a DLL from the
|
||||
# application directory in preference to its own builtin, so shipping the
|
||||
# Microsoft one here means the stub is never reached. One file closes a
|
||||
# hard Windows failure and the entire Linux blocker at once.
|
||||
#
|
||||
# Taken from the SDK's redistributable cab rather than from SysWOW64: that
|
||||
# cab is the licensed redistribution source, and it is the same signed
|
||||
# Microsoft binary. NOTE: the DirectX SDK terms nominate DXSETUP as the
|
||||
# delivery method for this redistributable - shipping the loose DLL is
|
||||
# near-universal practice and is what is verified working on both
|
||||
# platforms, but confirm the terms before a public release.
|
||||
$d3dxCab = Join-Path $env:DXSDK_DIR 'Redist\Jun2010_d3dx9_43_x86.cab'
|
||||
$d3dxDll = Join-Path $dist 'd3dx9_43.dll'
|
||||
if ($env:DXSDK_DIR -and (Test-Path $d3dxCab)) {
|
||||
& expand.exe $d3dxCab -F:d3dx9_43.dll $dist | Out-Null
|
||||
if (Test-Path $d3dxDll) {
|
||||
Write-Host " d3dx9_43.dll extracted from the DirectX SDK redist cab"
|
||||
}
|
||||
}
|
||||
if (-not (Test-Path $d3dxDll)) {
|
||||
# Fall back to an installed copy so a machine without the SDK can still
|
||||
# produce a working package.
|
||||
$d3dxSystem = "$env:WINDIR\SysWOW64\d3dx9_43.dll"
|
||||
if (-not (Test-Path $d3dxSystem)) { $d3dxSystem = "$env:WINDIR\System32\d3dx9_43.dll" }
|
||||
if (Test-Path $d3dxSystem) {
|
||||
Copy-Item $d3dxSystem $dist
|
||||
Write-Host " d3dx9_43.dll copied from $(Split-Path $d3dxSystem)"
|
||||
} else {
|
||||
Write-Warning ("d3dx9_43.dll NOT FOUND - this package will fail to start on any " +
|
||||
"machine without the June 2010 DirectX runtime, and will crash " +
|
||||
"under Wine/Proton on first mission load. Install the DirectX SDK " +
|
||||
"or the end-user runtime and repack.")
|
||||
}
|
||||
}
|
||||
|
||||
# --- OpenAL runtime --------------------------------------------------------
|
||||
# The exe links OpenAL32.dll (32-bit). Prefer shipping the already-installed
|
||||
# runtime beside the exe; oalinst.exe covers machines where that misses.
|
||||
|
||||
Reference in New Issue
Block a user