-
-
Notifications
You must be signed in to change notification settings - Fork 640
Fix D3D10 crash when DXGI runtime queries device proxy #405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Bypass proxy for QueryInterface calls from dxgi.dll to prevent crash when DXGI calls internal functions on adapter object - Add missing IDXGIFactory2 vtable hooks (CreateSwapChainForCoreWindow, CreateSwapChainForComposition) for complete swap chain interception
| // Bypass proxy for calls originating from DXGI runtime (dxgi.dll) | ||
| // When DXGI queries IDXGIDevice from our device proxy, it may call internal functions on the returned adapter object | ||
| // If a proxy adapter is returned, these internal calls will crash because they expect the original DXGI object layout | ||
| // This commonly occurs in IDXGIOutput::DuplicateOutput which queries device -> adapter -> internal adapter function |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IDXGIOutput::DuplicateOutput only works for devices created from a DXGI1.1 factory, which D3D10CreateDevice[...] doesn't do by default (only when a DXGI1.1 adapter is passed in, which wasn't a thing back when D3D10 was prominent), so it's unlikely that this case ever occurs in the wild. Still doesn't hurt to handle it though I guess.
| { | ||
| // Install vtable hook on original factory to catch all CreateSwapChain calls | ||
| // This is necessary because DXGI runtime may query IDXGIDevice from our device proxy and bypass our proxy chain | ||
| reshade::hooks::install("IDXGIFactory::CreateSwapChain", reshade::hooks::vtable_from_instance(factory.get()), 10, &IDXGIFactory_CreateSwapChain); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DXGI vtable hooks are intentionally not installed by default, see also #359. This would break that again. What is this trying to solve? There should be no need for this at all, since swapchain creation calls are instead directed through a proxy object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I investigated a D3D10 SwapChain hooking issue with Crysis and found that the proxy approach has a fundamental limitation for D3D10 games. Problem: Some D3D10 games (like Crysis) create temporary devices to test feature support, then destroy them. The proxy factory/adapter are destroyed along with these temporary devices. However, the game later obtains the original factory via IDXGIDevice::GetAdapter() → IDXGIAdapter::GetParent() and uses it for SwapChain creation, bypassing our hooks entirely. Proposed Solution: For D3D10 specifically, we could install vtable hooks on the original factory when adapter_proxy == nullptr:
// In D3D10CreateDeviceAndSwapChain1, after getting factory from adapter:
if (!reshade::hooks::is_hooked(reshade::hooks::vtable_from_instance(factory.get()) + 10))
{
reshade::hooks::install("IDXGIFactory::CreateSwapChain",
reshade::hooks::vtable_from_instance(factory.get()), 10,
reinterpret_castreshade::hook::address(&IDXGIFactory_CreateSwapChain));
}
Why this is safe for D3D10: NVIDIA Smooth Motion (which conflicts with vtable hooks in D3D11/D3D12 - the original reason for PR #359) only supports D3D11, D3D12, and Vulkan - not D3D10. So vtable hooks won't cause conflicts for D3D10 games. Would you consider this approach for D3D10 only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetAdapter -> GetParent to obtain the factory is already rerouted so that the proxy object is returned (and has been verified with other games that follow this pattern), so what you are proposing should not be necessary:
D3D10Device proxy object is created with a factory and adapter proxy that is created in D3D10CreateDeviceAndSwapchain1 (https://github.com/crosire/reshade/blob/main/source/d3d10/d3d10.cpp#L140 + https://github.com/crosire/reshade/blob/main/source/d3d10/d3d10.cpp#L160) -> game calls ID3D10Device::QueryInterface on this proxy, which is rerouted to return proxied DXGIDevice (https://github.com/crosire/reshade/blob/main/source/d3d10/d3d10_device.cpp#L94) -> game calls IDXGIDevice::GetAdapter on this proxy, which is rerouted to return the adapter proxy it was created with (https://github.com/crosire/reshade/blob/main/source/dxgi/dxgi_device.cpp#L69) -> game calls IDXGIAdapter::GetParent on this proxy, which is rerouted to return the factory proxy it was created with (https://github.com/crosire/reshade/blob/main/source/dxgi/dxgi_adapter.cpp#L138) -> any calls on factory (including swapchain creation) are made on factory proxy and ReShade gets to know them.
The fact that there are multiple temporary devices created doesn't matter for this, each gets its own proxy adapter and factory object that it then owns.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation. I understand the proxy chain should work in theory, but I have log evidence showing the chain is breaking somewhere. Log analysis from Crysis:
First device - uses proxy adapter, then destroyed
D3D10CreateDevice(pAdapter = 02573CD0, ...) <- proxy adapter
Returning ID3D10Device1 object 0DADDC70
Destroying ID3D10Device1 object 0DADDC70 <- immediately destroyed
Final device - uses ORIGINAL adapter directly!
D3D10CreateDevice(pAdapter = 0DB6B288, ...) <- this is _orig address!
Returning ID3D10Device1 object 05965C78 <- this device is kept
Adapter addresses:
Returning IDXGIAdapter0 object 0DBFFBA0 (0DB6B288) <- proxy=0DBFFBA0, _orig=0DB6B288
The game passes pAdapter = 0DB6B288, which is the _orig address, not the proxy address 0DBFFBA0. This means somewhere the game obtained the original adapter instead of the proxy. My guess is the game calls IDXGIDevice::GetAdapter() on the first temporary device, stores that adapter (which returns the original, not proxy), then reuses it for subsequent device creation. Could you check if DXGIDevice::GetAdapter() properly returns the proxy adapter in all cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried in Crysis myself now and am unfortunately unable to reproduce this. In my log it does use the proxy adapter during device creation, where the factory is one it creates itself via CreateDXGIFactory and ReShade is correctly initialized:
| INFO | Initializing crosire's ReShade version '[...]' (64-bit) loaded from '[...]\dxgi.dll' into '"[...]\Crysis.exe" ' (0xFE60FDD0) ...
[...]
| INFO | Redirecting CreateDXGIFactory(riid = {7B7166EC-21C7-44AE-B21A-C9AE321AE369}, ppFactory = 000000000014EE18) ...
| INFO | > Passing on to CreateDXGIFactory1:
| INFO | Redirecting CreateDXGIFactory1(riid = {7B7166EC-21C7-44AE-B21A-C9AE321AE369}, ppFactory = 000000000014EE18) ...
[...]
| DEBUG | Returning IDXGIFactory0 object 0000000000553740 (00000000026EDFA0).
| INFO | Redirecting IDXGIFactory::EnumAdapters(this = 0000000000553740, Adapter = 0, ppAdapter = 000000000014EE10) ...
| DEBUG | Returning IDXGIAdapter0 object 00000000047E2000 (00000000047E3540).
| INFO | Redirecting D3D10CreateDevice(pAdapter = 00000000047E2000, DriverType = 0, Software = 0000000000000000, Flags = 0, SDKVersion = 29, ppDevice = 000000000014EE20) ...
| INFO | > Passing on to D3D10CreateDeviceAndSwapChain1:
[...]
| INFO | Redirecting D3D10CreateDeviceAndSwapChain1(pAdapter = 00000000047E2000, DriverType = 0, Software = 0000000000000000, Flags = 0, HardwareLevel = a100, SDKVersion = 32, pSwapChainDesc = 0000000000000000, ppSwapChain = 0000000000000000, ppDevice = 000000000014EE20) ...
[...]
| DEBUG | Returning ID3D10Device1 object 0000000004FE15C0 (0000000004DAF898) and IDXGIDevice1 object 0000000004FE15A0 (0000000004DAEFD8).
| DEBUG | Destroying ID3D10Device1 object 0000000004FE15C0 (0000000004DAF898) and IDXGIDevice1 object 0000000004FE15A0 (0000000004DAEFD8).
[...]
| DEBUG | Destroying IDXGIAdapter0 object 00000000047E2000 (00000000047E3540).
| DEBUG | Destroying IDXGIFactory0 object 0000000000553740 (00000000026EDFA0).
| INFO | Redirecting CreateDXGIFactory(riid = {7B7166EC-21C7-44AE-B21A-C9AE321AE369}, ppFactory = 000000000014E8D0) ...
| INFO | > Passing on to CreateDXGIFactory1:
| INFO | Redirecting CreateDXGIFactory1(riid = {7B7166EC-21C7-44AE-B21A-C9AE321AE369}, ppFactory = 000000000014E8D0) ...
| DEBUG | Returning IDXGIFactory0 object 0000000004E89210 (0000000004803250).
| INFO | Redirecting IDXGIFactory::EnumAdapters(this = 0000000004E89210, Adapter = 0, ppAdapter = 000000000014E8E8) ...
| DEBUG | Returning IDXGIAdapter0 object 0000000004E898D0 (0000000004F98810).
| INFO | Redirecting D3D10CreateDevice(pAdapter = 0000000004E898D0, DriverType = 0, Software = 0000000000000000, Flags = 0, SDKVersion = 29, ppDevice = 000000000014E7B0) ...
| INFO | > Passing on to D3D10CreateDeviceAndSwapChain1:
| INFO | Redirecting D3D10CreateDeviceAndSwapChain1(pAdapter = 0000000004E898D0, DriverType = 0, Software = 0000000000000000, Flags = 0, HardwareLevel = a100, SDKVersion = 32, pSwapChainDesc = 0000000000000000, ppSwapChain = 0000000000000000, ppDevice = 000000000014E7B0) ...
[...]
| DEBUG | Returning ID3D10Device1 object 00000000050E7660 (0000000004E6A918) and IDXGIDevice1 object 00000000050E7640 (0000000004E6A058).
| DEBUG | Destroying ID3D10Device1 object 00000000050E7660 (0000000004E6A918) and IDXGIDevice1 object 00000000050E7640 (0000000004E6A058).
[...]
| INFO | Redirecting D3D10CreateDevice(pAdapter = 0000000000000000, DriverType = 1, Software = 0000000000000000, Flags = 0, SDKVersion = 29, ppDevice = 000000000014E7B0) ...
| INFO | > Passing on to D3D10CreateDeviceAndSwapChain1:
| INFO | Redirecting D3D10CreateDeviceAndSwapChain1(pAdapter = 0000000000000000, DriverType = 1, Software = 0000000000000000, Flags = 0, HardwareLevel = a100, SDKVersion = 32, pSwapChainDesc = 0000000000000000, ppSwapChain = 0000000000000000, ppDevice = 000000000014E7B0) ...
[...]
| WARN | D3D10CreateDeviceAndSwapChain1 failed with error code E_NOINTERFACE.
| INFO | Redirecting IDXGIFactory::EnumAdapters(this = 0000000004E89210, Adapter = 1, ppAdapter = 000000000014E8E8) ...
| DEBUG | Returning IDXGIAdapter0 object 0000000004E89F30 (0000000004944FE0).
| INFO | Redirecting D3D10CreateDevice(pAdapter = 0000000004E89F30, DriverType = 0, Software = 0000000000000000, Flags = 0, SDKVersion = 29, ppDevice = 000000000014E7B0) ...
| INFO | > Passing on to D3D10CreateDeviceAndSwapChain1:
| INFO | Redirecting D3D10CreateDeviceAndSwapChain1(pAdapter = 0000000004E89F30, DriverType = 0, Software = 0000000000000000, Flags = 0, HardwareLevel = a100, SDKVersion = 32, pSwapChainDesc = 0000000000000000, ppSwapChain = 0000000000000000, ppDevice = 000000000014E7B0) ...
[...]
| DEBUG | Returning ID3D10Device1 object 00000000050E7660 (0000000004893528) and IDXGIDevice1 object 00000000050E7640 (0000000004892C68).
| DEBUG | Destroying ID3D10Device1 object 00000000050E7660 (0000000004893528) and IDXGIDevice1 object 00000000050E7640 (0000000004892C68).
[...]
| INFO | Redirecting D3D10CreateDevice(pAdapter = 0000000000000000, DriverType = 1, Software = 0000000000000000, Flags = 0, SDKVersion = 29, ppDevice = 000000000014E7B0) ...
| INFO | > Passing on to D3D10CreateDeviceAndSwapChain1:
| INFO | Redirecting D3D10CreateDeviceAndSwapChain1(pAdapter = 0000000000000000, DriverType = 1, Software = 0000000000000000, Flags = 0, HardwareLevel = a100, SDKVersion = 32, pSwapChainDesc = 0000000000000000, ppSwapChain = 0000000000000000, ppDevice = 000000000014E7B0) ...
[...]
| WARN | D3D10CreateDeviceAndSwapChain1 failed with error code E_NOINTERFACE.
| DEBUG | Destroying IDXGIAdapter0 object 0000000004E89F30 (0000000004944FE0).
| INFO | Redirecting IDXGIFactory::EnumAdapters(this = 0000000004E89210, Adapter = 2, ppAdapter = 000000000014E8E8) ...
| INFO | Redirecting IDXGIFactory::EnumAdapters(this = 0000000004E89210, Adapter = 0, ppAdapter = 000000000014E910) ...
| DEBUG | Returning IDXGIAdapter0 object 0000000004E89A50 (0000000004944FE0).
| INFO | Redirecting D3D10CreateDevice(pAdapter = 0000000004E89A50, DriverType = 0, Software = 0000000000000000, Flags = 0x1, SDKVersion = 29, ppDevice = 000000000014E918) ...
| INFO | > Passing on to D3D10CreateDeviceAndSwapChain1:
| INFO | Redirecting D3D10CreateDeviceAndSwapChain1(pAdapter = 0000000004E89A50, DriverType = 0, Software = 0000000000000000, Flags = 0x1, HardwareLevel = a100, SDKVersion = 32, pSwapChainDesc = 0000000000000000, ppSwapChain = 0000000000000000, ppDevice = 000000000014E918) ...
[...]
| DEBUG | Returning ID3D10Device1 object 00000000048270B0 (0000000004F8A4B8) and IDXGIDevice1 object 0000000004827090 (0000000004F89BF8).
| INFO | Redirecting IDXGIFactory::CreateSwapChain(this = 0000000004E89210, pDevice = 00000000048270B0, pDesc = 00000000023F83B4, ppSwapChain = 000000000014E928) ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested with Crysis (Steam, Bin32) on Windows 11. Here's my log:
| INFO | Initializing crosire's ReShade version '6.6.2.0' (32-bit) loaded from 'D:\Program Files (x86)\Steam\steamapps\common\Crysis\bin32\dxgi.dll' into '"D:\Program Files (x86)\Steam\steamapps\common\Crysis\bin32\crysis.exe"' ...
[...]
| INFO | Redirecting IDXGIFactory::EnumAdapters(this = 025A4800, Adapter = 0, ppAdapter = 0019F0C4) ...
| DEBUG | Returning IDXGIAdapter0 object 025A4840 (059E60F8).
| INFO | Redirecting D3D10CreateDevice(pAdapter = 025A4840, DriverType = 0, Software = 00000000, Flags = 0, SDKVersion = 29, ppDevice = 0019F0CC) ...
| INFO | > Passing on to D3D10CreateDeviceAndSwapChain1:
| INFO | Redirecting D3D10CreateDeviceAndSwapChain1(pAdapter = 025A4840, DriverType = 0, Software = 00000000, Flags = 0, HardwareLevel = a100, SDKVersion = 32, pSwapChainDesc = 00000000, ppSwapChain = 00000000, ppDevice = 0019F0CC) ...
| DEBUG | Returning ID3D10Device1 object 05A7E900 (05A953E4) and IDXGIDevice1 object 05A7E8F0 (05A94F1C).
| DEBUG | Destroying ID3D10Device1 object 05A7E900 (05A953E4) and IDXGIDevice1 object 05A7E8F0 (05A94F1C).
| DEBUG | Destroying IDXGIAdapter0 object 025A4840 (059E60F8).
| DEBUG | Destroying IDXGIFactory0 object 025A4800 (059E3D10).
| INFO | Redirecting D3D10CreateDevice(pAdapter = 0DAC91C0, DriverType = 0, Software = 00000000, Flags = 0, SDKVersion = 29, ppDevice = 0019EC90) ...
| INFO | > Passing on to D3D10CreateDeviceAndSwapChain1:
| INFO | Redirecting D3D10CreateDeviceAndSwapChain1(pAdapter = 0DAC91C0, DriverType = 0, Software = 00000000, Flags = 0, HardwareLevel = a100, SDKVersion = 32, pSwapChainDesc = 00000000, ppSwapChain = 00000000, ppDevice = 0019EC90) ...
| DEBUG | Returning ID3D10Device1 object 059F1408 (05A76464) and IDXGIDevice1 object 059F13F8 (05A75F9C).
| DEBUG | Destroying ID3D10Device1 object 059F1408 (05A76464) and IDXGIDevice1 object 059F13F8 (05A75F9C).
| DEBUG | Destroying IDXGIAdapter0 object 0D9F2528 (0DAC91C0).
| DEBUG | Destroying IDXGIFactory0 object 0D9F2728 (059F6DF0).
| INFO | Redirecting D3D10CreateDevice(pAdapter = 059FF628, DriverType = 0, Software = 00000000, Flags = 0x1, SDKVersion = 29, ppDevice = 0019ED8C) ...
| INFO | > Passing on to D3D10CreateDeviceAndSwapChain1:
| INFO | Redirecting D3D10CreateDeviceAndSwapChain1(pAdapter = 059FF628, DriverType = 0, Software = 00000000, Flags = 0x1, HardwareLevel = a100, SDKVersion = 32, pSwapChainDesc = 00000000, ppSwapChain = 00000000, ppDevice = 0019ED8C) ...
| DEBUG | Returning ID3D10Device1 object 05AF7C38 (0D9356D4) and IDXGIDevice1 object 05AF7C28 (0D93520C).
The first device uses proxy adapter 025A4840, but the proxy is destroyed with the device. For subsequent devices, pAdapter = 0DAC91C0 and pAdapter = 059FF628 - these are not the proxy. Looking at Destroying IDXGIAdapter0 object 0D9F2528 (0DAC91C0) shows 0DAC91C0 is the _orig of a different proxy. Also there's no IDXGIFactory::CreateSwapChain log anywhere in the file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found the problem now:
CryRenderD3D10.dll statically links against dxgi.dll, which is why ReShade gets loaded from the game directory when the game boots. But CryRenderD3D10.dll then internally loads dxgi.dll a second time, dynamically with an absolute path pointing to the C:\Windows\system32\dxgi.dll version. It then performs DXGI factory creation using the creation function queried from that system dxgi.dll, effectively bypassing ReShade altogether.
Fortunately the popular C1-Launcher (which is also used by GOG by default) actually fixes this (which is why I couldn't reproduce before): https://github.com/ccomrade/c1-launcher/blob/master/Code/Launcher/MemoryPatch.cpp#L2991
So can solve this by simply installing https://github.com/ccomrade/c1-launcher.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the detailed analysis! I wasn't aware that CryRenderD3D10.dll statically links against dxgi.dll and then loads it again dynamically, bypassing ReShade's hooks. I'll try C1-Launcher as you suggested. Closing this PR since the issue is game-specific rather than a ReShade problem.
Fix D3D10 crash when DXGI runtime queries device proxy
when DXGI calls internal functions on adapter object
CreateSwapChainForComposition) for complete swap chain interception