-
Notifications
You must be signed in to change notification settings - Fork 0
Kingdom Hearts
Seven titles across two packages, one fault and one fix. The first of them was the first game here that wanted the decoder's own surface rather than a picture, and the test that had settled the previous title's fault gave the opposite answer, which is what made the route clear.
| Package | Fixed | Needs nothing |
|---|---|---|
| HD 2.8 Final Chapter Prologue | Dream Drop Distance, χ Back Cover | 0.2 Birth by Sleep |
| HD 1.5+2.5 ReMIX | FINAL MIX, Re_Chain of Memories, II FINAL MIX, Birth by Sleep FINAL MIX, the Theater | — |
KINGDOM HEARTS III is not here either: it plays unaided. Both exceptions are the same case and are explained below.
Everything measured below is from Dream Drop Distance, which is where the work happened. HD 1.5+2.5 ReMIX needed no new code at all — its six video-playing executables import the same five entry points and take the same route, and the fix reached them by widening one gate from two names to the family prefix.
| Video | H.264 High, 1920×1080, 30 fps, AAC-LC audio, in MP4 |
| Played by |
IMFSourceReader from a URL; the frame is fetched as an IMFDXGIBuffer, shared into D3D12, and its two planes copied into textures the game converts in its own shader |
| Symptom | Cutscene runs with sound, picture solid green |
| Fix | Software decode, and the luma and chroma planes written straight into the game's own plane textures |
| Backend | D3DMetal, D3D11 and D3D12 |
| CrossOver |
crossover-preview-arm64-20260821. Not tried on 26.3 |
KINGDOM HEARTS HD 2.8 Final Chapter Prologue ships two games behind a launcher, and only Dream Drop Distance needed fixing.
0.2 Birth by Sleep needs no fix: its cutscenes already play. That is worth stating plainly, because the same package failing in one half and working in the other looks like an oversight otherwise.
The reason it works is the reason it cannot break the same way. Its 49
cutscenes are CriWare .usm, and the video inside them is MPEG-1 —
mpeg_codec = 1 in every VIDEO_HDRINFO, every stream opening with a
00 00 01 B3 sequence header decoding to 1920×1080, and ffprobe
independently reporting mpeg1video. CriWare decodes that in its own software
decoder, statically linked into the executable. It never asks Media Foundation
for anything and never asks D3D for a video surface, so there is nothing for
D3DMetal to refuse. The payload is encrypted past the first 0x40 bytes of each
chunk by a scheme that was not determined, and that does not matter either —
the game decrypts it itself.
A game that decodes its own video in software is the case that was never going to be a problem.
HD 1.5+2.5 ReMIX ships four games, a Theater and a launcher, and all six
executables that play video import exactly the same five entry points as
Dream Drop Distance: dinput8's DirectInput8Create,
MFCreateSourceReaderFromURL, MFCreateDXGIDeviceManager,
D3D11CreateDevice, and d3d12.dll ordinal 101. Measured from the import
tables before anything was installed.
They take the same route too, and this was run rather than argued. Every executable below was watched opening a clip, arming the plane pair and putting a picture on screen:
| Opens | Plane pair | |
|---|---|---|
| FINAL MIX | dt/KH1Movie/OPN.mp4 |
1920×1080 and 960×540 |
| Re_Chain of Memories |
dt/KHCReSource/BIN/movie/RLP.mp4, then three more |
1920×1080, then rebuilt at 1280×720 |
| II FINAL MIX | juefigs/KH2ReSource/zmovie/en/opn.mp4 |
1280×720 and 640×360 |
| Birth by Sleep FINAL MIX | juefigs/BBSReSource/movie/EN/LOP.mp4 |
1280×720 and 640×360 |
| the launcher |
Mare/MOVIE/ReCoded/dt/hd501.mp4, then Mare/MOVIE/Days/dt/DOP.mp4
|
1280×720, then rebuilt at 1920×1080 |
The launcher is not a formality here: it is what plays both film collections, Re:coded and 358/2 Days, the way 2.8's launcher plays Back Cover. It also crosses a clip-size change between them, in the opposite direction from Re_Chain of Memories — 1280×720 up to 1920×1080 — which exercises the same re-arm the other way.
All in software, all getting the shared handle back through
OpenSharedHandle, all producing the plane pair the fix writes into.
The only change any of this needed was to the gate that decides which titles take the plane route. It had been two executable names; eight is not a list any more, so it is the family prefix instead. That is safe in a way a shape match is not: this DLL exists only where an installer put it, and the capture also needs a clip opened through the hooked reader, which is why KINGDOM HEARTS III matches the prefix and can never arm.
KINGDOM HEARTS χ Back Cover — the film in the package — is not played by either
game executable. The launcher plays it, out of its own Launcher/MOVIE
folder, through the same source reader and the same plane pair. That is
measured: the bridge engages inside KINGDOM HEARTS HD 2.8 Launcher.exe and
the log names the file it opens.
So the installer writes its registry override for both executables. The launcher happens to load the DLL beside it without being told to on this machine, but that is Wine's default load order rather than anything arranged, and a fix that works by default ordering is a fix that stops working when the ordering changes.
The 27 videos ship with their first 256 bytes scrambled — no ftyp at offset
4, and every file byte-identical to every other across its first twenty bytes.
That is not something the fix has to deal with, because the game rewrites each
file in place, in plaintext, the first time it plays it. Measured: after
playing exactly one cutscene, 1 of 27 files had a readable ftyp mp42 header
and the other 26 did not.
This is worth writing down because it makes the reader's job ordinary.
MFCreateSourceReaderFromURL is handed a path and opens a normal MP4, and the
scrambling never appears anywhere in the video path.
The game asks for MF_SOURCE_READER_D3D_MANAGER, so on Windows Media
Foundation hands it an NV12 texture. It then does something none of the earlier
titles do: it opens that texture in D3D12 through a shared handle and copies
plane 0 and plane 1 into resources of its own — R8_UNORM at 1920×1080 for
luma, R8G8_UNORM at 960×540 for chroma — and converts them in its own shader.
The bridge published a B8G8R8A8 texture, because that is what a game wanting
a picture needs. Those plane copies read nothing from it, so both planes stayed
at zero. Zero luma with zero chroma is green, and that is the whole
symptom: not a missing frame, but a correct frame in a format the game was not
reading.
Painting the bridge's texture solid magenta is the test that split the previous title's fault in two. Here it came out green anyway — the magenta never appeared. That is a stronger result than a positive one would have been: it proved the shared texture is not what the game draws, and pointed at the two plane resources it had created a few lines earlier in the log.
The frames themselves were never in doubt. A sparse sweep of the luma plane
measured average 16, range 14..20 on the first frame and average 63, range 28..235 by the hundred-and-twentieth — a picture fading in, decoded correctly
all along.
The obvious repair is to publish the shared texture as NV12, which is what the decoder would have handed over.
ID3D12Device::CreateCommittedResource with DXGI_FORMAT_NV12 does not
return an error under D3DMetal. It takes the process down. No exception a
proxy DLL can catch, and nothing in the log after the call — the last line
written is the shared handle being opened. Measured on this title; reported in
Findings.
So the frame goes the other way round.
The bridge already watches ID3D12Device::CreateCommittedResource — that is
how the plane pair became visible. It now captures them:
R8_UNORM at the clip's size -> luma
R8G8_UNORM at half the clip's size -> chroma
and every decoded frame is copied straight into them from a copy queue, with no colour conversion at all. The game's shader does that, as it always did.
ReadSample -> NV12 in system memory
-> memcpy of the luma rows -> the game's R8 texture
-> memcpy of the chroma rows -> the game's R8G8 texture
Nothing engages unless a game creates that exact pair at the clip's dimensions, so the four titles that take the shared-texture route are untouched.
Both plane resources are referenced while held, and replaced when the game creates a new pair. That path is not theoretical and was not left to argument: Re_Chain of Memories plays a 1920×1080 clip and then a 1280×720 one in the same session, and the log shows the bridge rebuilding around it —
bridge ready: 1280x720, upload pitch 5120
plane: luma 1280x720
plane: chroma 640x360
plane: writing both planes directly, luma pitch 1280 chroma pitch 1280
— with both clips visible on screen. Writing into the first pair after the second was created would have been silent, which is the reason to hold a reference and replace on sight rather than capture once.
Only steam_api64.dll sits beside the executable, and nothing here rides on
Steam's API or re-exports a Steamworks entry point. So the bridge rides on
dinput8.dll, which the game imports, which has five exports and nothing to do
with rendering. The original is CrossOver's own and the installer copies it out
of the bottle; nothing is redistributed, but it is a copy, so refresh it after a
CrossOver upgrade.
Like NieR Replicant, this one writes a registry key so Wine prefers the copy
beside the game over its own dinput8. It is scoped to this executable alone.
Do not use this on a game with anti-cheat. The fix patches a running process.
Measured on Preview and D3DMetal only. 26.3 has not been tried.
0.2 Birth by Sleep and KINGDOM HEARTS III are untouched by this and need to be. Both decode their own video in software.
Seven of the eight executables have been run and watched, covering every distinct engine in both packages: Dream Drop Distance, 2.8's launcher playing Back Cover, FINAL MIX, Re_Chain of Memories, II FINAL MIX, Birth by Sleep FINAL MIX and 1.5+2.5's launcher.
KINGDOM HEARTS Theater.exe is the one not observed, and it now looks like it
never will be: both film collections were reached through the launcher, so
nothing in normal play appears to route through that executable at all. It
carries the same import signature and the installer covers it regardless, which
costs a registry key and removes the question.
The launcher crashes on exit on this machine, inside libobjc by way of
winemac, in a thread belonging to the launcher rather than the game. It is
unrelated to the bridge and the game keeps running.
Eight runs. Three hypotheses died by measurement: that the game never asks for a D3D texture (it does, and refusing turns green into black), that the shared texture's size was the mismatch (matching it changed nothing), and that publishing NV12 would work (it ends the process).