Nioh 3
Cutscene runs with sound, picture black. Same bridge as DYNASTY WARRIORS on a different carrier; its VP9 is decoded by the fix's own reader, since Preview's decoder draws magenta blocks into it.
GPL-3.0-or-laterThis fix in MacGameFix
What you see
Failed to play movie.
Why it happens
The game asks for D3D-backed decode on a D3D12 renderer; the frames the D3D11 decoder produces never reach the renderer.
What the fix changes
The DYNASTY WARRIORS video bridge, on amd_ags_x64.dll
The code
The D3D12 renderer opens the handle of a frame decoded on the D3D11 device; the bridge answers with a texture on the game's own device that carries the frame across.
fixes/dwo-video-bridge.c · d3d12_open_shared_handle, lines 2163–2215c
static HRESULT WINAPI d3d12_open_shared_handle(void *self, HANDLE handle,
REFIID iid, void **out)
{
{
static LONG told;
if (InterlockedIncrement(&told) <= 12)
logf_("D3D12 OpenSharedHandle %p %s%s", handle, guid_text_(iid),
handle == BRIDGE_HANDLE ? " << ours" : "");
}
if (handle == BRIDGE_HANDLE)
{
HRESULT hr;
if (!out) return E_INVALIDARG;
*out = NULL;
if (!IsEqualGUID(iid, &IID_ID3D12Resource)) return E_NOINTERFACE;
EnterCriticalSection(&frame_lock);
/* A new clip of another size: the game has made a new texture and is
* opening it now, so this is the one moment the bridge can change size.
* Handing back the old one leaves the game drawing from a texture no
* frame of this clip can be written into. */
if (bridge_texture && frame_width && frame_height &&
(texture_width != frame_width || texture_height != frame_height))
{
logf_("bridge: the game opens a %ux%u clip on a %ux%u bridge; rebuilding",
frame_width, frame_height, texture_width, texture_height);
bridge_destroy();
}
if (!bridge_texture && frame_width && frame_height)
{
/*
* The size the game asked to share, when it said one.
*
* The handle it passes belongs to a texture it created itself, and
* what comes back has to describe that texture -- not the clip.
* Kingdom Hearts shares a 2048x1080 surface to carry a 1920x1080
* frame, so a texture built to the clip's size is the wrong
* resource under the same handle, and what the game copies out of
* it is not a picture.
*/
bridge_create((ID3D12Device *)self, frame_width, frame_height);
}
hr = bridge_texture ? S_OK : E_FAIL;
if (bridge_texture)
{
ID3D12Resource_AddRef(bridge_texture);
*out = bridge_texture;
}
LeaveCriticalSection(&frame_lock);
return hr;
}
return real_open_shared_handle(self, handle, iid, out);
}On the D3D11 side IDXGIResource::GetSharedHandle returns E_NOTIMPL; the bridge hands out a handle of its own that the D3D12 side recognises.
fixes/dwo-video-bridge.c · res_get_shared_handle, lines 1863–1884c
static HRESULT WINAPI res_get_shared_handle(void *self, HANDLE *handle)
{
HRESULT hr = real_res_get_shared_handle(self, handle);
{
static LONG told;
if (InterlockedIncrement(&told) <= 12)
logf_("GetSharedHandle -> 0x%08lx%s", (unsigned long)hr,
(FAILED(hr) && d3d12_bridge_armed) ? " (answering BRIDGE_HANDLE)" : "");
}
/*
* Only invent a handle if the side that can recognise it is armed.
*
* BRIDGE_HANDLE is meaningful to d3d12_open_shared_handle and to nothing
* else. Handing it out while that hook is missing turns a clean failure --
* the game learns it cannot share, and says so -- into a real D3D12 device
* dereferencing 0xD3D12B21D. Nioh 3 crashed exactly that way, because its
* D3D12 arrives through Streamline and the hook had not been placed there
* yet. Reporting the original failure is the safe answer.
*/
if (FAILED(hr) && handle && d3d12_bridge_armed) { *handle = BRIDGE_HANDLE; return S_OK; }
return hr;
}Details
- Needs
- D3DMetal, D3D12 — the opposite of Nioh and Nioh 2
- Installer
- install-nioh3-bridge.sh
- Executable
- Nioh3.exe
- Built from
- dwo-video-bridge.c, asf-reader.c, wmv-dmo.c, wm-reader.c, dshow-graph.c
- Verified on
- Stable 26.3, on an engine carrying this project's
winegstreamer; CrossOver Preview 27 through NotProton, with its VP9 decoded by the fix (2026-10-14)