-
Notifications
You must be signed in to change notification settings - Fork 0
Games
Titles we have taken on, and what was measured on each. Every row comes from an installed copy rather than from memory; where a claim comes from a static scan rather than from playing the game, it says so.
Tested on an M4 Max, macOS 27, CrossOver 26.2 patched with winevideo, GPTK 4.0b2.
| Cutscenes | 61 × VP9 in .mp4, inside pakchunk0-Windows.pak and loose |
| Played by | Electra, decoding VP9 with its own libvpx |
| Symptom |
EXCEPTION_ACCESS_VIOLATION reading 0x0 in FElectraMediaDecoderOutputBufferPoolBlock_DX12::AllocateBuffer
|
| Fix | Runtime patch — 4 patch sites, all four confirmed at runtime |
Both modes work. The runtime patch leaves the original VP9 files untouched; re-encoding is the fallback.
| Cutscenes | 355 .webm, VP9 Profile 0. 42 of them 2560x1440 and up to four minutes; the rest 960x540 interface clips |
| Played by |
IMFSourceReader on a separate D3D11 device, presented by a D3D12 renderer |
| Was | Cutscene ran with sound and subtitles, picture black |
| Fix | An injected DLL that supplies the video interfaces, decodes in software, and bridges the frame into D3D12 itself |
Five things were wrong, in sequence, and each had to be cleared before the next became visible.
1. ID3D11VideoDevice and ID3D11VideoContext return E_NOINTERFACE.
The player refuses to start without both. With CX_GRAPHICS_BACKEND=d3dmetal,
CrossOver puts Apple's GPTK ahead of Wine, and GPTK's 136 KB d3d11.dll is a
shim over D3DMetal carrying neither — while Wine's own 425 KB one implements
ID3D11VideoDevice1 with 24 real methods the game never sees. Answering the
two queries with stub objects is enough; of the eighty-odd methods on those
interfaces the game calls almost none.
2. MF_SOURCE_READER_D3D_MANAGER. The game asks the reader to decode into
D3D video textures, which D3DMetal cannot produce. Dropping that attribute
gives software decoding, which winegstreamer handles without complaint.
3. The video processor. The player walks the rate conversion caps looking
for one bit — ProcessorCaps bit 1, DEINTERLACE_BOB — and returns E_FAIL
when no entry has it. Claiming it is honest: bob deinterlacing a progressive
frame is a copy.
4. Samples must be D3D-backed. The executable references IMFDXGIBuffer
and ID3D11Texture2D and neither IMF2DBuffer nor IMFMediaBuffer, so step
2 removed the very thing its presentation path needs. Each decoded frame is
converted from NV12 to BGRA and uploaded into a texture, and the buffer's
QueryInterface is answered with an IMFDXGIBuffer that hands it over.
5. IDXGIResource::GetSharedHandle returns E_NOTIMPL. The game shares
its video texture with its D3D12 renderer by handle, and D3DMetal has no way
to make one: the legacy call refuses and IDXGIResource1, whose
CreateSharedHandle replaces it, is absent.
That last one looked like the end of the road, and it was not. winevideo had
already met the same wall for D3D9 under DXMT and gone around it — see
0008-d3d9-dxmt-video-bridge-handle.patch — by manufacturing the handle
itself and owning the far side. The same shape works here:
-
GetSharedHandlehands back a handle of ours instead of the refusal. - The game carries it to
ID3D12Device::OpenSharedHandleand asks for anID3D12Resource. - That call returns a BGRA texture created on the game's own D3D12 device.
- Each converted frame is re-pitched into an upload buffer, copied with
CopyTextureRegionon a copy queue, and waited on with a fence.
Reaching the D3D12 device took one more step than expected: the game ships
NVIDIA Streamline and asks sl.interposer.dll for D3D12CreateDevice by
name, so an import-table hook on d3d12.dll — where it also imports the
function, by ordinal 101 — is installed correctly and never called. Returning
a wrapper from GetProcAddress catches it whichever module it is asked of.
A copy queue rather than a direct one: resources sit in COMMON, are promoted
implicitly for the copy and decay back afterwards, so no barriers are needed
and nothing is assumed about the state the renderer expects. The fence wait is
not caution — winevideo needed the same, and letting the frame go early shows
the previous one or nothing.
Caveats. The NV12 to BGRA conversion is scalar over 3.7 million pixels a frame and the fence wait is synchronous, so frame pacing suffers; both are addressable and neither is load-bearing. The player's four abort branches are still patched out, which was necessary before the bridge existed and probably is not now. And this is memory patching: never use it on a game with anti-cheat.
Method, since most of it was wrong turns. Eight hypotheses were tested and
discarded before the disassembler was used properly: a missing codec
registration, the absent audio track, the container duration, a missing pixel
aspect ratio and interlace mode — setting those actively broke the negotiation
and had to be reverted — NV12 texture creation, and the shared texture itself.
Two conclusions were also wrong and had to be withdrawn, the second of them
"this is blocked upstream and cannot be fixed from here". What worked was
reading the code rather than guessing at it: find-callsites.py locating the
player statically by resolving call qword ptr [rip+disp] against the import
table, following its branches in a disassembler, and answering interface
queries with stubs that log — so the game said what it wanted next instead of
being guessed at. Writing solid magenta into the texture settled in one run
what several rounds of reasoning had not.
This page covers titles we have deliberately taken on, not everything that happens to be installed on someone's machine. A game gets a row when there is a reason to work on it.
To add one: run the survey on that game's folder, and if it misbehaves, the
probe. Both are in diagnostics/, and both are described in
Diagnosing a new game. Paste what they print —
measurements are the point of this page, and a row without one is worse than no
row.