// dx11 / dxgi hook entrypoint. trampolines d3d11.dll / dxgi.dll exports // the moment those DLLs appear (LDR notification + poll-thread fallback), // then drives proactive vtable capture so we don't lose the race against // the execexe loader. per-vtable hook implementations live in the sibling // files (d3d11_swapchain / d3d11_factory / d3d11_vtable_capture / // d3d11_screenshot). // // note: never LoadLibrary d3d11/dxgi -- execexe pre-loads them itself and // fails (error 0xa) if they're already in the loader's module list. // // 64-bit only. #include "d3d11_backend.h" #ifndef SPICE_D3D11 void graphics_d3d11_init() {} void graphics_d3d11_shutdown() {} #else #include #include #include #include #include #include #include #include #include #include "d3d11_internal.h" #include "util/nt_loader.h" namespace { using D3D11CreateDeviceAndSwapChain_t = HRESULT(WINAPI *)( IDXGIAdapter *, D3D_DRIVER_TYPE, HMODULE, UINT, const D3D_FEATURE_LEVEL *, UINT, UINT, const DXGI_SWAP_CHAIN_DESC *, IDXGISwapChain **, ID3D11Device **, D3D_FEATURE_LEVEL *, ID3D11DeviceContext **); using CreateDXGIFactory_t = HRESULT(WINAPI *)(REFIID, void **); using CreateDXGIFactory1_t = HRESULT(WINAPI *)(REFIID, void **); using CreateDXGIFactory2_t = HRESULT(WINAPI *)(UINT, REFIID, void **); D3D11CreateDeviceAndSwapChain_t D3D11CreateDeviceAndSwapChain_orig = nullptr; CreateDXGIFactory_t CreateDXGIFactory_orig = nullptr; CreateDXGIFactory1_t CreateDXGIFactory1_orig = nullptr; CreateDXGIFactory2_t CreateDXGIFactory2_orig = nullptr; std::atomic g_d3d11_exports_hooked { false }; std::atomic g_dxgi_exports_hooked { false }; // ---------------------------------------------------------------------- // top-level export hooks HRESULT WINAPI D3D11CreateDeviceAndSwapChain_hook( IDXGIAdapter *pAdapter, D3D_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, const D3D_FEATURE_LEVEL *pFeatureLevels, UINT FeatureLevels, UINT SDKVersion, const DXGI_SWAP_CHAIN_DESC *pSwapChainDesc, IDXGISwapChain **ppSwapChain, ID3D11Device **ppDevice, D3D_FEATURE_LEVEL *pFeatureLevel, ID3D11DeviceContext **ppImmediateContext) { HRESULT res = D3D11CreateDeviceAndSwapChain_orig( pAdapter, DriverType, Software, Flags, pFeatureLevels, FeatureLevels, SDKVersion, pSwapChainDesc, ppSwapChain, ppDevice, pFeatureLevel, ppImmediateContext); if (SUCCEEDED(res) && ppSwapChain && *ppSwapChain) { if (pSwapChainDesc) { d3d11_hooks::note_main_hwnd(pSwapChainDesc->OutputWindow); } d3d11_hooks::install_swapchain_hooks(*ppSwapChain); } return res; } #define DEFINE_FACTORY_HOOK(NAME, SIG_PARAMS, ORIG_ARGS) \ HRESULT WINAPI NAME##_hook SIG_PARAMS { \ HRESULT res = NAME##_orig ORIG_ARGS; \ if (SUCCEEDED(res) && ppFactory && *ppFactory) { \ d3d11_hooks::install_factory_hooks( \ reinterpret_cast(*ppFactory)); \ } \ return res; \ } DEFINE_FACTORY_HOOK(CreateDXGIFactory, (REFIID riid, void **ppFactory), (riid, ppFactory)) DEFINE_FACTORY_HOOK(CreateDXGIFactory1, (REFIID riid, void **ppFactory), (riid, ppFactory)) DEFINE_FACTORY_HOOK(CreateDXGIFactory2, (UINT Flags, REFIID riid, void **ppFactory), (Flags, riid, ppFactory)) #undef DEFINE_FACTORY_HOOK // ---------------------------------------------------------------------- // export trampoline plumbing // serializes trampoline_export() so the LDR notification callback and the // poll thread don't race each other into MinHook against the same target. std::mutex g_export_mutex; bool trampoline_export(const char *dll, const char *name, void *hook, void **orig) { std::lock_guard lock(g_export_mutex); if (*orig) { return true; } HMODULE mod = GetModuleHandleA(dll); if (!mod) { return false; } void *addr = reinterpret_cast(GetProcAddress(mod, name)); if (!addr) { return false; } *orig = addr; // trampoline_try reads *orig before overwriting it. if (!detour::trampoline_try(addr, hook, orig)) { *orig = nullptr; return false; } log_info("graphics::d3d11", "trampolined {}!{}", dll, name); return true; } void try_install_d3d11_exports() { if (g_d3d11_exports_hooked) { return; } if (trampoline_export("d3d11.dll", "D3D11CreateDeviceAndSwapChain", (void *) D3D11CreateDeviceAndSwapChain_hook, (void **) &D3D11CreateDeviceAndSwapChain_orig)) { g_d3d11_exports_hooked = true; } } void try_install_dxgi_exports() { if (g_dxgi_exports_hooked) { return; } struct entry { const char *name; void *hook; void **orig; }; const entry entries[] = { { "CreateDXGIFactory", (void *) CreateDXGIFactory_hook, (void **) &CreateDXGIFactory_orig }, { "CreateDXGIFactory1", (void *) CreateDXGIFactory1_hook, (void **) &CreateDXGIFactory1_orig }, { "CreateDXGIFactory2", (void *) CreateDXGIFactory2_hook, (void **) &CreateDXGIFactory2_orig }, }; bool any = false; for (auto &e : entries) { any |= trampoline_export("dxgi.dll", e.name, e.hook, e.orig); } if (any) { g_dxgi_exports_hooked = true; } } void try_capture_if_ready() { if (g_d3d11_exports_hooked && g_dxgi_exports_hooked) { d3d11_hooks::try_capture_vtables(); } } // ---------------------------------------------------------------------- // LDR notification + polling fallback bool dll_name_ends_with(PCUNICODE_STRING name, const wchar_t *suffix) { if (!name || !name->Buffer) { return false; } const size_t n = name->Length / sizeof(WCHAR); const size_t s = wcslen(suffix); return n >= s && _wcsnicmp(name->Buffer + n - s, suffix, s) == 0; } VOID CALLBACK ldr_dll_notification( ULONG reason, PCLDR_DLL_NOTIFICATION_DATA data, PVOID /*context*/) { if (reason != LDR_DLL_NOTIFICATION_REASON_LOADED || !data) { return; } if (dll_name_ends_with(data->Loaded.BaseDllName, L"d3d11.dll")) { try_install_d3d11_exports(); } else if (dll_name_ends_with(data->Loaded.BaseDllName, L"dxgi.dll")) { try_install_dxgi_exports(); } } // execexe maps d3d11/dxgi via a path that bypasses LdrLoadDll, so the // notification above never fires for those DLLs and we have to poll. std::atomic g_stop { false }; std::thread g_poll_thread; std::mutex g_init_mutex; PVOID g_ldr_cookie = nullptr; void poll_thread() { using namespace std::chrono_literals; for (int32_t i = 0; i < 120 && !g_stop.load(); ++i) { try_install_d3d11_exports(); try_install_dxgi_exports(); if (g_d3d11_exports_hooked && g_dxgi_exports_hooked) { d3d11_hooks::try_capture_vtables(); return; } // sliced so shutdown doesn't have to wait a full second. for (int32_t s = 0; s < 10 && !g_stop.load(); ++s) { std::this_thread::sleep_for(100ms); } } } // the overlay's imgui dx11 backend needs D3DCompile (d3dcompiler_XX.dll) to // build its shaders. _43 ships with the DX June 2010 redist on stock Win7; // _46/_47 come with newer Windows. bool d3dcompiler_available() { static const wchar_t *names[] = { L"d3dcompiler_47.dll", L"d3dcompiler_46.dll", L"d3dcompiler_43.dll", }; for (auto name : names) { HMODULE mod = GetModuleHandleW(name); if (!mod) { mod = LoadLibraryW(name); } if (mod && GetProcAddress(mod, "D3DCompile")) { return true; } } return false; } } // namespace void graphics_d3d11_init() { // dx11 titles always run under execexe. skipping on pure-dx9 games keeps // their startup path completely untouched (no exports patched, no poll // thread, no LDR callback). if (!GetModuleHandleW(L"execexe.dll")) { return; } // no d3dcompiler -> overlay can't build shaders; skip dx11 overlay if (!d3dcompiler_available()) { log_warning( "graphics::d3d11", "d3dcompiler not found; dx11 overlay disabled"); return; } std::lock_guard lock(g_init_mutex); if (g_poll_thread.joinable()) { return; // already initialized } log_info("graphics::d3d11", "initializing"); // trampoline now if either DLL is already in the PEB. try_install_d3d11_exports(); try_install_dxgi_exports(); try_capture_if_ready(); // catches standard LdrLoadDll loads. auto reg = reinterpret_cast( GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "LdrRegisterDllNotification")); if (reg) { NTSTATUS st = reg(0, ldr_dll_notification, nullptr, &g_ldr_cookie); if (NT_SUCCESS(st)) { log_info("graphics::d3d11", "registered LDR DLL notification"); } else { g_ldr_cookie = nullptr; log_warning("graphics::d3d11", "LdrRegisterDllNotification failed: {:#x}", (unsigned long)st); } } // catches the execexe loader path that bypasses LdrLoadDll. g_poll_thread = std::thread(poll_thread); } void graphics_d3d11_shutdown() { std::lock_guard lock(g_init_mutex); // unregister first so the callback can't fire mid-teardown. if (g_ldr_cookie) { auto unreg = reinterpret_cast( GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "LdrUnregisterDllNotification")); if (unreg) { unreg(g_ldr_cookie); } g_ldr_cookie = nullptr; } g_stop.store(true); if (g_poll_thread.joinable()) { g_poll_thread.join(); } } #endif // SPICE_D3D11