mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 14:50:41 -07:00
2e2968d1bc
## Link to GitHub Issue or related Pull Request, if one exists Fixes #737, regressed by #707 ## Description of change ImGui backend for DX11 pulled in dependency on a specific version of `d3dcompiler_47.dll` which is not present on stock Win7 OS image. Change this to a runtime load of the DLL and disable the overlay if we can't find one. ## Testing - [x] Win11, DX11 overlay - [x] Win11, DX9 overlay - [x] Win7, DX9 overlay
305 lines
10 KiB
C++
305 lines
10 KiB
C++
// 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 <atomic>
|
|
#include <thread>
|
|
#include <chrono>
|
|
#include <cwchar>
|
|
#include <mutex>
|
|
|
|
#include <windows.h>
|
|
#include <d3d11.h>
|
|
#include <dxgi.h>
|
|
#include <dxgi1_2.h>
|
|
|
|
#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<bool> g_d3d11_exports_hooked { false };
|
|
std::atomic<bool> 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<IUnknown *>(*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<std::mutex> lock(g_export_mutex);
|
|
if (*orig) {
|
|
return true;
|
|
}
|
|
HMODULE mod = GetModuleHandleA(dll);
|
|
if (!mod) {
|
|
return false;
|
|
}
|
|
void *addr = reinterpret_cast<void *>(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<bool> 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<std::mutex> 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<decltype(&LdrRegisterDllNotification)>(
|
|
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<std::mutex> lock(g_init_mutex);
|
|
|
|
// unregister first so the callback can't fire mid-teardown.
|
|
if (g_ldr_cookie) {
|
|
auto unreg = reinterpret_cast<decltype(&LdrUnregisterDllNotification)>(
|
|
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
|