diff --git a/src/spice2x/CMakeLists.txt b/src/spice2x/CMakeLists.txt index 64e329d..4f890a6 100644 --- a/src/spice2x/CMakeLists.txt +++ b/src/spice2x/CMakeLists.txt @@ -528,6 +528,7 @@ set(SOURCE_FILES ${SOURCE_FILES} hooks/devicehook.cpp hooks/graphics/graphics.cpp hooks/graphics/graphics_windowed.cpp + hooks/graphics/nvapi_impl.cpp hooks/graphics/nvapi_hook.cpp hooks/graphics/nvenc_hook.cpp hooks/graphics/backends/d3d9/d3d9_backend.cpp diff --git a/src/spice2x/games/sdvx/sdvx.cpp b/src/spice2x/games/sdvx/sdvx.cpp index 6cc7975..650e9a0 100644 --- a/src/spice2x/games/sdvx/sdvx.cpp +++ b/src/spice2x/games/sdvx/sdvx.cpp @@ -8,6 +8,9 @@ #include "hooks/graphics/graphics.h" #include "hooks/devicehook.h" #include "hooks/libraryhook.h" +#ifdef SPICE64 +#include "hooks/graphics/nvapi_impl.h" +#endif #include "hooks/graphics/nvapi_hook.h" #include "hooks/powrprof.h" #include "hooks/sleephook.h" @@ -428,11 +431,24 @@ namespace games::sdvx { if (aio != nullptr) { // enable 9on12 for AMD - if (!libutils::try_library("nvapi64.dll")) { + const auto nvapi = libutils::try_library("nvapi64.dll"); + if (nvapi == nullptr) { log_info( "sdvx", "nvapi64.dll not found; for non-NVIDIA GPUs, requesting 9on12 to be enabled"); GRAPHICS_9_ON_12_REQUESTED_BY_GAME = true; + } + + if (nvapi_hook::BYPASS_NVAPI || nvapi == nullptr) { + const uint32_t main_refresh_hz = GRAPHICS_FORCE_REFRESH > 0 ? + GRAPHICS_FORCE_REFRESH : (is_valkyrie_model() ? 120 : 60); + const uint32_t sub_refresh_hz = GRAPHICS_FORCE_REFRESH_SUB.value_or(60); + if (!nvapi_impl::initialize( + avs::game::DLL_INSTANCE, + main_refresh_hz, + sub_refresh_hz)) { + log_warning("sdvx", "failed to initialize synthetic NVAPI"); + } } else { // don't let nvapi mess with display settings nvapi_hook::initialize(avs::game::DLL_INSTANCE); diff --git a/src/spice2x/hooks/graphics/nvapi_hook.cpp b/src/spice2x/hooks/graphics/nvapi_hook.cpp index ab22fe6..f5dec43 100644 --- a/src/spice2x/hooks/graphics/nvapi_hook.cpp +++ b/src/spice2x/hooks/graphics/nvapi_hook.cpp @@ -1,5 +1,6 @@ #include "external/nvapi/nvapi.h" #include "external/nvapi/NvApiDriverSettings.h" +#include "avs/game.h" #include "hooks/libraryhook.h" #include "util/detour.h" #include "util/libutils.h" @@ -12,14 +13,21 @@ namespace nvapi_hook { bool BYPASS_NVAPI = false; typedef uintptr_t *(*NvAPI_QueryInterface_t)(unsigned int); + static NvAPI_QueryInterface_t NvAPI_QueryInterface_orig = nullptr; + static decltype(NvAPI_GPU_GetConnectedDisplayIds) *NvAPI_GPU_GetConnectedDisplayIds_orig = nullptr; + static decltype(NvAPI_DISP_GetGDIPrimaryDisplayId) *NvAPI_DISP_GetGDIPrimaryDisplayId_orig = nullptr; static uintptr_t* __cdecl NvAPI_QueryInterface_hook(unsigned int func_code); + static NvAPI_Status __cdecl NvAPI_GPU_GetConnectedDisplayIds_hook( + NvPhysicalGpuHandle hPhysicalGpu, + NV_GPU_DISPLAYIDS *pDisplayIds, + NvU32 *pDisplayIdCount, + NvU32 flags); static NvAPI_Status __cdecl NvAPI_DISP_SetDisplayConfig_hook( NvU32 pathInfoCount, NV_DISPLAYCONFIG_PATH_INFO *pathInfo, NvU32 flags); void initialize(HINSTANCE dll) { - #ifdef SPICE64 std::string nvapi_dll = "nvapi64.dll"; #else @@ -40,6 +48,18 @@ namespace nvapi_hook { return nullptr; } + // spoof display connector types only for SDVX + if (func_code == 0x0078DBA2 && avs::game::is_model("KFC")) { + NvAPI_GPU_GetConnectedDisplayIds_orig = + reinterpret_cast( + NvAPI_QueryInterface_orig(func_code)); + NvAPI_DISP_GetGDIPrimaryDisplayId_orig = + reinterpret_cast( + NvAPI_QueryInterface_orig(0x1E9D8A31)); + log_misc("nvapi_hook", "NvAPI_QueryInterface(NvAPI_GPU_GetConnectedDisplayIds) - hooked"); + return (uintptr_t *)NvAPI_GPU_GetConnectedDisplayIds_hook; + } + // NvAPI_DISP_SetDisplayConfig if (func_code == 0x5D8CF8DE) { log_misc("nvapi_hook", "NvAPI_QueryInterface(NvAPI_DISP_SetDisplayConfig) - hooked"); @@ -51,8 +71,53 @@ namespace nvapi_hook { return NvAPI_QueryInterface_orig(func_code); } + NvAPI_Status __cdecl NvAPI_GPU_GetConnectedDisplayIds_hook( + NvPhysicalGpuHandle hPhysicalGpu, + NV_GPU_DISPLAYIDS *pDisplayIds, + NvU32 *pDisplayIdCount, + NvU32 flags) { + + if (NvAPI_GPU_GetConnectedDisplayIds_orig == nullptr) { + return NVAPI_NO_IMPLEMENTATION; + } + + const NvAPI_Status status = NvAPI_GPU_GetConnectedDisplayIds_orig( + hPhysicalGpu, + pDisplayIds, + pDisplayIdCount, + flags); + + if (status != NVAPI_OK || pDisplayIds == nullptr || pDisplayIdCount == nullptr || + NvAPI_DISP_GetGDIPrimaryDisplayId_orig == nullptr) { + return status; + } + + NvU32 primary_display_id = 0; + if (NvAPI_DISP_GetGDIPrimaryDisplayId_orig(&primary_display_id) != NVAPI_OK) { + return status; + } + + // report the primary display adapter as DP and all other displays as HDMI + // (the game expects this since the VM cab is like that) + // + // this doesn't actually affect gameplay; only how + // NvDisplayConfig: MainDisplay={}hz / SubDisplay={}hz + // gets printed. + // + // for the actual value that matters for gameplay, it's this: + // NvDisplayConfig: GetMainDisplayRefreshRate = {}hz + for (NvU32 index = 0; index < *pDisplayIdCount; index++) { + auto &display = pDisplayIds[index]; + display.connectorType = display.displayId == primary_display_id ? + NV_MONITOR_CONN_TYPE_DP : NV_MONITOR_CONN_TYPE_HDMI; + } + + return status; + } + NvAPI_Status __cdecl NvAPI_DISP_SetDisplayConfig_hook( NvU32 pathInfoCount, NV_DISPLAYCONFIG_PATH_INFO *pathInfo, NvU32 flags) { + // do not let the game apply its emulated cabinet topology to Windows log_misc("nvapi_hook", "NvAPI_DISP_SetDisplayConfig_hook - do nothing and return"); return NVAPI_OK; } diff --git a/src/spice2x/hooks/graphics/nvapi_impl.cpp b/src/spice2x/hooks/graphics/nvapi_impl.cpp new file mode 100644 index 0000000..dbe6b08 --- /dev/null +++ b/src/spice2x/hooks/graphics/nvapi_impl.cpp @@ -0,0 +1,480 @@ +#include "nvapi_impl.h" + +#ifdef SPICE64 + +#include +#include + +#include + +#include "external/nvapi/nvapi.h" +#include "hooks/libraryhook.h" +#include "util/logging.h" +#include "util/sysutils.h" + +namespace nvapi_impl { + +namespace { + +constexpr unsigned int NVAPI_INITIALIZE_ID = 0x0150E828; +constexpr unsigned int NVAPI_INITIALIZE_EX_ID = 0xAD298D3F; +constexpr unsigned int NVAPI_UNLOAD_ID = 0xD22BDD7E; +constexpr unsigned int NVAPI_ENUM_PHYSICAL_GPUS_ID = 0xE5AC921F; +constexpr unsigned int NVAPI_GPU_GET_CONNECTED_DISPLAY_IDS_ID = 0x0078DBA2; +constexpr unsigned int NVAPI_DISP_GET_GDI_PRIMARY_DISPLAY_ID = 0x1E9D8A31; +constexpr unsigned int NVAPI_DISP_GET_DISPLAY_CONFIG_ID = 0x11ABCCF8; +constexpr unsigned int NVAPI_DISP_SET_DISPLAY_CONFIG_ID = 0x5D8CF8DE; + +constexpr char NVAPI_DLL_NAME_A[] = "nvapi64.dll"; + +struct SyntheticDisplay { + NvU32 display_id; + NvU32 width; + NvU32 height; + NvU32 color_depth; + NvS32 x; + NvS32 y; + NvU32 refresh_rate_1k; + NV_ROTATE rotation; + bool primary; +}; + +static bool provider_initialized = false; +static bool nvapi_initialized = false; +static int gpu_handle_storage = 0; +// snapshot of the Win32 display state exposed through synthetic NVAPI +static std::vector displays; + +static NvPhysicalGpuHandle get_gpu_handle() { + return reinterpret_cast(&gpu_handle_storage); +} + +static NV_ROTATE get_rotation(DWORD orientation) { + switch (orientation) { + case DMDO_90: + return NV_ROTATE_90; + case DMDO_180: + return NV_ROTATE_180; + case DMDO_270: + return NV_ROTATE_270; + default: + return NV_ROTATE_0; + } +} + +static std::vector enumerate_displays( + uint32_t main_refresh_hz, + uint32_t sub_refresh_hz) { + + std::vector result; + + // reuse the active monitor list, then read live modes after -mainmonitor changes + for (const auto &monitor : sysutils::enumerate_monitors()) { + DEVMODEA mode {}; + mode.dmSize = sizeof(mode); + if (!EnumDisplaySettingsExA( + monitor.display_name.c_str(), + ENUM_CURRENT_SETTINGS, + &mode, + 0)) { + continue; + } + + const bool primary = mode.dmPosition.x == 0 && mode.dmPosition.y == 0; + result.push_back({ + .display_id = 0, + .width = mode.dmPelsWidth, + .height = mode.dmPelsHeight, + .color_depth = mode.dmBitsPerPel > 0 ? mode.dmBitsPerPel : 32, + .x = mode.dmPosition.x, + .y = mode.dmPosition.y, + .refresh_rate_1k = 0, + .rotation = get_rotation(mode.dmDisplayOrientation), + .primary = primary, + }); + } + + std::stable_sort(result.begin(), result.end(), [](const auto &left, const auto &right) { + return left.primary && !right.primary; + }); + + if (result.size() > 2) { + result.resize(2); + } + + if (result.empty()) { + result.push_back({ + .display_id = 0, + .width = 1920, + .height = 1080, + .color_depth = 32, + .x = 0, + .y = 0, + .refresh_rate_1k = 0, + .rotation = NV_ROTATE_0, + .primary = true, + }); + } + + for (size_t index = 0; index < result.size(); index++) { + auto &display = result[index]; + display.primary = index == 0; + display.display_id = 0x80000000u | static_cast(index + 1); + const uint32_t refresh_hz = index == 0 ? main_refresh_hz : sub_refresh_hz; + display.refresh_rate_1k = refresh_hz * 1000; + } + + return result; +} + +// initializes NVAPI for the calling process. +// marks the synthetic provider initialized without contacting a driver. +static NvAPI_Status __cdecl NvAPI_Initialize_impl() { + log_misc("nvapi_impl", "NvAPI_Initialize"); + nvapi_initialized = true; + return NVAPI_OK; +} + +// initializes NVAPI with additional client flags. +// accepts the flags and marks the synthetic provider initialized. +static NvAPI_Status __cdecl NvAPI_InitializeEx_impl(NvU32 flags) { + log_misc("nvapi_impl", "NvAPI_InitializeEx(flags={:#x})", flags); + nvapi_initialized = true; + return NVAPI_OK; +} + +// releases NVAPI state held for the calling process. +// clears the synthetic initialization state while leaving the provider installed. +static NvAPI_Status __cdecl NvAPI_Unload_impl() { + log_misc("nvapi_impl", "NvAPI_Unload"); + nvapi_initialized = false; + return NVAPI_OK; +} + +// enumerates physical GPU handles managed by the NVIDIA driver. +// returns one stable synthetic GPU containing all exposed displays. +static NvAPI_Status __cdecl NvAPI_EnumPhysicalGPUs_impl( + NvPhysicalGpuHandle gpu_handles[NVAPI_MAX_PHYSICAL_GPUS], + NvU32 *gpu_count) { + + log_misc( + "nvapi_impl", + "NvAPI_EnumPhysicalGPUs(handles={}, count={})", + fmt::ptr(gpu_handles), + fmt::ptr(gpu_count)); + + if (!nvapi_initialized) { + return NVAPI_API_NOT_INITIALIZED; + } + if (gpu_handles == nullptr || gpu_count == nullptr) { + return NVAPI_INVALID_ARGUMENT; + } + + gpu_handles[0] = get_gpu_handle(); + *gpu_count = 1; + log_misc( + "nvapi_impl", + "NvAPI_EnumPhysicalGPUs - gpu={}, count={}", + fmt::ptr(gpu_handles[0]), + *gpu_count); + return NVAPI_OK; +} + +// returns connected display descriptors for a physical GPU. +// exposes the monitor snapshot as DP primary and HDMI secondary displays. +static NvAPI_Status __cdecl NvAPI_GPU_GetConnectedDisplayIds_impl( + NvPhysicalGpuHandle gpu_handle, + NV_GPU_DISPLAYIDS *display_ids, + NvU32 *display_id_count, + NvU32 flags) { + + const NvU32 input_count = display_id_count != nullptr ? *display_id_count : 0; + log_misc( + "nvapi_impl", + "NvAPI_GPU_GetConnectedDisplayIds(gpu={}, ids={}, count={}, flags={:#x})", + fmt::ptr(gpu_handle), + fmt::ptr(display_ids), + input_count, + flags); + + if (!nvapi_initialized) { + return NVAPI_API_NOT_INITIALIZED; + } + if (gpu_handle != get_gpu_handle()) { + return NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE; + } + if (display_id_count == nullptr) { + return NVAPI_INVALID_ARGUMENT; + } + + const NvU32 required_count = static_cast(displays.size()); + if (display_ids == nullptr) { + *display_id_count = required_count; + log_misc( + "nvapi_impl", + "NvAPI_GPU_GetConnectedDisplayIds - required_count={}", + required_count); + return NVAPI_OK; + } + + const NvU32 capacity = *display_id_count; + *display_id_count = required_count; + if (capacity < required_count) { + return NVAPI_INSUFFICIENT_BUFFER; + } + + for (NvU32 index = 0; index < required_count; index++) { + const auto &source = displays[index]; + auto &destination = display_ids[index]; + destination = {}; + destination.version = NV_GPU_DISPLAYIDS_VER; + destination.connectorType = source.primary ? + NV_MONITOR_CONN_TYPE_DP : NV_MONITOR_CONN_TYPE_HDMI; + destination.displayId = source.display_id; + destination.isActive = 1; + destination.isOSVisible = 1; + destination.isConnected = 1; + destination.isPhysicallyConnected = 1; + } + + log_misc( + "nvapi_impl", + "NvAPI_GPU_GetConnectedDisplayIds - returned_count={}", + required_count); + return NVAPI_OK; +} + +// returns the NVAPI display ID associated with the Windows GDI primary. +// returns the first synthetic display, ordered from the live desktop origin. +static NvAPI_Status __cdecl NvAPI_DISP_GetGDIPrimaryDisplayId_impl(NvU32 *display_id) { + log_misc( + "nvapi_impl", + "NvAPI_DISP_GetGDIPrimaryDisplayId(display_id={})", + fmt::ptr(display_id)); + + if (!nvapi_initialized) { + return NVAPI_API_NOT_INITIALIZED; + } + if (display_id == nullptr || displays.empty()) { + return NVAPI_INVALID_ARGUMENT; + } + + *display_id = displays.front().display_id; + log_misc( + "nvapi_impl", + "NvAPI_DISP_GetGDIPrimaryDisplayId - display_id={:#x}", + *display_id); + return NVAPI_OK; +} + +static void fill_source_mode( + NV_DISPLAYCONFIG_SOURCE_MODE_INFO *destination, + const SyntheticDisplay &source) { + + if (destination == nullptr) { + return; + } + + *destination = {}; + destination->resolution.width = source.width; + destination->resolution.height = source.height; + destination->resolution.colorDepth = source.color_depth; + destination->colorFormat = NV_FORMAT_A8R8G8B8; + destination->position.x = source.x; + destination->position.y = source.y; + destination->spanningOrientation = NV_DISPLAYCONFIG_SPAN_NONE; + destination->bGDIPrimary = source.primary ? 1 : 0; +} + +static NvAPI_Status fill_target( + NV_DISPLAYCONFIG_PATH_TARGET_INFO *destination, + const SyntheticDisplay &source, + NvU32 target_id) { + + if (destination == nullptr) { + return NVAPI_OK; + } + + auto *details = destination->details; + destination->displayId = source.display_id; + destination->targetId = target_id; + + if (details == nullptr) { + return NVAPI_OK; + } + if (details->version != NV_DISPLAYCONFIG_PATH_ADVANCED_TARGET_INFO_VER) { + return NVAPI_INCOMPATIBLE_STRUCT_VERSION; + } + + *details = {}; + details->version = NV_DISPLAYCONFIG_PATH_ADVANCED_TARGET_INFO_VER; + details->rotation = source.rotation; + details->scaling = NV_SCALING_DEFAULT; + details->refreshRate1K = source.refresh_rate_1k; + details->timingOverride = NV_TIMING_OVERRIDE_CURRENT; + return NVAPI_OK; +} + +// retrieves the current global display topology through NVAPI's three-pass contract. +// fills caller-owned buffers from the synthetic monitor snapshot and configured rates. +static NvAPI_Status __cdecl NvAPI_DISP_GetDisplayConfig_impl( + NvU32 *path_info_count, + NV_DISPLAYCONFIG_PATH_INFO *path_info) { + + const NvU32 input_count = path_info_count != nullptr ? *path_info_count : 0; + log_misc( + "nvapi_impl", + "NvAPI_DISP_GetDisplayConfig(count={}, paths={})", + input_count, + fmt::ptr(path_info)); + + if (!nvapi_initialized) { + return NVAPI_API_NOT_INITIALIZED; + } + if (path_info_count == nullptr) { + return NVAPI_INVALID_ARGUMENT; + } + + const NvU32 required_count = static_cast(displays.size()); + if (path_info == nullptr) { + *path_info_count = required_count; + log_misc( + "nvapi_impl", + "NvAPI_DISP_GetDisplayConfig - required_count={}", + required_count); + return NVAPI_OK; + } + + const NvU32 capacity = *path_info_count; + *path_info_count = required_count; + if (capacity < required_count) { + return NVAPI_INSUFFICIENT_BUFFER; + } + + for (NvU32 index = 0; index < required_count; index++) { + auto &path = path_info[index]; + if (path.version != NV_DISPLAYCONFIG_PATH_INFO_VER2) { + return NVAPI_INCOMPATIBLE_STRUCT_VERSION; + } + if (path.targetInfo != nullptr && path.targetInfoCount < 1) { + return NVAPI_INSUFFICIENT_BUFFER; + } + + const auto &display = displays[index]; + path.sourceId = index; + path.targetInfoCount = 1; + path.IsNonNVIDIAAdapter = 0; + path.pOSAdapterID = nullptr; + fill_source_mode(path.sourceModeInfo, display); + + const NvAPI_Status status = fill_target(path.targetInfo, display, index); + if (status != NVAPI_OK) { + return status; + } + } + + log_misc( + "nvapi_impl", + "NvAPI_DISP_GetDisplayConfig - returned_count={}", + required_count); + return NVAPI_OK; +} + +// applies a supplied global display topology through the NVIDIA driver. +// accepts the cabinet topology without making any changes to Windows. +static NvAPI_Status __cdecl NvAPI_DISP_SetDisplayConfig_impl( + NvU32 path_info_count, + NV_DISPLAYCONFIG_PATH_INFO *path_info, + NvU32 flags) { + + log_misc( + "nvapi_impl", + "NvAPI_DISP_SetDisplayConfig(count={}, paths={}, flags={:#x})", + path_info_count, + fmt::ptr(path_info), + flags); + + if (!nvapi_initialized) { + return NVAPI_API_NOT_INITIALIZED; + } + + log_misc("nvapi_impl", "NvAPI_DISP_SetDisplayConfig - return synthetic success"); + return NVAPI_OK; +} + +template +static uintptr_t *query_result(T function) { + return reinterpret_cast(function); +} + +// resolves an NVAPI function ID to its implementation address. +// exposes only the synthetic entry points used by KFC and rejects all others. +static uintptr_t *__cdecl NvAPI_QueryInterface_impl(unsigned int function_id) { + uintptr_t *result = nullptr; + switch (function_id) { + case NVAPI_INITIALIZE_ID: + result = query_result(NvAPI_Initialize_impl); + break; + case NVAPI_INITIALIZE_EX_ID: + result = query_result(NvAPI_InitializeEx_impl); + break; + case NVAPI_UNLOAD_ID: + result = query_result(NvAPI_Unload_impl); + break; + case NVAPI_ENUM_PHYSICAL_GPUS_ID: + result = query_result(NvAPI_EnumPhysicalGPUs_impl); + break; + case NVAPI_GPU_GET_CONNECTED_DISPLAY_IDS_ID: + result = query_result(NvAPI_GPU_GetConnectedDisplayIds_impl); + break; + case NVAPI_DISP_GET_GDI_PRIMARY_DISPLAY_ID: + result = query_result(NvAPI_DISP_GetGDIPrimaryDisplayId_impl); + break; + case NVAPI_DISP_GET_DISPLAY_CONFIG_ID: + result = query_result(NvAPI_DISP_GetDisplayConfig_impl); + break; + case NVAPI_DISP_SET_DISPLAY_CONFIG_ID: + result = query_result(NvAPI_DISP_SetDisplayConfig_impl); + break; + default: + break; + } + + log_misc( + "nvapi_impl", + "NvAPI_QueryInterface(0x{:x}) - {}", + function_id, + result != nullptr ? "implemented" : "unsupported"); + return result; +} + +} + +bool initialize(HINSTANCE dll, uint32_t main_refresh_hz, uint32_t sub_refresh_hz) { + if (provider_initialized) { + return true; + } + if (dll == nullptr) { + log_warning("nvapi_impl", "invalid synthetic module handle"); + return false; + } + + displays = enumerate_displays(main_refresh_hz, sub_refresh_hz); + libraryhook_hook_library(NVAPI_DLL_NAME_A, dll); + libraryhook_hook_proc("nvapi_QueryInterface", NvAPI_QueryInterface_impl); + libraryhook_enable(); + + provider_initialized = true; + log_info( + "nvapi_impl", + "synthetic {} enabled with {} display(s), main={} Hz, sub={} Hz", + NVAPI_DLL_NAME_A, + displays.size(), + main_refresh_hz, + sub_refresh_hz); + return true; +} + +} + +#endif diff --git a/src/spice2x/hooks/graphics/nvapi_impl.h b/src/spice2x/hooks/graphics/nvapi_impl.h new file mode 100644 index 0000000..a43c9bc --- /dev/null +++ b/src/spice2x/hooks/graphics/nvapi_impl.h @@ -0,0 +1,14 @@ +#pragma once + +#ifdef SPICE64 + +#include +#include + +namespace nvapi_impl { + +bool initialize(HINSTANCE dll, uint32_t main_refresh_hz, uint32_t sub_refresh_hz); + +} + +#endif diff --git a/src/spice2x/hooks/libraryhook.cpp b/src/spice2x/hooks/libraryhook.cpp index e981586..ec72fbe 100644 --- a/src/spice2x/hooks/libraryhook.cpp +++ b/src/spice2x/hooks/libraryhook.cpp @@ -12,6 +12,7 @@ static robin_hood::unordered_map PROCS; static decltype(LoadLibraryA) *LoadLibraryA_orig = nullptr; static decltype(LoadLibraryW) *LoadLibraryW_orig = nullptr; +static decltype(LoadLibraryExW) *LoadLibraryExW_orig = nullptr; static decltype(GetModuleHandleA) *GetModuleHandleA_orig = nullptr; static decltype(GetModuleHandleW) *GetModuleHandleW_orig = nullptr; static decltype(GetProcAddress) *GetProcAddress_orig = nullptr; @@ -44,6 +45,20 @@ static HMODULE WINAPI LoadLibraryW_hook(LPCWSTR lpFileName) { return LoadLibraryW_orig(lpFileName); } +static HMODULE WINAPI LoadLibraryExW_hook(LPCWSTR lpFileName, HANDLE hFile, DWORD dwFlags) { + + // check hooks + if (lpFileName) { + auto module = LIBRARIES_W.find(lpFileName); + if (module != LIBRARIES_W.end()) { + return module->second; + } + } + + // fallback + return LoadLibraryExW_orig(lpFileName, hFile, dwFlags); +} + static HMODULE WINAPI GetModuleHandleA_hook(LPCSTR lpModuleName) { // check hooks @@ -103,6 +118,7 @@ void libraryhook_enable(HMODULE module) { // detour detour::trampoline_try("kernel32.dll", "LoadLibraryA", LoadLibraryA_hook, &LoadLibraryA_orig); detour::trampoline_try("kernel32.dll", "LoadLibraryW", LoadLibraryW_hook, &LoadLibraryW_orig); + detour::trampoline_try("kernel32.dll", "LoadLibraryExW", LoadLibraryExW_hook, &LoadLibraryExW_orig); detour::trampoline_try("kernel32.dll", "GetModuleHandleA", GetModuleHandleA_hook, &GetModuleHandleA_orig); detour::trampoline_try("kernel32.dll", "GetModuleHandleW", GetModuleHandleW_hook, &GetModuleHandleW_orig); detour::trampoline_try("kernel32.dll", "GetProcAddress", GetProcAddress_hook, &GetProcAddress_orig);