From 06d2752059598a1d53f3df234ffa8242ead4cd5a Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 3 May 2026 21:35:08 +0300 Subject: [PATCH 01/11] added dx12 hooking --- CMakeLists.txt | 16 +- include/omath/hooks/hooks_manager.hpp | 96 +++++++ source/hooks/hooks_manager.cpp | 369 ++++++++++++++++++++++++++ vcpkg.json | 16 ++ 4 files changed, 496 insertions(+), 1 deletion(-) create mode 100644 include/omath/hooks/hooks_manager.hpp create mode 100644 source/hooks/hooks_manager.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ff324be..768414b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,9 +31,10 @@ option(OMATH_SUPRESS_SAFETY_CHECKS option(OMATH_ENABLE_COVERAGE "Enable coverage" OFF) option(OMATH_ENABLE_FORCE_INLINE "Will for compiler to make some functions to be force inlined no matter what" ON) - option(OMATH_ENABLE_LUA "omath bindings for lua" OFF) +option(OMATH_ENABLE_HOOKING "omath will HooksManager that can hook DirectX automatically" OFF) + if(VCPKG_MANIFEST_FEATURES) foreach(omath_feature IN LISTS VCPKG_MANIFEST_FEATURES) if(omath_feature STREQUAL "imgui") @@ -48,6 +49,8 @@ if(VCPKG_MANIFEST_FEATURES) set(OMATH_BUILD_EXAMPLES ON) elseif(omath_feature STREQUAL "lua") set(OMATH_ENABLE_LUA ON) + elseif(omath_feature STREQUAL "hooking") + set(OMATH_ENABLE_HOOKING ON) endif() endforeach() @@ -100,6 +103,17 @@ if (OMATH_ENABLE_LUA) target_include_directories(${PROJECT_NAME} PRIVATE ${SOL2_INCLUDE_DIRS}) endif () +if (OMATH_ENABLE_HOOKING) + target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_ENABLE_HOOKING) + + find_package(safetyhook CONFIG REQUIRED) + target_link_libraries(${PROJECT_NAME} PRIVATE safetyhook::safetyhook) + + if (WIN32) + target_link_libraries(${PROJECT_NAME} PRIVATE d3d12 dxgi) + endif () +endif () + add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_VERSION="${PROJECT_VERSION}") diff --git a/include/omath/hooks/hooks_manager.hpp b/include/omath/hooks/hooks_manager.hpp new file mode 100644 index 0000000..99aeffe --- /dev/null +++ b/include/omath/hooks/hooks_manager.hpp @@ -0,0 +1,96 @@ +#pragma once + +#ifdef OMATH_ENABLE_HOOKING +#include +#include +#include +#include +#include + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include +#include +#include + +namespace omath::hooks +{ + class HooksManager final + { + HooksManager() = default; + public: + using present_callback = std::function; + using resize_buffers_callback = std::function; + using execute_command_lists_callback = std::function; + // Return nullopt to pass the message to the original WndProc; return a value to intercept it. + using wnd_proc_callback = std::function(HWND, UINT, WPARAM, LPARAM)>; + + [[nodiscard]] static HooksManager& get(); + HooksManager(const HooksManager&) = delete; + HooksManager& operator=(const HooksManager&) = delete; + ~HooksManager(); + + [[nodiscard]] bool hook_dx12(); + void unhook_dx12(); + + void set_on_present(present_callback callback); + void set_on_resize_buffers(resize_buffers_callback callback); + void set_on_execute_command_lists(execute_command_lists_callback callback); + + [[nodiscard]] bool hook_wnd_proc(HWND hwnd); + void unhook_wnd_proc(); + + void set_on_wnd_proc(wnd_proc_callback callback); + + private: + [[nodiscard]] bool build_vtable(); + + static HRESULT __stdcall present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags); + static HRESULT __stdcall resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count, + UINT width, UINT height, DXGI_FORMAT new_format, + UINT swap_chain_flags); + static void __stdcall execute_command_lists_detour(ID3D12CommandQueue* p_command_queue, + UINT num_command_lists, + ID3D12CommandList* const* pp_command_lists); + static LRESULT __stdcall wnd_proc_detour(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param); + + mutable std::shared_mutex m_mutex; + + bool m_is_dx12_hooked = false; + bool m_is_wnd_proc_hooked = false; + + std::vector m_vtable; + HWND m_hooked_hwnd = nullptr; + WNDPROC m_original_wndproc = nullptr; + + safetyhook::InlineHook m_present_hook; + safetyhook::InlineHook m_resize_buffers_hook; + safetyhook::InlineHook m_execute_command_lists_hook; + + present_callback m_present_cb; + resize_buffers_callback m_resize_buffers_cb; + execute_command_lists_callback m_execute_command_lists_cb; + wnd_proc_callback m_wnd_proc_cb; + }; +} + +#else // !OMATH_ENABLE_HOOKING + +namespace omath::hooks +{ + class HooksManager final + { + HooksManager() = default; + public: + [[nodiscard]] static HooksManager& get(); + HooksManager(const HooksManager&) = delete; + ~HooksManager(); + }; +} + +#endif diff --git a/source/hooks/hooks_manager.cpp b/source/hooks/hooks_manager.cpp new file mode 100644 index 0000000..24e608c --- /dev/null +++ b/source/hooks/hooks_manager.cpp @@ -0,0 +1,369 @@ +#include "omath/hooks/hooks_manager.hpp" + +#ifdef OMATH_ENABLE_HOOKING +#include + +namespace +{ + class DummyWindow final + { + WNDCLASSEX m_window_class{}; + HWND m_window_handle = nullptr; + + public: + DummyWindow() + { + m_window_class.cbSize = sizeof(WNDCLASSEX); + m_window_class.style = CS_HREDRAW | CS_VREDRAW; + m_window_class.lpfnWndProc = DefWindowProc; + m_window_class.hInstance = GetModuleHandle(nullptr); + m_window_class.lpszClassName = "OM"; + RegisterClassEx(&m_window_class); + m_window_handle = CreateWindow(m_window_class.lpszClassName, "Dummy", WS_OVERLAPPEDWINDOW, + 0, 0, 100, 100, nullptr, nullptr, m_window_class.hInstance, nullptr); + } + ~DummyWindow() + { + if (m_window_handle) + DestroyWindow(m_window_handle); + UnregisterClass(m_window_class.lpszClassName, m_window_class.hInstance); + } + [[nodiscard]] HWND handle() const { return m_window_handle; } + [[nodiscard]] bool valid() const { return m_window_handle != nullptr; } + }; + + // Method counts per interface (vtable slots including inherited ones) + constexpr std::size_t k_device_methods = 44; + constexpr std::size_t k_command_queue_methods = 19; + constexpr std::size_t k_command_allocator_methods = 9; + constexpr std::size_t k_command_list_methods = 60; + constexpr std::size_t k_swap_chain_methods = 18; + constexpr std::size_t k_total_methods = k_device_methods + k_command_queue_methods + + k_command_allocator_methods + k_command_list_methods + + k_swap_chain_methods; // 150 + + // Base offsets in the combined table + constexpr std::size_t k_cmd_queue_base = k_device_methods; // 44 + constexpr std::size_t k_swap_chain_base = k_device_methods + k_command_queue_methods + + k_command_allocator_methods + k_command_list_methods; // 132 + + // IDXGISwapChain vtable: Present=8, ResizeBuffers=13 (from IUnknown base) + // ID3D12CommandQueue vtable: ExecuteCommandLists=8 (from IUnknown base) + constexpr std::size_t k_present_index = k_swap_chain_base + 8; // 140 + constexpr std::size_t k_resize_buffers_index = k_swap_chain_base + 13; // 145 + constexpr std::size_t k_execute_cmd_lists_index = k_cmd_queue_base + 8; // 52 +} // namespace + +namespace omath::hooks +{ + HooksManager& HooksManager::get() + { + static HooksManager obj; + return obj; + } + + HooksManager::~HooksManager() + { + unhook_wnd_proc(); + unhook_dx12(); + } + + bool HooksManager::build_vtable() + { + const DummyWindow window; + if (!window.valid()) + return false; + + const HMODULE d3d12_module = GetModuleHandle("d3d12.dll"); + const HMODULE dxgi_module = GetModuleHandle("dxgi.dll"); + if (!d3d12_module || !dxgi_module) + return false; + + using create_dxgi_factory_fn = HRESULT(__stdcall*)(REFIID, void**); + using d3d12_create_device_fn = HRESULT(__stdcall*)(IUnknown*, D3D_FEATURE_LEVEL, REFIID, void**); + + const auto create_dxgi_factory = reinterpret_cast( + GetProcAddress(dxgi_module, "CreateDXGIFactory")); + const auto d3d12_create_device = reinterpret_cast( + GetProcAddress(d3d12_module, "D3D12CreateDevice")); + + if (!create_dxgi_factory || !d3d12_create_device) + return false; + + IDXGIFactory* factory = nullptr; + if (FAILED(create_dxgi_factory(__uuidof(IDXGIFactory), reinterpret_cast(&factory)))) + return false; + + IDXGIAdapter* adapter = nullptr; + if (factory->EnumAdapters(0, &adapter) == DXGI_ERROR_NOT_FOUND) + { + factory->Release(); + return false; + } + + ID3D12Device* device = nullptr; + if (FAILED(d3d12_create_device(adapter, D3D_FEATURE_LEVEL_11_0, + __uuidof(ID3D12Device), reinterpret_cast(&device)))) + { + adapter->Release(); + factory->Release(); + return false; + } + adapter->Release(); + + D3D12_COMMAND_QUEUE_DESC queue_desc{}; + queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + + ID3D12CommandQueue* command_queue = nullptr; + if (FAILED(device->CreateCommandQueue(&queue_desc, __uuidof(ID3D12CommandQueue), + reinterpret_cast(&command_queue)))) + { + device->Release(); + factory->Release(); + return false; + } + + ID3D12CommandAllocator* command_allocator = nullptr; + if (FAILED(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, + __uuidof(ID3D12CommandAllocator), + reinterpret_cast(&command_allocator)))) + { + command_queue->Release(); + device->Release(); + factory->Release(); + return false; + } + + ID3D12GraphicsCommandList* command_list = nullptr; + if (FAILED(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, command_allocator, nullptr, + __uuidof(ID3D12GraphicsCommandList), + reinterpret_cast(&command_list)))) + { + command_allocator->Release(); + command_queue->Release(); + device->Release(); + factory->Release(); + return false; + } + + DXGI_SWAP_CHAIN_DESC swap_chain_desc{}; + swap_chain_desc.BufferDesc.Width = 100; + swap_chain_desc.BufferDesc.Height = 100; + swap_chain_desc.BufferDesc.RefreshRate = {60, 1}; + swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swap_chain_desc.SampleDesc = {1, 0}; + swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swap_chain_desc.BufferCount = 2; + swap_chain_desc.OutputWindow = window.handle(); + swap_chain_desc.Windowed = TRUE; + swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; + + IDXGISwapChain* swap_chain = nullptr; + if (FAILED(factory->CreateSwapChain(command_queue, &swap_chain_desc, &swap_chain))) + { + command_list->Release(); + command_allocator->Release(); + command_queue->Release(); + device->Release(); + factory->Release(); + return false; + } + + m_vtable.resize(k_total_methods); + + const auto copy_vtable = [](uintptr_t* dst, void* com_obj, std::size_t count) + { + std::memcpy(dst, *reinterpret_cast(com_obj), count * sizeof(uintptr_t)); + }; + + copy_vtable(m_vtable.data(), device, k_device_methods); + copy_vtable(m_vtable.data() + k_device_methods, command_queue, k_command_queue_methods); + copy_vtable(m_vtable.data() + k_device_methods + k_command_queue_methods, command_allocator, k_command_allocator_methods); + copy_vtable(m_vtable.data() + k_device_methods + k_command_queue_methods + k_command_allocator_methods, command_list, k_command_list_methods); + copy_vtable(m_vtable.data() + k_swap_chain_base, swap_chain, k_swap_chain_methods); + + swap_chain->Release(); + command_list->Release(); + command_allocator->Release(); + command_queue->Release(); + device->Release(); + factory->Release(); + + return true; + } + + bool HooksManager::hook_dx12() + { + std::unique_lock lock(m_mutex); + if (m_is_dx12_hooked) + return true; + + if (!build_vtable()) + return false; + + m_present_hook = safetyhook::create_inline( + reinterpret_cast(m_vtable[k_present_index]), + reinterpret_cast(&present_detour)); + + m_resize_buffers_hook = safetyhook::create_inline( + reinterpret_cast(m_vtable[k_resize_buffers_index]), + reinterpret_cast(&resize_buffers_detour)); + + m_execute_command_lists_hook = safetyhook::create_inline( + reinterpret_cast(m_vtable[k_execute_cmd_lists_index]), + reinterpret_cast(&execute_command_lists_detour)); + + if (!m_present_hook || !m_resize_buffers_hook || !m_execute_command_lists_hook) + { + m_present_hook = {}; + m_resize_buffers_hook = {}; + m_execute_command_lists_hook = {}; + return false; + } + + m_is_dx12_hooked = true; + return true; + } + + void HooksManager::unhook_dx12() + { + std::unique_lock lock(m_mutex); + m_present_hook = {}; + m_resize_buffers_hook = {}; + m_execute_command_lists_hook = {}; + m_vtable.clear(); + m_is_dx12_hooked = false; + } + + void HooksManager::set_on_present(present_callback callback) + { + std::unique_lock lock(m_mutex); + m_present_cb = std::move(callback); + } + + void HooksManager::set_on_resize_buffers(resize_buffers_callback callback) + { + std::unique_lock lock(m_mutex); + m_resize_buffers_cb = std::move(callback); + } + + void HooksManager::set_on_execute_command_lists(execute_command_lists_callback callback) + { + std::unique_lock lock(m_mutex); + m_execute_command_lists_cb = std::move(callback); + } + + bool HooksManager::hook_wnd_proc(HWND hwnd) + { + std::unique_lock lock(m_mutex); + if (m_is_wnd_proc_hooked) + return true; + + const auto prev = reinterpret_cast( + SetWindowLongPtr(hwnd, GWLP_WNDPROC, reinterpret_cast(&wnd_proc_detour))); + if (!prev) + return false; + + m_hooked_hwnd = hwnd; + m_original_wndproc = prev; + m_is_wnd_proc_hooked = true; + return true; + } + + void HooksManager::unhook_wnd_proc() + { + std::unique_lock lock(m_mutex); + if (!m_is_wnd_proc_hooked) + return; + + SetWindowLongPtr(m_hooked_hwnd, GWLP_WNDPROC, reinterpret_cast(m_original_wndproc)); + m_hooked_hwnd = nullptr; + m_original_wndproc = nullptr; + m_is_wnd_proc_hooked = false; + } + + void HooksManager::set_on_wnd_proc(wnd_proc_callback callback) + { + std::unique_lock lock(m_mutex); + m_wnd_proc_cb = std::move(callback); + } + + // Detour implementations: copy callback under shared lock, call it unlocked, + // then call original. This avoids a deadlock if the callback itself calls set_on_*(). + HRESULT __stdcall HooksManager::present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags) + { + auto& mgr = get(); + present_callback cb; + { + std::shared_lock lock(mgr.m_mutex); + cb = mgr.m_present_cb; + } + if (cb) + cb(p_swap_chain, sync_interval, flags); + return mgr.m_present_hook.call(p_swap_chain, sync_interval, flags); + } + + HRESULT __stdcall HooksManager::resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count, + UINT width, UINT height, DXGI_FORMAT new_format, + UINT swap_chain_flags) + { + auto& mgr = get(); + resize_buffers_callback cb; + { + std::shared_lock lock(mgr.m_mutex); + cb = mgr.m_resize_buffers_cb; + } + if (cb) + cb(p_swap_chain, buffer_count, width, height, new_format, swap_chain_flags); + return mgr.m_resize_buffers_hook.call(p_swap_chain, buffer_count, width, height, new_format, + swap_chain_flags); + } + + void __stdcall HooksManager::execute_command_lists_detour(ID3D12CommandQueue* p_command_queue, + UINT num_command_lists, + ID3D12CommandList* const* pp_command_lists) + { + auto& mgr = get(); + execute_command_lists_callback cb; + { + std::shared_lock lock(mgr.m_mutex); + cb = mgr.m_execute_command_lists_cb; + } + if (cb) + cb(p_command_queue, num_command_lists, pp_command_lists); + mgr.m_execute_command_lists_hook.call(p_command_queue, num_command_lists, pp_command_lists); + } + + LRESULT __stdcall HooksManager::wnd_proc_detour(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param) + { + auto& mgr = get(); + wnd_proc_callback cb; + WNDPROC original; + { + std::shared_lock lock(mgr.m_mutex); + cb = mgr.m_wnd_proc_cb; + original = mgr.m_original_wndproc; + } + if (cb) + { + if (const auto result = cb(hwnd, msg, w_param, l_param)) + return *result; + } + return CallWindowProc(original, hwnd, msg, w_param, l_param); + } +} // namespace omath::hooks + +#else // !OMATH_ENABLE_HOOKING + +namespace omath::hooks +{ + HooksManager& HooksManager::get() + { + static HooksManager obj; + return obj; + } + HooksManager::~HooksManager() = default; +} // namespace omath::hooks + +#endif diff --git a/vcpkg.json b/vcpkg.json index 5ad46d9..35d4b52 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -16,6 +16,15 @@ } ], "features": { + "all": { + "description": "Enable all additional features", + "dependencies": [ + { + "name": "omath", + "features": ["imgui", "lua", "hooking"] + } + ] + }, "avx2": { "description": "omath will use AVX2 to boost performance", "supports": "!arm" @@ -26,6 +35,13 @@ "benchmark" ] }, + "hooking": { + "description": "Add interface for automatic hooking of DirectX", + "dependencies": [ + "safetyhook" + ], + "supports": "(windows | linux) & !arm & !uwp" + }, "examples": { "description": "Build examples", "dependencies": [ From 064d0cebbc4a15de9acbadfe1f69b33da532d7fd Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 3 May 2026 21:38:31 +0300 Subject: [PATCH 02/11] update --- include/omath/hooks/hooks_manager.hpp | 16 ++---- source/hooks/hooks_manager.cpp | 81 ++++++++------------------- 2 files changed, 28 insertions(+), 69 deletions(-) diff --git a/include/omath/hooks/hooks_manager.hpp b/include/omath/hooks/hooks_manager.hpp index 99aeffe..c0c47fd 100644 --- a/include/omath/hooks/hooks_manager.hpp +++ b/include/omath/hooks/hooks_manager.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #ifndef WIN32_LEAN_AND_MEAN @@ -24,11 +23,11 @@ namespace omath::hooks { HooksManager() = default; public: - using present_callback = std::function; - using resize_buffers_callback = std::function; + using present_callback = std::function; + using resize_buffers_callback = std::function; using execute_command_lists_callback = std::function; // Return nullopt to pass the message to the original WndProc; return a value to intercept it. - using wnd_proc_callback = std::function(HWND, UINT, WPARAM, LPARAM)>; + using wnd_proc_callback = std::function(HWND, UINT, WPARAM, LPARAM)>; [[nodiscard]] static HooksManager& get(); HooksManager(const HooksManager&) = delete; @@ -48,8 +47,6 @@ namespace omath::hooks void set_on_wnd_proc(wnd_proc_callback callback); private: - [[nodiscard]] bool build_vtable(); - static HRESULT __stdcall present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags); static HRESULT __stdcall resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count, UINT width, UINT height, DXGI_FORMAT new_format, @@ -64,7 +61,6 @@ namespace omath::hooks bool m_is_dx12_hooked = false; bool m_is_wnd_proc_hooked = false; - std::vector m_vtable; HWND m_hooked_hwnd = nullptr; WNDPROC m_original_wndproc = nullptr; @@ -72,10 +68,10 @@ namespace omath::hooks safetyhook::InlineHook m_resize_buffers_hook; safetyhook::InlineHook m_execute_command_lists_hook; - present_callback m_present_cb; - resize_buffers_callback m_resize_buffers_cb; + present_callback m_present_cb; + resize_buffers_callback m_resize_buffers_cb; execute_command_lists_callback m_execute_command_lists_cb; - wnd_proc_callback m_wnd_proc_cb; + wnd_proc_callback m_wnd_proc_cb; }; } diff --git a/source/hooks/hooks_manager.cpp b/source/hooks/hooks_manager.cpp index 24e608c..af8f331 100644 --- a/source/hooks/hooks_manager.cpp +++ b/source/hooks/hooks_manager.cpp @@ -1,7 +1,6 @@ #include "omath/hooks/hooks_manager.hpp" #ifdef OMATH_ENABLE_HOOKING -#include namespace { @@ -32,26 +31,10 @@ namespace [[nodiscard]] bool valid() const { return m_window_handle != nullptr; } }; - // Method counts per interface (vtable slots including inherited ones) - constexpr std::size_t k_device_methods = 44; - constexpr std::size_t k_command_queue_methods = 19; - constexpr std::size_t k_command_allocator_methods = 9; - constexpr std::size_t k_command_list_methods = 60; - constexpr std::size_t k_swap_chain_methods = 18; - constexpr std::size_t k_total_methods = k_device_methods + k_command_queue_methods + - k_command_allocator_methods + k_command_list_methods + - k_swap_chain_methods; // 150 - - // Base offsets in the combined table - constexpr std::size_t k_cmd_queue_base = k_device_methods; // 44 - constexpr std::size_t k_swap_chain_base = k_device_methods + k_command_queue_methods + - k_command_allocator_methods + k_command_list_methods; // 132 - - // IDXGISwapChain vtable: Present=8, ResizeBuffers=13 (from IUnknown base) - // ID3D12CommandQueue vtable: ExecuteCommandLists=8 (from IUnknown base) - constexpr std::size_t k_present_index = k_swap_chain_base + 8; // 140 - constexpr std::size_t k_resize_buffers_index = k_swap_chain_base + 13; // 145 - constexpr std::size_t k_execute_cmd_lists_index = k_cmd_queue_base + 8; // 52 + void* vtable_fn(void* com_obj, std::size_t index) + { + return (*reinterpret_cast(com_obj))[index]; + } } // namespace namespace omath::hooks @@ -68,8 +51,12 @@ namespace omath::hooks unhook_dx12(); } - bool HooksManager::build_vtable() + bool HooksManager::hook_dx12() { + std::unique_lock lock(m_mutex); + if (m_is_dx12_hooked) + return true; + const DummyWindow window; if (!window.valid()) return false; @@ -170,18 +157,19 @@ namespace omath::hooks return false; } - m_vtable.resize(k_total_methods); + // Read the 3 needed vtable slots directly — these addresses live in the DLL, + // not in the objects, so they remain valid after the objects are released. + m_present_hook = safetyhook::create_inline( + vtable_fn(swap_chain, 8), // IDXGISwapChain::Present + reinterpret_cast(&present_detour)); - const auto copy_vtable = [](uintptr_t* dst, void* com_obj, std::size_t count) - { - std::memcpy(dst, *reinterpret_cast(com_obj), count * sizeof(uintptr_t)); - }; + m_resize_buffers_hook = safetyhook::create_inline( + vtable_fn(swap_chain, 13), // IDXGISwapChain::ResizeBuffers + reinterpret_cast(&resize_buffers_detour)); - copy_vtable(m_vtable.data(), device, k_device_methods); - copy_vtable(m_vtable.data() + k_device_methods, command_queue, k_command_queue_methods); - copy_vtable(m_vtable.data() + k_device_methods + k_command_queue_methods, command_allocator, k_command_allocator_methods); - copy_vtable(m_vtable.data() + k_device_methods + k_command_queue_methods + k_command_allocator_methods, command_list, k_command_list_methods); - copy_vtable(m_vtable.data() + k_swap_chain_base, swap_chain, k_swap_chain_methods); + m_execute_command_lists_hook = safetyhook::create_inline( + vtable_fn(command_queue, 8), // ID3D12CommandQueue::ExecuteCommandLists + reinterpret_cast(&execute_command_lists_detour)); swap_chain->Release(); command_list->Release(); @@ -190,30 +178,6 @@ namespace omath::hooks device->Release(); factory->Release(); - return true; - } - - bool HooksManager::hook_dx12() - { - std::unique_lock lock(m_mutex); - if (m_is_dx12_hooked) - return true; - - if (!build_vtable()) - return false; - - m_present_hook = safetyhook::create_inline( - reinterpret_cast(m_vtable[k_present_index]), - reinterpret_cast(&present_detour)); - - m_resize_buffers_hook = safetyhook::create_inline( - reinterpret_cast(m_vtable[k_resize_buffers_index]), - reinterpret_cast(&resize_buffers_detour)); - - m_execute_command_lists_hook = safetyhook::create_inline( - reinterpret_cast(m_vtable[k_execute_cmd_lists_index]), - reinterpret_cast(&execute_command_lists_detour)); - if (!m_present_hook || !m_resize_buffers_hook || !m_execute_command_lists_hook) { m_present_hook = {}; @@ -232,7 +196,6 @@ namespace omath::hooks m_present_hook = {}; m_resize_buffers_hook = {}; m_execute_command_lists_hook = {}; - m_vtable.clear(); m_is_dx12_hooked = false; } @@ -265,8 +228,8 @@ namespace omath::hooks if (!prev) return false; - m_hooked_hwnd = hwnd; - m_original_wndproc = prev; + m_hooked_hwnd = hwnd; + m_original_wndproc = prev; m_is_wnd_proc_hooked = true; return true; } From 1789b1ef516a75434e49f90a2a594547be22d970 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 3 May 2026 21:54:03 +0300 Subject: [PATCH 03/11] added dx11 hook --- CMakeLists.txt | 2 +- include/omath/hooks/hooks_manager.hpp | 37 ++++-- source/hooks/hooks_manager.cpp | 165 +++++++++++++++++++++----- 3 files changed, 165 insertions(+), 39 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 768414b..3343416 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,7 +110,7 @@ if (OMATH_ENABLE_HOOKING) target_link_libraries(${PROJECT_NAME} PRIVATE safetyhook::safetyhook) if (WIN32) - target_link_libraries(${PROJECT_NAME} PRIVATE d3d12 dxgi) + target_link_libraries(${PROJECT_NAME} PRIVATE d3d11 d3d12 dxgi) endif () endif () diff --git a/include/omath/hooks/hooks_manager.hpp b/include/omath/hooks/hooks_manager.hpp index c0c47fd..cf6aeb6 100644 --- a/include/omath/hooks/hooks_manager.hpp +++ b/include/omath/hooks/hooks_manager.hpp @@ -23,6 +23,7 @@ namespace omath::hooks { HooksManager() = default; public: + // IDXGISwapChain callbacks are shared between DX11 and DX12 — same interface, same signature. using present_callback = std::function; using resize_buffers_callback = std::function; using execute_command_lists_callback = std::function; @@ -34,39 +35,53 @@ namespace omath::hooks HooksManager& operator=(const HooksManager&) = delete; ~HooksManager(); + [[nodiscard]] bool hook_dx11(); + void unhook_dx11(); + [[nodiscard]] bool hook_dx12(); void unhook_dx12(); + // Present and ResizeBuffers callbacks fire for whichever API is hooked. void set_on_present(present_callback callback); void set_on_resize_buffers(resize_buffers_callback callback); + void set_on_execute_command_lists(execute_command_lists_callback callback); [[nodiscard]] bool hook_wnd_proc(HWND hwnd); void unhook_wnd_proc(); - void set_on_wnd_proc(wnd_proc_callback callback); private: - static HRESULT __stdcall present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags); - static HRESULT __stdcall resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count, - UINT width, UINT height, DXGI_FORMAT new_format, - UINT swap_chain_flags); - static void __stdcall execute_command_lists_detour(ID3D12CommandQueue* p_command_queue, - UINT num_command_lists, - ID3D12CommandList* const* pp_command_lists); + static HRESULT __stdcall dx11_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags); + static HRESULT __stdcall dx11_resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count, + UINT width, UINT height, DXGI_FORMAT new_format, + UINT swap_chain_flags); + + static HRESULT __stdcall dx12_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags); + static HRESULT __stdcall dx12_resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count, + UINT width, UINT height, DXGI_FORMAT new_format, + UINT swap_chain_flags); + static void __stdcall dx12_execute_command_lists_detour(ID3D12CommandQueue* p_command_queue, + UINT num_command_lists, + ID3D12CommandList* const* pp_command_lists); + static LRESULT __stdcall wnd_proc_detour(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param); mutable std::shared_mutex m_mutex; + bool m_is_dx11_hooked = false; bool m_is_dx12_hooked = false; bool m_is_wnd_proc_hooked = false; HWND m_hooked_hwnd = nullptr; WNDPROC m_original_wndproc = nullptr; - safetyhook::InlineHook m_present_hook; - safetyhook::InlineHook m_resize_buffers_hook; - safetyhook::InlineHook m_execute_command_lists_hook; + safetyhook::InlineHook m_dx11_present_hook; + safetyhook::InlineHook m_dx11_resize_buffers_hook; + + safetyhook::InlineHook m_dx12_present_hook; + safetyhook::InlineHook m_dx12_resize_buffers_hook; + safetyhook::InlineHook m_dx12_execute_command_lists_hook; present_callback m_present_cb; resize_buffers_callback m_resize_buffers_cb; diff --git a/source/hooks/hooks_manager.cpp b/source/hooks/hooks_manager.cpp index af8f331..1471688 100644 --- a/source/hooks/hooks_manager.cpp +++ b/source/hooks/hooks_manager.cpp @@ -1,6 +1,7 @@ #include "omath/hooks/hooks_manager.hpp" #ifdef OMATH_ENABLE_HOOKING +#include namespace { @@ -48,9 +49,89 @@ namespace omath::hooks HooksManager::~HooksManager() { unhook_wnd_proc(); + unhook_dx11(); unhook_dx12(); } + bool HooksManager::hook_dx11() + { + std::unique_lock lock(m_mutex); + if (m_is_dx11_hooked) + return true; + + const DummyWindow window; + if (!window.valid()) + return false; + + const HMODULE d3d11_module = GetModuleHandle("d3d11.dll"); + if (!d3d11_module) + return false; + + using d3d11_create_device_and_swap_chain_fn = + HRESULT(__stdcall*)(IDXGIAdapter*, D3D_DRIVER_TYPE, HMODULE, UINT, + const D3D_FEATURE_LEVEL*, UINT, UINT, + const DXGI_SWAP_CHAIN_DESC*, IDXGISwapChain**, + ID3D11Device**, D3D_FEATURE_LEVEL*, ID3D11DeviceContext**); + + const auto create_device_and_swap_chain = reinterpret_cast( + GetProcAddress(d3d11_module, "D3D11CreateDeviceAndSwapChain")); + if (!create_device_and_swap_chain) + return false; + + DXGI_SWAP_CHAIN_DESC swap_chain_desc{}; + swap_chain_desc.BufferDesc.Width = 100; + swap_chain_desc.BufferDesc.Height = 100; + swap_chain_desc.BufferDesc.RefreshRate = {60, 1}; + swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swap_chain_desc.SampleDesc = {1, 0}; + swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swap_chain_desc.BufferCount = 1; + swap_chain_desc.OutputWindow = window.handle(); + swap_chain_desc.Windowed = TRUE; + swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + + constexpr D3D_FEATURE_LEVEL feature_levels[] = {D3D_FEATURE_LEVEL_11_0}; + ID3D11Device* device = nullptr; + ID3D11DeviceContext* device_context = nullptr; + IDXGISwapChain* swap_chain = nullptr; + + if (FAILED(create_device_and_swap_chain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, + feature_levels, 1, D3D11_SDK_VERSION, + &swap_chain_desc, &swap_chain, + &device, nullptr, &device_context))) + return false; + + m_dx11_present_hook = safetyhook::create_inline( + vtable_fn(swap_chain, 8), // IDXGISwapChain::Present + reinterpret_cast(&dx11_present_detour)); + + m_dx11_resize_buffers_hook = safetyhook::create_inline( + vtable_fn(swap_chain, 13), // IDXGISwapChain::ResizeBuffers + reinterpret_cast(&dx11_resize_buffers_detour)); + + swap_chain->Release(); + device_context->Release(); + device->Release(); + + if (!m_dx11_present_hook || !m_dx11_resize_buffers_hook) + { + m_dx11_present_hook = {}; + m_dx11_resize_buffers_hook = {}; + return false; + } + + m_is_dx11_hooked = true; + return true; + } + + void HooksManager::unhook_dx11() + { + std::unique_lock lock(m_mutex); + m_dx11_present_hook = {}; + m_dx11_resize_buffers_hook = {}; + m_is_dx11_hooked = false; + } + bool HooksManager::hook_dx12() { std::unique_lock lock(m_mutex); @@ -157,19 +238,19 @@ namespace omath::hooks return false; } - // Read the 3 needed vtable slots directly — these addresses live in the DLL, + // Read the needed vtable slots directly — addresses live in the DLL, // not in the objects, so they remain valid after the objects are released. - m_present_hook = safetyhook::create_inline( - vtable_fn(swap_chain, 8), // IDXGISwapChain::Present - reinterpret_cast(&present_detour)); + m_dx12_present_hook = safetyhook::create_inline( + vtable_fn(swap_chain, 8), // IDXGISwapChain::Present + reinterpret_cast(&dx12_present_detour)); - m_resize_buffers_hook = safetyhook::create_inline( - vtable_fn(swap_chain, 13), // IDXGISwapChain::ResizeBuffers - reinterpret_cast(&resize_buffers_detour)); + m_dx12_resize_buffers_hook = safetyhook::create_inline( + vtable_fn(swap_chain, 13), // IDXGISwapChain::ResizeBuffers + reinterpret_cast(&dx12_resize_buffers_detour)); - m_execute_command_lists_hook = safetyhook::create_inline( + m_dx12_execute_command_lists_hook = safetyhook::create_inline( vtable_fn(command_queue, 8), // ID3D12CommandQueue::ExecuteCommandLists - reinterpret_cast(&execute_command_lists_detour)); + reinterpret_cast(&dx12_execute_command_lists_detour)); swap_chain->Release(); command_list->Release(); @@ -178,11 +259,11 @@ namespace omath::hooks device->Release(); factory->Release(); - if (!m_present_hook || !m_resize_buffers_hook || !m_execute_command_lists_hook) + if (!m_dx12_present_hook || !m_dx12_resize_buffers_hook || !m_dx12_execute_command_lists_hook) { - m_present_hook = {}; - m_resize_buffers_hook = {}; - m_execute_command_lists_hook = {}; + m_dx12_present_hook = {}; + m_dx12_resize_buffers_hook = {}; + m_dx12_execute_command_lists_hook = {}; return false; } @@ -193,9 +274,9 @@ namespace omath::hooks void HooksManager::unhook_dx12() { std::unique_lock lock(m_mutex); - m_present_hook = {}; - m_resize_buffers_hook = {}; - m_execute_command_lists_hook = {}; + m_dx12_present_hook = {}; + m_dx12_resize_buffers_hook = {}; + m_dx12_execute_command_lists_hook = {}; m_is_dx12_hooked = false; } @@ -254,7 +335,8 @@ namespace omath::hooks // Detour implementations: copy callback under shared lock, call it unlocked, // then call original. This avoids a deadlock if the callback itself calls set_on_*(). - HRESULT __stdcall HooksManager::present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags) + + HRESULT __stdcall HooksManager::dx11_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags) { auto& mgr = get(); present_callback cb; @@ -264,12 +346,12 @@ namespace omath::hooks } if (cb) cb(p_swap_chain, sync_interval, flags); - return mgr.m_present_hook.call(p_swap_chain, sync_interval, flags); + return mgr.m_dx11_present_hook.call(p_swap_chain, sync_interval, flags); } - HRESULT __stdcall HooksManager::resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count, - UINT width, UINT height, DXGI_FORMAT new_format, - UINT swap_chain_flags) + HRESULT __stdcall HooksManager::dx11_resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count, + UINT width, UINT height, DXGI_FORMAT new_format, + UINT swap_chain_flags) { auto& mgr = get(); resize_buffers_callback cb; @@ -279,13 +361,42 @@ namespace omath::hooks } if (cb) cb(p_swap_chain, buffer_count, width, height, new_format, swap_chain_flags); - return mgr.m_resize_buffers_hook.call(p_swap_chain, buffer_count, width, height, new_format, - swap_chain_flags); + return mgr.m_dx11_resize_buffers_hook.call(p_swap_chain, buffer_count, width, height, + new_format, swap_chain_flags); } - void __stdcall HooksManager::execute_command_lists_detour(ID3D12CommandQueue* p_command_queue, - UINT num_command_lists, - ID3D12CommandList* const* pp_command_lists) + HRESULT __stdcall HooksManager::dx12_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags) + { + auto& mgr = get(); + present_callback cb; + { + std::shared_lock lock(mgr.m_mutex); + cb = mgr.m_present_cb; + } + if (cb) + cb(p_swap_chain, sync_interval, flags); + return mgr.m_dx12_present_hook.call(p_swap_chain, sync_interval, flags); + } + + HRESULT __stdcall HooksManager::dx12_resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count, + UINT width, UINT height, DXGI_FORMAT new_format, + UINT swap_chain_flags) + { + auto& mgr = get(); + resize_buffers_callback cb; + { + std::shared_lock lock(mgr.m_mutex); + cb = mgr.m_resize_buffers_cb; + } + if (cb) + cb(p_swap_chain, buffer_count, width, height, new_format, swap_chain_flags); + return mgr.m_dx12_resize_buffers_hook.call(p_swap_chain, buffer_count, width, height, + new_format, swap_chain_flags); + } + + void __stdcall HooksManager::dx12_execute_command_lists_detour(ID3D12CommandQueue* p_command_queue, + UINT num_command_lists, + ID3D12CommandList* const* pp_command_lists) { auto& mgr = get(); execute_command_lists_callback cb; @@ -295,7 +406,7 @@ namespace omath::hooks } if (cb) cb(p_command_queue, num_command_lists, pp_command_lists); - mgr.m_execute_command_lists_hook.call(p_command_queue, num_command_lists, pp_command_lists); + mgr.m_dx12_execute_command_lists_hook.call(p_command_queue, num_command_lists, pp_command_lists); } LRESULT __stdcall HooksManager::wnd_proc_detour(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param) From 71171acf360b36a00db93057c26bbbdbf882d8ad Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 3 May 2026 21:58:51 +0300 Subject: [PATCH 04/11] added hooking of dx9 --- include/omath/hooks/hooks_manager.hpp | 36 ++++++- source/hooks/hooks_manager.cpp | 139 ++++++++++++++++++++++++++ 2 files changed, 171 insertions(+), 4 deletions(-) diff --git a/include/omath/hooks/hooks_manager.hpp b/include/omath/hooks/hooks_manager.hpp index cf6aeb6..e82da9d 100644 --- a/include/omath/hooks/hooks_manager.hpp +++ b/include/omath/hooks/hooks_manager.hpp @@ -14,6 +14,7 @@ #endif #include #include +#include #include #include @@ -23,28 +24,39 @@ namespace omath::hooks { HooksManager() = default; public: - // IDXGISwapChain callbacks are shared between DX11 and DX12 — same interface, same signature. + // IDXGISwapChain callbacks — shared between DX11 and DX12 (same interface, same signature). using present_callback = std::function; using resize_buffers_callback = std::function; using execute_command_lists_callback = std::function; + + // IDirect3DDevice9 callbacks — DX9 only. + using dx9_present_callback = std::function; + using dx9_reset_callback = std::function; + using dx9_end_scene_callback = std::function; + // Return nullopt to pass the message to the original WndProc; return a value to intercept it. - using wnd_proc_callback = std::function(HWND, UINT, WPARAM, LPARAM)>; + using wnd_proc_callback = std::function(HWND, UINT, WPARAM, LPARAM)>; [[nodiscard]] static HooksManager& get(); HooksManager(const HooksManager&) = delete; HooksManager& operator=(const HooksManager&) = delete; ~HooksManager(); + [[nodiscard]] bool hook_dx9(); + void unhook_dx9(); + void set_on_dx9_present(dx9_present_callback callback); + void set_on_dx9_reset(dx9_reset_callback callback); + void set_on_dx9_end_scene(dx9_end_scene_callback callback); + [[nodiscard]] bool hook_dx11(); void unhook_dx11(); [[nodiscard]] bool hook_dx12(); void unhook_dx12(); - // Present and ResizeBuffers callbacks fire for whichever API is hooked. + // Present and ResizeBuffers callbacks fire for whichever of DX11/DX12 is hooked. void set_on_present(present_callback callback); void set_on_resize_buffers(resize_buffers_callback callback); - void set_on_execute_command_lists(execute_command_lists_callback callback); [[nodiscard]] bool hook_wnd_proc(HWND hwnd); @@ -52,6 +64,13 @@ namespace omath::hooks void set_on_wnd_proc(wnd_proc_callback callback); private: + static HRESULT __stdcall dx9_present_detour(IDirect3DDevice9* p_device, const RECT* p_source_rect, + const RECT* p_dest_rect, HWND h_dest_window_override, + const RGNDATA* p_dirty_region); + static HRESULT __stdcall dx9_reset_detour(IDirect3DDevice9* p_device, + D3DPRESENT_PARAMETERS* p_presentation_parameters); + static HRESULT __stdcall dx9_end_scene_detour(IDirect3DDevice9* p_device); + static HRESULT __stdcall dx11_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags); static HRESULT __stdcall dx11_resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count, UINT width, UINT height, DXGI_FORMAT new_format, @@ -69,6 +88,7 @@ namespace omath::hooks mutable std::shared_mutex m_mutex; + bool m_is_dx9_hooked = false; bool m_is_dx11_hooked = false; bool m_is_dx12_hooked = false; bool m_is_wnd_proc_hooked = false; @@ -76,6 +96,10 @@ namespace omath::hooks HWND m_hooked_hwnd = nullptr; WNDPROC m_original_wndproc = nullptr; + safetyhook::InlineHook m_dx9_present_hook; + safetyhook::InlineHook m_dx9_reset_hook; + safetyhook::InlineHook m_dx9_end_scene_hook; + safetyhook::InlineHook m_dx11_present_hook; safetyhook::InlineHook m_dx11_resize_buffers_hook; @@ -83,6 +107,10 @@ namespace omath::hooks safetyhook::InlineHook m_dx12_resize_buffers_hook; safetyhook::InlineHook m_dx12_execute_command_lists_hook; + dx9_present_callback m_dx9_present_cb; + dx9_reset_callback m_dx9_reset_cb; + dx9_end_scene_callback m_dx9_end_scene_cb; + present_callback m_present_cb; resize_buffers_callback m_resize_buffers_cb; execute_command_lists_callback m_execute_command_lists_cb; diff --git a/source/hooks/hooks_manager.cpp b/source/hooks/hooks_manager.cpp index 1471688..0765a99 100644 --- a/source/hooks/hooks_manager.cpp +++ b/source/hooks/hooks_manager.cpp @@ -49,10 +49,106 @@ namespace omath::hooks HooksManager::~HooksManager() { unhook_wnd_proc(); + unhook_dx9(); unhook_dx11(); unhook_dx12(); } + bool HooksManager::hook_dx9() + { + std::unique_lock lock(m_mutex); + if (m_is_dx9_hooked) + return true; + + const DummyWindow window; + if (!window.valid()) + return false; + + const HMODULE d3d9_module = GetModuleHandle("d3d9.dll"); + if (!d3d9_module) + return false; + + using direct3d_create9_fn = IDirect3D9*(__stdcall*)(UINT); + const auto direct3d_create9 = reinterpret_cast( + GetProcAddress(d3d9_module, "Direct3DCreate9")); + if (!direct3d_create9) + return false; + + IDirect3D9* d3d9 = direct3d_create9(D3D_SDK_VERSION); + if (!d3d9) + return false; + + D3DPRESENT_PARAMETERS pp{}; + pp.SwapEffect = D3DSWAPEFFECT_DISCARD; + pp.hDeviceWindow = window.handle(); + pp.Windowed = TRUE; + + IDirect3DDevice9* device = nullptr; + if (FAILED(d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, window.handle(), + D3DCREATE_SOFTWARE_VERTEXPROCESSING, &pp, &device))) + { + d3d9->Release(); + return false; + } + + // IDirect3DDevice9 vtable indices (from IUnknown base): + // Reset = 16 + // Present = 17 + // EndScene = 42 + m_dx9_present_hook = safetyhook::create_inline( + vtable_fn(device, 17), + reinterpret_cast(&dx9_present_detour)); + + m_dx9_reset_hook = safetyhook::create_inline( + vtable_fn(device, 16), + reinterpret_cast(&dx9_reset_detour)); + + m_dx9_end_scene_hook = safetyhook::create_inline( + vtable_fn(device, 42), + reinterpret_cast(&dx9_end_scene_detour)); + + device->Release(); + d3d9->Release(); + + if (!m_dx9_present_hook || !m_dx9_reset_hook || !m_dx9_end_scene_hook) + { + m_dx9_present_hook = {}; + m_dx9_reset_hook = {}; + m_dx9_end_scene_hook = {}; + return false; + } + + m_is_dx9_hooked = true; + return true; + } + + void HooksManager::unhook_dx9() + { + std::unique_lock lock(m_mutex); + m_dx9_present_hook = {}; + m_dx9_reset_hook = {}; + m_dx9_end_scene_hook = {}; + m_is_dx9_hooked = false; + } + + void HooksManager::set_on_dx9_present(dx9_present_callback callback) + { + std::unique_lock lock(m_mutex); + m_dx9_present_cb = std::move(callback); + } + + void HooksManager::set_on_dx9_reset(dx9_reset_callback callback) + { + std::unique_lock lock(m_mutex); + m_dx9_reset_cb = std::move(callback); + } + + void HooksManager::set_on_dx9_end_scene(dx9_end_scene_callback callback) + { + std::unique_lock lock(m_mutex); + m_dx9_end_scene_cb = std::move(callback); + } + bool HooksManager::hook_dx11() { std::unique_lock lock(m_mutex); @@ -336,6 +432,49 @@ namespace omath::hooks // Detour implementations: copy callback under shared lock, call it unlocked, // then call original. This avoids a deadlock if the callback itself calls set_on_*(). + HRESULT __stdcall HooksManager::dx9_present_detour(IDirect3DDevice9* p_device, const RECT* p_source_rect, + const RECT* p_dest_rect, HWND h_dest_window_override, + const RGNDATA* p_dirty_region) + { + auto& mgr = get(); + dx9_present_callback cb; + { + std::shared_lock lock(mgr.m_mutex); + cb = mgr.m_dx9_present_cb; + } + if (cb) + cb(p_device, p_source_rect, p_dest_rect, h_dest_window_override, p_dirty_region); + return mgr.m_dx9_present_hook.call(p_device, p_source_rect, p_dest_rect, + h_dest_window_override, p_dirty_region); + } + + HRESULT __stdcall HooksManager::dx9_reset_detour(IDirect3DDevice9* p_device, + D3DPRESENT_PARAMETERS* p_presentation_parameters) + { + auto& mgr = get(); + dx9_reset_callback cb; + { + std::shared_lock lock(mgr.m_mutex); + cb = mgr.m_dx9_reset_cb; + } + if (cb) + cb(p_device, p_presentation_parameters); + return mgr.m_dx9_reset_hook.call(p_device, p_presentation_parameters); + } + + HRESULT __stdcall HooksManager::dx9_end_scene_detour(IDirect3DDevice9* p_device) + { + auto& mgr = get(); + dx9_end_scene_callback cb; + { + std::shared_lock lock(mgr.m_mutex); + cb = mgr.m_dx9_end_scene_cb; + } + if (cb) + cb(p_device); + return mgr.m_dx9_end_scene_hook.call(p_device); + } + HRESULT __stdcall HooksManager::dx11_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags) { auto& mgr = get(); From 3aba53c8f89c369701d51a5cd21486e279551375 Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 3 May 2026 21:59:48 +0300 Subject: [PATCH 05/11] fix --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3343416..d6fdb62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,7 +110,7 @@ if (OMATH_ENABLE_HOOKING) target_link_libraries(${PROJECT_NAME} PRIVATE safetyhook::safetyhook) if (WIN32) - target_link_libraries(${PROJECT_NAME} PRIVATE d3d11 d3d12 dxgi) + target_link_libraries(${PROJECT_NAME} PRIVATE d3d9 d3d11 d3d12 dxgi) endif () endif () From 105df90d05183278546edaebe502832f60a7533d Mon Sep 17 00:00:00 2001 From: Orange Date: Sun, 3 May 2026 22:16:16 +0300 Subject: [PATCH 06/11] decomposed method --- source/hooks/hooks_manager.cpp | 218 +++++++++++++++++---------------- 1 file changed, 112 insertions(+), 106 deletions(-) diff --git a/source/hooks/hooks_manager.cpp b/source/hooks/hooks_manager.cpp index 0765a99..3ce8e6b 100644 --- a/source/hooks/hooks_manager.cpp +++ b/source/hooks/hooks_manager.cpp @@ -36,6 +36,113 @@ namespace { return (*reinterpret_cast(com_obj))[index]; } + + struct dx12_vtable_fns + { + void* present; + void* resize_buffers; + void* execute_command_lists; + }; + + // RAII wrapper so all early-return paths release COM objects automatically. + struct dx12_com_objects + { + IDXGIFactory* factory = nullptr; + ID3D12Device* device = nullptr; + ID3D12CommandQueue* command_queue = nullptr; + ID3D12CommandAllocator* command_allocator = nullptr; + ID3D12GraphicsCommandList* command_list = nullptr; + IDXGISwapChain* swap_chain = nullptr; + + dx12_com_objects() = default; + dx12_com_objects(const dx12_com_objects&) = delete; + dx12_com_objects& operator=(const dx12_com_objects&) = delete; + + ~dx12_com_objects() + { + if (swap_chain) swap_chain->Release(); + if (command_list) command_list->Release(); + if (command_allocator) command_allocator->Release(); + if (command_queue) command_queue->Release(); + if (device) device->Release(); + if (factory) factory->Release(); + } + }; + + std::optional read_dx12_vtable_fns(HWND hwnd) + { + using create_dxgi_factory_fn = HRESULT(__stdcall*)(REFIID, void**); + using d3d12_create_device_fn = HRESULT(__stdcall*)(IUnknown*, D3D_FEATURE_LEVEL, REFIID, void**); + + const HMODULE d3d12_module = GetModuleHandle("d3d12.dll"); + const HMODULE dxgi_module = GetModuleHandle("dxgi.dll"); + if (!d3d12_module || !dxgi_module) + return std::nullopt; + + const auto create_dxgi_factory = reinterpret_cast( + GetProcAddress(dxgi_module, "CreateDXGIFactory")); + const auto d3d12_create_device = reinterpret_cast( + GetProcAddress(d3d12_module, "D3D12CreateDevice")); + + if (!create_dxgi_factory || !d3d12_create_device) + return std::nullopt; + + dx12_com_objects objs; + + if (FAILED(create_dxgi_factory(__uuidof(IDXGIFactory), reinterpret_cast(&objs.factory)))) + return std::nullopt; + + IDXGIAdapter* adapter = nullptr; + if (objs.factory->EnumAdapters(0, &adapter) == DXGI_ERROR_NOT_FOUND) + return std::nullopt; + + const HRESULT device_hr = d3d12_create_device(adapter, D3D_FEATURE_LEVEL_11_0, + __uuidof(ID3D12Device), + reinterpret_cast(&objs.device)); + adapter->Release(); + if (FAILED(device_hr)) + return std::nullopt; + + D3D12_COMMAND_QUEUE_DESC queue_desc{}; + queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + + if (FAILED(objs.device->CreateCommandQueue(&queue_desc, __uuidof(ID3D12CommandQueue), + reinterpret_cast(&objs.command_queue)))) + return std::nullopt; + + if (FAILED(objs.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, + __uuidof(ID3D12CommandAllocator), + reinterpret_cast(&objs.command_allocator)))) + return std::nullopt; + + if (FAILED(objs.device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, objs.command_allocator, + nullptr, __uuidof(ID3D12GraphicsCommandList), + reinterpret_cast(&objs.command_list)))) + return std::nullopt; + + DXGI_SWAP_CHAIN_DESC swap_chain_desc{}; + swap_chain_desc.BufferDesc.Width = 100; + swap_chain_desc.BufferDesc.Height = 100; + swap_chain_desc.BufferDesc.RefreshRate = {60, 1}; + swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swap_chain_desc.SampleDesc = {1, 0}; + swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swap_chain_desc.BufferCount = 2; + swap_chain_desc.OutputWindow = hwnd; + swap_chain_desc.Windowed = TRUE; + swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; + + if (FAILED(objs.factory->CreateSwapChain(objs.command_queue, &swap_chain_desc, &objs.swap_chain))) + return std::nullopt; + + // objs destructor releases all COM objects after we capture the addresses. + return dx12_vtable_fns{ + vtable_fn(objs.swap_chain, 8), // IDXGISwapChain::Present + vtable_fn(objs.swap_chain, 13), // IDXGISwapChain::ResizeBuffers + vtable_fn(objs.command_queue, 8), // ID3D12CommandQueue::ExecuteCommandLists + }; + } } // namespace namespace omath::hooks @@ -238,123 +345,22 @@ namespace omath::hooks if (!window.valid()) return false; - const HMODULE d3d12_module = GetModuleHandle("d3d12.dll"); - const HMODULE dxgi_module = GetModuleHandle("dxgi.dll"); - if (!d3d12_module || !dxgi_module) + const auto fns = read_dx12_vtable_fns(window.handle()); + if (!fns) return false; - using create_dxgi_factory_fn = HRESULT(__stdcall*)(REFIID, void**); - using d3d12_create_device_fn = HRESULT(__stdcall*)(IUnknown*, D3D_FEATURE_LEVEL, REFIID, void**); - - const auto create_dxgi_factory = reinterpret_cast( - GetProcAddress(dxgi_module, "CreateDXGIFactory")); - const auto d3d12_create_device = reinterpret_cast( - GetProcAddress(d3d12_module, "D3D12CreateDevice")); - - if (!create_dxgi_factory || !d3d12_create_device) - return false; - - IDXGIFactory* factory = nullptr; - if (FAILED(create_dxgi_factory(__uuidof(IDXGIFactory), reinterpret_cast(&factory)))) - return false; - - IDXGIAdapter* adapter = nullptr; - if (factory->EnumAdapters(0, &adapter) == DXGI_ERROR_NOT_FOUND) - { - factory->Release(); - return false; - } - - ID3D12Device* device = nullptr; - if (FAILED(d3d12_create_device(adapter, D3D_FEATURE_LEVEL_11_0, - __uuidof(ID3D12Device), reinterpret_cast(&device)))) - { - adapter->Release(); - factory->Release(); - return false; - } - adapter->Release(); - - D3D12_COMMAND_QUEUE_DESC queue_desc{}; - queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; - - ID3D12CommandQueue* command_queue = nullptr; - if (FAILED(device->CreateCommandQueue(&queue_desc, __uuidof(ID3D12CommandQueue), - reinterpret_cast(&command_queue)))) - { - device->Release(); - factory->Release(); - return false; - } - - ID3D12CommandAllocator* command_allocator = nullptr; - if (FAILED(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, - __uuidof(ID3D12CommandAllocator), - reinterpret_cast(&command_allocator)))) - { - command_queue->Release(); - device->Release(); - factory->Release(); - return false; - } - - ID3D12GraphicsCommandList* command_list = nullptr; - if (FAILED(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, command_allocator, nullptr, - __uuidof(ID3D12GraphicsCommandList), - reinterpret_cast(&command_list)))) - { - command_allocator->Release(); - command_queue->Release(); - device->Release(); - factory->Release(); - return false; - } - - DXGI_SWAP_CHAIN_DESC swap_chain_desc{}; - swap_chain_desc.BufferDesc.Width = 100; - swap_chain_desc.BufferDesc.Height = 100; - swap_chain_desc.BufferDesc.RefreshRate = {60, 1}; - swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - swap_chain_desc.SampleDesc = {1, 0}; - swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - swap_chain_desc.BufferCount = 2; - swap_chain_desc.OutputWindow = window.handle(); - swap_chain_desc.Windowed = TRUE; - swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; - swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; - - IDXGISwapChain* swap_chain = nullptr; - if (FAILED(factory->CreateSwapChain(command_queue, &swap_chain_desc, &swap_chain))) - { - command_list->Release(); - command_allocator->Release(); - command_queue->Release(); - device->Release(); - factory->Release(); - return false; - } - - // Read the needed vtable slots directly — addresses live in the DLL, - // not in the objects, so they remain valid after the objects are released. m_dx12_present_hook = safetyhook::create_inline( - vtable_fn(swap_chain, 8), // IDXGISwapChain::Present + fns->present, reinterpret_cast(&dx12_present_detour)); m_dx12_resize_buffers_hook = safetyhook::create_inline( - vtable_fn(swap_chain, 13), // IDXGISwapChain::ResizeBuffers + fns->resize_buffers, reinterpret_cast(&dx12_resize_buffers_detour)); m_dx12_execute_command_lists_hook = safetyhook::create_inline( - vtable_fn(command_queue, 8), // ID3D12CommandQueue::ExecuteCommandLists + fns->execute_command_lists, reinterpret_cast(&dx12_execute_command_lists_detour)); - swap_chain->Release(); - command_list->Release(); - command_allocator->Release(); - command_queue->Release(); - device->Release(); - factory->Release(); - if (!m_dx12_present_hook || !m_dx12_resize_buffers_hook || !m_dx12_execute_command_lists_hook) { m_dx12_present_hook = {}; From 232b48c3dd01f1a165c392e8370a24130ec128fb Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 4 May 2026 00:10:53 +0300 Subject: [PATCH 07/11] fixed dx12 hook --- examples/CMakeLists.txt | 13 ++ examples/example_dx11_hook/CMakeLists.txt | 12 ++ examples/example_dx11_hook/dllmain.cpp | 138 ++++++++++++++ examples/example_dx12_hook/CMakeLists.txt | 12 ++ examples/example_dx12_hook/dllmain.cpp | 215 ++++++++++++++++++++++ examples/example_dx9_hook/CMakeLists.txt | 12 ++ examples/example_dx9_hook/dllmain.cpp | 107 +++++++++++ source/hooks/hooks_manager.cpp | 2 +- 8 files changed, 510 insertions(+), 1 deletion(-) create mode 100644 examples/example_dx11_hook/CMakeLists.txt create mode 100644 examples/example_dx11_hook/dllmain.cpp create mode 100644 examples/example_dx12_hook/CMakeLists.txt create mode 100644 examples/example_dx12_hook/dllmain.cpp create mode 100644 examples/example_dx9_hook/CMakeLists.txt create mode 100644 examples/example_dx9_hook/dllmain.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 026b487..200ab71 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -4,6 +4,19 @@ add_subdirectory(example_proj_mat_builder) add_subdirectory(example_signature_scan) add_subdirectory(example_hud) +if(OMATH_ENABLE_HOOKING AND WIN32) + # Requires imgui with dx9-binding, dx11-binding, dx12-binding, win32-binding. + # Install via: vcpkg install imgui[dx9-binding,dx11-binding,dx12-binding,win32-binding] + find_package(imgui CONFIG QUIET) + if(imgui_FOUND) + add_subdirectory(example_dx9_hook) + add_subdirectory(example_dx11_hook) + add_subdirectory(example_dx12_hook) + else() + message(STATUS "[omath] imgui not found — DX hook examples skipped") + endif() +endif() + if(OMATH_ENABLE_VALGRIND) omath_setup_valgrind(example_projection_matrix_builder) omath_setup_valgrind(example_signature_scan) diff --git a/examples/example_dx11_hook/CMakeLists.txt b/examples/example_dx11_hook/CMakeLists.txt new file mode 100644 index 0000000..d0d0978 --- /dev/null +++ b/examples/example_dx11_hook/CMakeLists.txt @@ -0,0 +1,12 @@ +project(example_dx11_hook) + +add_library(${PROJECT_NAME} SHARED dllmain.cpp) + +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 23 + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}") + +find_package(imgui CONFIG REQUIRED) +target_link_libraries(${PROJECT_NAME} PRIVATE omath::omath imgui::imgui d3d11 dxgi) diff --git a/examples/example_dx11_hook/dllmain.cpp b/examples/example_dx11_hook/dllmain.cpp new file mode 100644 index 0000000..dcfe94c --- /dev/null +++ b/examples/example_dx11_hook/dllmain.cpp @@ -0,0 +1,138 @@ +#include +#include +#include +#include +#include +#include + +#include "omath/hooks/hooks_manager.hpp" + +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM); + +namespace +{ + bool g_initialized = false; + bool g_init_attempted = false; + + ID3D11Device* g_device = nullptr; + ID3D11DeviceContext* g_context = nullptr; + ID3D11RenderTargetView* g_render_target_view = nullptr; + + void create_render_target(IDXGISwapChain* swap_chain) + { + ID3D11Texture2D* back_buffer = nullptr; + if (FAILED(swap_chain->GetBuffer(0, IID_PPV_ARGS(&back_buffer)))) + return; + g_device->CreateRenderTargetView(back_buffer, nullptr, &g_render_target_view); + back_buffer->Release(); + } + + void init(IDXGISwapChain* swap_chain) + { + g_init_attempted = true; + + if (FAILED(swap_chain->GetDevice(IID_PPV_ARGS(&g_device)))) + return; + + g_device->GetImmediateContext(&g_context); + + DXGI_SWAP_CHAIN_DESC desc{}; + swap_chain->GetDesc(&desc); + + create_render_target(swap_chain); + + ImGui::CreateContext(); + ImGui::StyleColorsDark(); + ImGui::GetIO().IniFilename = nullptr; + ImGui::GetIO().LogFilename = nullptr; + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange; + + ImGui_ImplWin32_Init(desc.OutputWindow); + ImGui_ImplDX11_Init(g_device, g_context); + + auto& mgr = omath::hooks::HooksManager::get(); + mgr.set_on_wnd_proc([](HWND h, UINT msg, WPARAM wp, LPARAM lp) -> std::optional { + if (ImGui_ImplWin32_WndProcHandler(h, msg, wp, lp)) + return 0; + return std::nullopt; + }); + mgr.hook_wnd_proc(desc.OutputWindow); + + g_initialized = true; + } + + void on_present(IDXGISwapChain* swap_chain, UINT, UINT) + { + if (!g_initialized) + { + if (!g_init_attempted) + init(swap_chain); + return; + } + + if (!g_render_target_view) + create_render_target(swap_chain); + + g_context->OMSetRenderTargets(1, &g_render_target_view, nullptr); + + ImGui_ImplDX11_NewFrame(); + ImGui_ImplWin32_NewFrame(); + ImGui::NewFrame(); + + ImGui::SetNextWindowSize({300.f, 80.f}, ImGuiCond_Once); + ImGui::SetNextWindowPos({10.f, 10.f}, ImGuiCond_Once); + ImGui::Begin("omath | DX11 hook"); + ImGui::Text("Hook active"); + ImGui::Text("FPS: %.1f", ImGui::GetIO().Framerate); + ImGui::End(); + + ImGui::Render(); + ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); + } + + void on_resize_buffers(IDXGISwapChain*, UINT, UINT, UINT, DXGI_FORMAT, UINT) + { + if (g_render_target_view) + { + g_render_target_view->Release(); + g_render_target_view = nullptr; + } + } +} // namespace + +BOOL WINAPI DllMain(HINSTANCE h_instance, DWORD reason, LPVOID) +{ + if (reason == DLL_PROCESS_ATTACH) + { + DisableThreadLibraryCalls(h_instance); + CreateThread(nullptr, 0, [](LPVOID) -> DWORD + { + while (!GetModuleHandle("d3d11.dll")) + Sleep(100); + + auto& mgr = omath::hooks::HooksManager::get(); + mgr.set_on_present(on_present); + mgr.set_on_resize_buffers(on_resize_buffers); + mgr.hook_dx11(); + return 0; + }, nullptr, 0, nullptr); + } + else if (reason == DLL_PROCESS_DETACH) + { + auto& mgr = omath::hooks::HooksManager::get(); + mgr.unhook_wnd_proc(); + mgr.unhook_dx11(); + + if (g_initialized) + { + ImGui_ImplDX11_Shutdown(); + ImGui_ImplWin32_Shutdown(); + ImGui::DestroyContext(); + } + + if (g_render_target_view) { g_render_target_view->Release(); g_render_target_view = nullptr; } + if (g_context) { g_context->Release(); g_context = nullptr; } + if (g_device) { g_device->Release(); g_device = nullptr; } + } + return TRUE; +} diff --git a/examples/example_dx12_hook/CMakeLists.txt b/examples/example_dx12_hook/CMakeLists.txt new file mode 100644 index 0000000..2fc353c --- /dev/null +++ b/examples/example_dx12_hook/CMakeLists.txt @@ -0,0 +1,12 @@ +project(example_dx12_hook) +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +add_library(${PROJECT_NAME} MODULE dllmain.cpp) + +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 23 + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}") + +find_package(imgui CONFIG REQUIRED) +target_link_libraries(${PROJECT_NAME} PRIVATE omath::omath imgui::imgui d3d12 dxgi) diff --git a/examples/example_dx12_hook/dllmain.cpp b/examples/example_dx12_hook/dllmain.cpp new file mode 100644 index 0000000..4194960 --- /dev/null +++ b/examples/example_dx12_hook/dllmain.cpp @@ -0,0 +1,215 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "omath/hooks/hooks_manager.hpp" + +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM); + +namespace +{ + struct frame_context + { + ID3D12CommandAllocator* command_allocator = nullptr; + ID3D12Resource* render_target = nullptr; + }; + + bool g_initialized = false; + bool g_init_attempted = false; + + ID3D12Device* g_device = nullptr; + ID3D12CommandQueue* g_command_queue = nullptr; + IDXGISwapChain3* g_swap_chain = nullptr; + ID3D12DescriptorHeap* g_rtv_heap = nullptr; + ID3D12DescriptorHeap* g_srv_heap = nullptr; + ID3D12GraphicsCommandList* g_command_list = nullptr; + std::vector g_frames; + UINT g_rtv_descriptor_size = 0; + + void init(IDXGISwapChain* swap_chain) + { + g_init_attempted = true; + + if (FAILED(swap_chain->QueryInterface(IID_PPV_ARGS(&g_swap_chain)))) + return; + + if (FAILED(swap_chain->GetDevice(IID_PPV_ARGS(&g_device)))) + return; + + DXGI_SWAP_CHAIN_DESC desc{}; + swap_chain->GetDesc(&desc); + const UINT buffer_count = desc.BufferCount; + + { + D3D12_DESCRIPTOR_HEAP_DESC heap_desc{}; + heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + heap_desc.NumDescriptors = 1; + heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + if (FAILED(g_device->CreateDescriptorHeap(&heap_desc, IID_PPV_ARGS(&g_srv_heap)))) + return; + } + { + D3D12_DESCRIPTOR_HEAP_DESC heap_desc{}; + heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; + heap_desc.NumDescriptors = buffer_count; + if (FAILED(g_device->CreateDescriptorHeap(&heap_desc, IID_PPV_ARGS(&g_rtv_heap)))) + return; + } + + g_rtv_descriptor_size = g_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + g_frames.resize(buffer_count); + + D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = g_rtv_heap->GetCPUDescriptorHandleForHeapStart(); + for (UINT i = 0; i < buffer_count; ++i) + { + if (FAILED(g_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, + IID_PPV_ARGS(&g_frames[i].command_allocator)))) + return; + if (FAILED(swap_chain->GetBuffer(i, IID_PPV_ARGS(&g_frames[i].render_target)))) + return; + g_device->CreateRenderTargetView(g_frames[i].render_target, nullptr, rtv_handle); + rtv_handle.ptr += g_rtv_descriptor_size; + } + + if (FAILED(g_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, + g_frames[0].command_allocator, nullptr, + IID_PPV_ARGS(&g_command_list)))) + return; + g_command_list->Close(); + + ImGui::CreateContext(); + ImGui::StyleColorsDark(); + ImGui::GetIO().IniFilename = nullptr; + ImGui::GetIO().LogFilename = nullptr; + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange; + + ImGui_ImplWin32_Init(desc.OutputWindow); + ImGui_ImplDX12_Init(g_device, static_cast(buffer_count), + desc.BufferDesc.Format, g_srv_heap, + g_srv_heap->GetCPUDescriptorHandleForHeapStart(), + g_srv_heap->GetGPUDescriptorHandleForHeapStart()); + + auto& mgr = omath::hooks::HooksManager::get(); + mgr.set_on_wnd_proc([](HWND h, UINT msg, WPARAM wp, LPARAM lp) -> std::optional { + if (ImGui_ImplWin32_WndProcHandler(h, msg, wp, lp)) + return 0; + return std::nullopt; + }); + mgr.hook_wnd_proc(desc.OutputWindow); + + g_initialized = true; + } + + void on_execute_command_lists(ID3D12CommandQueue* queue, UINT, ID3D12CommandList* const*) + { + if (!g_command_queue) + g_command_queue = queue; + } + + void on_present(IDXGISwapChain* swap_chain, UINT, UINT) + { + // Delay init until we have the command queue (captured from ExecuteCommandLists). + if (!g_initialized) + { + if (!g_init_attempted && g_command_queue) + init(swap_chain); + return; + } + + const UINT buf_idx = g_swap_chain->GetCurrentBackBufferIndex(); + auto& fc = g_frames[buf_idx]; + + fc.command_allocator->Reset(); + g_command_list->Reset(fc.command_allocator, nullptr); + + // Transition back buffer: Present → RenderTarget + D3D12_RESOURCE_BARRIER barrier{}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Transition.pResource = fc.render_target; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; + g_command_list->ResourceBarrier(1, &barrier); + + D3D12_CPU_DESCRIPTOR_HANDLE rtv = g_rtv_heap->GetCPUDescriptorHandleForHeapStart(); + rtv.ptr += buf_idx * g_rtv_descriptor_size; + g_command_list->OMSetRenderTargets(1, &rtv, FALSE, nullptr); + g_command_list->SetDescriptorHeaps(1, &g_srv_heap); + + ImGui_ImplDX12_NewFrame(); + ImGui_ImplWin32_NewFrame(); + ImGui::NewFrame(); + + ImGui::SetNextWindowSize({300.f, 80.f}, ImGuiCond_Once); + ImGui::SetNextWindowPos({10.f, 10.f}, ImGuiCond_Once); + ImGui::Begin("omath | DX12 hook"); + ImGui::Text("Hook active"); + ImGui::Text("FPS: %.1f", ImGui::GetIO().Framerate); + ImGui::End(); + + ImGui::Render(); + ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), g_command_list); + + // Transition back buffer: RenderTarget → Present + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; + g_command_list->ResourceBarrier(1, &barrier); + g_command_list->Close(); + + ID3D12CommandList* cmd_lists[] = {g_command_list}; + g_command_queue->ExecuteCommandLists(1, cmd_lists); + } + + void release_dx12_resources() + { + for (auto& fc : g_frames) + { + if (fc.command_allocator) { fc.command_allocator->Release(); fc.command_allocator = nullptr; } + if (fc.render_target) { fc.render_target->Release(); fc.render_target = nullptr; } + } + g_frames.clear(); + if (g_command_list) { g_command_list->Release(); g_command_list = nullptr; } + if (g_srv_heap) { g_srv_heap->Release(); g_srv_heap = nullptr; } + if (g_rtv_heap) { g_rtv_heap->Release(); g_rtv_heap = nullptr; } + if (g_swap_chain) { g_swap_chain->Release(); g_swap_chain = nullptr; } + if (g_device) { g_device->Release(); g_device = nullptr; } + } +} // namespace + +BOOL WINAPI DllMain(HINSTANCE h_instance, DWORD reason, LPVOID) +{ + if (reason == DLL_PROCESS_ATTACH) + { + DisableThreadLibraryCalls(h_instance); + CreateThread(nullptr, 0, [](LPVOID) -> DWORD + { + while (!GetModuleHandle("d3d12.dll")) + Sleep(100); + + auto& mgr = omath::hooks::HooksManager::get(); + mgr.set_on_present(on_present); + mgr.set_on_execute_command_lists(on_execute_command_lists); + mgr.hook_dx12(); + return 0; + }, nullptr, 0, nullptr); + } + else if (reason == DLL_PROCESS_DETACH) + { + auto& mgr = omath::hooks::HooksManager::get(); + mgr.unhook_wnd_proc(); + mgr.unhook_dx12(); + + if (g_initialized) + { + ImGui_ImplDX12_Shutdown(); + ImGui_ImplWin32_Shutdown(); + ImGui::DestroyContext(); + } + release_dx12_resources(); + } + return TRUE; +} diff --git a/examples/example_dx9_hook/CMakeLists.txt b/examples/example_dx9_hook/CMakeLists.txt new file mode 100644 index 0000000..c1dd44c --- /dev/null +++ b/examples/example_dx9_hook/CMakeLists.txt @@ -0,0 +1,12 @@ +project(example_dx9_hook) +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +add_library(${PROJECT_NAME} MODULE dllmain.cpp) + +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 23 + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}") + +find_package(imgui CONFIG REQUIRED) +target_link_libraries(${PROJECT_NAME} PRIVATE omath::omath imgui::imgui d3d9) diff --git a/examples/example_dx9_hook/dllmain.cpp b/examples/example_dx9_hook/dllmain.cpp new file mode 100644 index 0000000..1d4820e --- /dev/null +++ b/examples/example_dx9_hook/dllmain.cpp @@ -0,0 +1,107 @@ +#include +#include +#include +#include +#include + +#include "omath/hooks/hooks_manager.hpp" + +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM); + +namespace +{ + bool g_initialized = false; + bool g_init_attempted = false; + + void init(IDirect3DDevice9* device) + { + g_init_attempted = true; + + D3DDEVICE_CREATION_PARAMETERS params{}; + if (FAILED(device->GetCreationParameters(¶ms))) + return; + + ImGui::CreateContext(); + ImGui::StyleColorsDark(); + ImGui::GetIO().IniFilename = nullptr; + ImGui::GetIO().LogFilename = nullptr; + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange; + + ImGui_ImplWin32_Init(params.hFocusWindow); + ImGui_ImplDX9_Init(device); + + auto& mgr = omath::hooks::HooksManager::get(); + mgr.set_on_wnd_proc([](HWND h, UINT msg, WPARAM wp, LPARAM lp) -> std::optional { + if (ImGui_ImplWin32_WndProcHandler(h, msg, wp, lp)) + return 0; + return std::nullopt; + }); + mgr.hook_wnd_proc(params.hFocusWindow); + + g_initialized = true; + } + + void on_present(IDirect3DDevice9* device, const RECT*, const RECT*, HWND, const RGNDATA*) + { + if (!g_initialized) + { + if (!g_init_attempted) + init(device); + return; + } + + ImGui_ImplDX9_NewFrame(); + ImGui_ImplWin32_NewFrame(); + ImGui::NewFrame(); + + ImGui::SetNextWindowSize({300.f, 80.f}, ImGuiCond_Once); + ImGui::SetNextWindowPos({10.f, 10.f}, ImGuiCond_Once); + ImGui::Begin("omath | DX9 hook"); + ImGui::Text("Hook active"); + ImGui::Text("FPS: %.1f", ImGui::GetIO().Framerate); + ImGui::End(); + + ImGui::EndFrame(); + ImGui::Render(); + ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData()); + } + + void on_reset(IDirect3DDevice9*, D3DPRESENT_PARAMETERS*) + { + if (g_initialized) + ImGui_ImplDX9_InvalidateDeviceObjects(); + } +} // namespace + +BOOL WINAPI DllMain(HINSTANCE h_instance, DWORD reason, LPVOID) +{ + if (reason == DLL_PROCESS_ATTACH) + { + DisableThreadLibraryCalls(h_instance); + CreateThread(nullptr, 0, [](LPVOID) -> DWORD + { + while (!GetModuleHandle("d3d9.dll")) + Sleep(100); + + auto& mgr = omath::hooks::HooksManager::get(); + mgr.set_on_dx9_present(on_present); + mgr.set_on_dx9_reset(on_reset); + mgr.hook_dx9(); + return 0; + }, nullptr, 0, nullptr); + } + else if (reason == DLL_PROCESS_DETACH) + { + auto& mgr = omath::hooks::HooksManager::get(); + mgr.unhook_wnd_proc(); + mgr.unhook_dx9(); + + if (g_initialized) + { + ImGui_ImplDX9_Shutdown(); + ImGui_ImplWin32_Shutdown(); + ImGui::DestroyContext(); + } + } + return TRUE; +} diff --git a/source/hooks/hooks_manager.cpp b/source/hooks/hooks_manager.cpp index 3ce8e6b..0787695 100644 --- a/source/hooks/hooks_manager.cpp +++ b/source/hooks/hooks_manager.cpp @@ -140,7 +140,7 @@ namespace return dx12_vtable_fns{ vtable_fn(objs.swap_chain, 8), // IDXGISwapChain::Present vtable_fn(objs.swap_chain, 13), // IDXGISwapChain::ResizeBuffers - vtable_fn(objs.command_queue, 8), // ID3D12CommandQueue::ExecuteCommandLists + vtable_fn(objs.command_queue, 10), // ID3D12CommandQueue::ExecuteCommandLists }; } } // namespace From 51bf4461ff2ab622530fa0177ce2d4e2142161e6 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 4 May 2026 00:45:48 +0300 Subject: [PATCH 08/11] fixed dx12 overlay --- examples/example_dx12_hook/dllmain.cpp | 104 +++++++++++++------------ vcpkg.json | 17 +++- 2 files changed, 72 insertions(+), 49 deletions(-) diff --git a/examples/example_dx12_hook/dllmain.cpp b/examples/example_dx12_hook/dllmain.cpp index 4194960..cf22164 100644 --- a/examples/example_dx12_hook/dllmain.cpp +++ b/examples/example_dx12_hook/dllmain.cpp @@ -9,26 +9,27 @@ #include "omath/hooks/hooks_manager.hpp" extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM); +bool show_menu = true; namespace { struct frame_context { - ID3D12CommandAllocator* command_allocator = nullptr; - ID3D12Resource* render_target = nullptr; + ID3D12Resource* render_target = nullptr; + D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = {}; }; bool g_initialized = false; bool g_init_attempted = false; - ID3D12Device* g_device = nullptr; - ID3D12CommandQueue* g_command_queue = nullptr; - IDXGISwapChain3* g_swap_chain = nullptr; - ID3D12DescriptorHeap* g_rtv_heap = nullptr; - ID3D12DescriptorHeap* g_srv_heap = nullptr; - ID3D12GraphicsCommandList* g_command_list = nullptr; + ID3D12Device* g_device = nullptr; + ID3D12CommandQueue* g_command_queue = nullptr; + IDXGISwapChain3* g_swap_chain = nullptr; + ID3D12DescriptorHeap* g_rtv_heap = nullptr; + ID3D12DescriptorHeap* g_srv_heap = nullptr; + ID3D12GraphicsCommandList* g_command_list = nullptr; + ID3D12CommandAllocator* g_command_allocator = nullptr; std::vector g_frames; - UINT g_rtv_descriptor_size = 0; void init(IDXGISwapChain* swap_chain) { @@ -47,7 +48,7 @@ namespace { D3D12_DESCRIPTOR_HEAP_DESC heap_desc{}; heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; - heap_desc.NumDescriptors = 1; + heap_desc.NumDescriptors = buffer_count; heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; if (FAILED(g_device->CreateDescriptorHeap(&heap_desc, IID_PPV_ARGS(&g_srv_heap)))) return; @@ -56,27 +57,31 @@ namespace D3D12_DESCRIPTOR_HEAP_DESC heap_desc{}; heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; heap_desc.NumDescriptors = buffer_count; + heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + heap_desc.NodeMask = 1; if (FAILED(g_device->CreateDescriptorHeap(&heap_desc, IID_PPV_ARGS(&g_rtv_heap)))) return; } - g_rtv_descriptor_size = g_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); - g_frames.resize(buffer_count); + if (FAILED(g_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, + IID_PPV_ARGS(&g_command_allocator)))) + return; + g_frames.resize(buffer_count); + const UINT rtv_size = g_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = g_rtv_heap->GetCPUDescriptorHandleForHeapStart(); + for (UINT i = 0; i < buffer_count; ++i) { - if (FAILED(g_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, - IID_PPV_ARGS(&g_frames[i].command_allocator)))) - return; + g_frames[i].rtv_handle = rtv_handle; if (FAILED(swap_chain->GetBuffer(i, IID_PPV_ARGS(&g_frames[i].render_target)))) return; g_device->CreateRenderTargetView(g_frames[i].render_target, nullptr, rtv_handle); - rtv_handle.ptr += g_rtv_descriptor_size; + rtv_handle.ptr += rtv_size; } if (FAILED(g_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, - g_frames[0].command_allocator, nullptr, + g_command_allocator, nullptr, IID_PPV_ARGS(&g_command_list)))) return; g_command_list->Close(); @@ -85,19 +90,22 @@ namespace ImGui::StyleColorsDark(); ImGui::GetIO().IniFilename = nullptr; ImGui::GetIO().LogFilename = nullptr; - ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange; + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; ImGui_ImplWin32_Init(desc.OutputWindow); ImGui_ImplDX12_Init(g_device, static_cast(buffer_count), desc.BufferDesc.Format, g_srv_heap, g_srv_heap->GetCPUDescriptorHandleForHeapStart(), g_srv_heap->GetGPUDescriptorHandleForHeapStart()); + ImGui_ImplDX12_CreateDeviceObjects(); auto& mgr = omath::hooks::HooksManager::get(); mgr.set_on_wnd_proc([](HWND h, UINT msg, WPARAM wp, LPARAM lp) -> std::optional { - if (ImGui_ImplWin32_WndProcHandler(h, msg, wp, lp)) - return 0; - return std::nullopt; + if (!show_menu) + return std::nullopt; + + ImGui_ImplWin32_WndProcHandler(h, msg, wp, lp); + return true; }); mgr.hook_wnd_proc(desc.OutputWindow); @@ -112,7 +120,6 @@ namespace void on_present(IDXGISwapChain* swap_chain, UINT, UINT) { - // Delay init until we have the command queue (captured from ExecuteCommandLists). if (!g_initialized) { if (!g_init_attempted && g_command_queue) @@ -120,41 +127,42 @@ namespace return; } + if (!g_command_queue) + return; + + if (GetAsyncKeyState(VK_INSERT) & 1) + show_menu = !show_menu; + + if (!show_menu) + return; + + ImGui_ImplDX12_NewFrame(); + ImGui_ImplWin32_NewFrame(); + ImGui::NewFrame(); + ImGui::GetIO().MouseDrawCursor = true; + ImGui::ShowDemoWindow(); + ImGui::EndFrame(); + const UINT buf_idx = g_swap_chain->GetCurrentBackBufferIndex(); auto& fc = g_frames[buf_idx]; - fc.command_allocator->Reset(); - g_command_list->Reset(fc.command_allocator, nullptr); + g_command_allocator->Reset(); + g_command_list->Reset(g_command_allocator, nullptr); - // Transition back buffer: Present → RenderTarget D3D12_RESOURCE_BARRIER barrier{}; barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; barrier.Transition.pResource = fc.render_target; barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; g_command_list->ResourceBarrier(1, &barrier); - - D3D12_CPU_DESCRIPTOR_HANDLE rtv = g_rtv_heap->GetCPUDescriptorHandleForHeapStart(); - rtv.ptr += buf_idx * g_rtv_descriptor_size; - g_command_list->OMSetRenderTargets(1, &rtv, FALSE, nullptr); + g_command_list->OMSetRenderTargets(1, &fc.rtv_handle, FALSE, nullptr); g_command_list->SetDescriptorHeaps(1, &g_srv_heap); - ImGui_ImplDX12_NewFrame(); - ImGui_ImplWin32_NewFrame(); - ImGui::NewFrame(); - - ImGui::SetNextWindowSize({300.f, 80.f}, ImGuiCond_Once); - ImGui::SetNextWindowPos({10.f, 10.f}, ImGuiCond_Once); - ImGui::Begin("omath | DX12 hook"); - ImGui::Text("Hook active"); - ImGui::Text("FPS: %.1f", ImGui::GetIO().Framerate); - ImGui::End(); - ImGui::Render(); ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), g_command_list); - // Transition back buffer: RenderTarget → Present barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; g_command_list->ResourceBarrier(1, &barrier); @@ -168,15 +176,15 @@ namespace { for (auto& fc : g_frames) { - if (fc.command_allocator) { fc.command_allocator->Release(); fc.command_allocator = nullptr; } - if (fc.render_target) { fc.render_target->Release(); fc.render_target = nullptr; } + if (fc.render_target) { fc.render_target->Release(); fc.render_target = nullptr; } } g_frames.clear(); - if (g_command_list) { g_command_list->Release(); g_command_list = nullptr; } - if (g_srv_heap) { g_srv_heap->Release(); g_srv_heap = nullptr; } - if (g_rtv_heap) { g_rtv_heap->Release(); g_rtv_heap = nullptr; } - if (g_swap_chain) { g_swap_chain->Release(); g_swap_chain = nullptr; } - if (g_device) { g_device->Release(); g_device = nullptr; } + if (g_command_allocator) { g_command_allocator->Release(); g_command_allocator = nullptr; } + if (g_command_list) { g_command_list->Release(); g_command_list = nullptr; } + if (g_srv_heap) { g_srv_heap->Release(); g_srv_heap = nullptr; } + if (g_rtv_heap) { g_rtv_heap->Release(); g_rtv_heap = nullptr; } + if (g_swap_chain) { g_swap_chain->Release(); g_swap_chain = nullptr; } + if (g_device) { g_device->Release(); g_device = nullptr; } } } // namespace diff --git a/vcpkg.json b/vcpkg.json index 35d4b52..49a1091 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -42,6 +42,21 @@ ], "supports": "(windows | linux) & !arm & !uwp" }, + "hooking-examples": { + "description": "ImGui overlay examples for DX9/DX11/DX12 hooking (requires hooking feature)", + "dependencies": [ + { + "name": "omath", + "features": ["hooking"] + }, + { + "name": "imgui", + "features": ["dx9-binding", "dx11-binding", "dx12-binding", "win32-binding"], + "default-features": false + } + ], + "supports": "windows & !arm & !uwp" + }, "examples": { "description": "Build examples", "dependencies": [ @@ -50,7 +65,7 @@ "opengl", { "name": "imgui", - "features": ["glfw-binding", "opengl3-binding"] + "features": ["glfw-binding", "opengl3-binding", "dx12-binding", "dx9-binding", "dx11-binding", "win32-binding"] } ] }, From 77b0ed3c819882e593fadb50ac642e548d57e524 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 4 May 2026 00:47:20 +0300 Subject: [PATCH 09/11] fixed code style --- examples/example_dx12_hook/dllmain.cpp | 140 +++++++++++++++---------- 1 file changed, 85 insertions(+), 55 deletions(-) diff --git a/examples/example_dx12_hook/dllmain.cpp b/examples/example_dx12_hook/dllmain.cpp index cf22164..4aafac3 100644 --- a/examples/example_dx12_hook/dllmain.cpp +++ b/examples/example_dx12_hook/dllmain.cpp @@ -1,12 +1,11 @@ +#include "omath/hooks/hooks_manager.hpp" #include #include #include -#include #include #include #include - -#include "omath/hooks/hooks_manager.hpp" +#include extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM); bool show_menu = true; @@ -15,20 +14,20 @@ namespace { struct frame_context { - ID3D12Resource* render_target = nullptr; - D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = {}; + ID3D12Resource* render_target = nullptr; + D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle = {}; }; - bool g_initialized = false; + bool g_initialized = false; bool g_init_attempted = false; - ID3D12Device* g_device = nullptr; - ID3D12CommandQueue* g_command_queue = nullptr; - IDXGISwapChain3* g_swap_chain = nullptr; - ID3D12DescriptorHeap* g_rtv_heap = nullptr; - ID3D12DescriptorHeap* g_srv_heap = nullptr; - ID3D12GraphicsCommandList* g_command_list = nullptr; - ID3D12CommandAllocator* g_command_allocator = nullptr; + ID3D12Device* g_device = nullptr; + ID3D12CommandQueue* g_command_queue = nullptr; + IDXGISwapChain3* g_swap_chain = nullptr; + ID3D12DescriptorHeap* g_rtv_heap = nullptr; + ID3D12DescriptorHeap* g_srv_heap = nullptr; + ID3D12GraphicsCommandList* g_command_list = nullptr; + ID3D12CommandAllocator* g_command_allocator = nullptr; std::vector g_frames; void init(IDXGISwapChain* swap_chain) @@ -47,24 +46,24 @@ namespace { D3D12_DESCRIPTOR_HEAP_DESC heap_desc{}; - heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; heap_desc.NumDescriptors = buffer_count; - heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; if (FAILED(g_device->CreateDescriptorHeap(&heap_desc, IID_PPV_ARGS(&g_srv_heap)))) return; } { D3D12_DESCRIPTOR_HEAP_DESC heap_desc{}; - heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; + heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; heap_desc.NumDescriptors = buffer_count; - heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; - heap_desc.NodeMask = 1; + heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + heap_desc.NodeMask = 1; if (FAILED(g_device->CreateDescriptorHeap(&heap_desc, IID_PPV_ARGS(&g_rtv_heap)))) return; } if (FAILED(g_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, - IID_PPV_ARGS(&g_command_allocator)))) + IID_PPV_ARGS(&g_command_allocator)))) return; g_frames.resize(buffer_count); @@ -80,9 +79,8 @@ namespace rtv_handle.ptr += rtv_size; } - if (FAILED(g_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, - g_command_allocator, nullptr, - IID_PPV_ARGS(&g_command_list)))) + if (FAILED(g_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_command_allocator, nullptr, + IID_PPV_ARGS(&g_command_list)))) return; g_command_list->Close(); @@ -93,21 +91,22 @@ namespace ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; ImGui_ImplWin32_Init(desc.OutputWindow); - ImGui_ImplDX12_Init(g_device, static_cast(buffer_count), - desc.BufferDesc.Format, g_srv_heap, - g_srv_heap->GetCPUDescriptorHandleForHeapStart(), - g_srv_heap->GetGPUDescriptorHandleForHeapStart()); + ImGui_ImplDX12_Init(g_device, static_cast(buffer_count), desc.BufferDesc.Format, g_srv_heap, + g_srv_heap->GetCPUDescriptorHandleForHeapStart(), + g_srv_heap->GetGPUDescriptorHandleForHeapStart()); ImGui_ImplDX12_CreateDeviceObjects(); auto& mgr = omath::hooks::HooksManager::get(); - mgr.set_on_wnd_proc([](HWND h, UINT msg, WPARAM wp, LPARAM lp) -> std::optional { - if (!show_menu) - return std::nullopt; + mgr.set_on_wnd_proc( + [](HWND h, UINT msg, WPARAM wp, LPARAM lp) -> std::optional + { + if (!show_menu) + return std::nullopt; - ImGui_ImplWin32_WndProcHandler(h, msg, wp, lp); - return true; - }); - mgr.hook_wnd_proc(desc.OutputWindow); + ImGui_ImplWin32_WndProcHandler(h, msg, wp, lp); + return true; + }); + std::ignore = mgr.hook_wnd_proc(desc.OutputWindow); g_initialized = true; } @@ -150,12 +149,12 @@ namespace g_command_list->Reset(g_command_allocator, nullptr); D3D12_RESOURCE_BARRIER barrier{}; - barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; - barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; - barrier.Transition.pResource = fc.render_target; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.Transition.pResource = fc.render_target; barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; - barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; g_command_list->ResourceBarrier(1, &barrier); g_command_list->OMSetRenderTargets(1, &fc.rtv_handle, FALSE, nullptr); g_command_list->SetDescriptorHeaps(1, &g_srv_heap); @@ -164,7 +163,7 @@ namespace ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), g_command_list); barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; - barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; g_command_list->ResourceBarrier(1, &barrier); g_command_list->Close(); @@ -176,15 +175,43 @@ namespace { for (auto& fc : g_frames) { - if (fc.render_target) { fc.render_target->Release(); fc.render_target = nullptr; } + if (fc.render_target) + { + fc.render_target->Release(); + fc.render_target = nullptr; + } } g_frames.clear(); - if (g_command_allocator) { g_command_allocator->Release(); g_command_allocator = nullptr; } - if (g_command_list) { g_command_list->Release(); g_command_list = nullptr; } - if (g_srv_heap) { g_srv_heap->Release(); g_srv_heap = nullptr; } - if (g_rtv_heap) { g_rtv_heap->Release(); g_rtv_heap = nullptr; } - if (g_swap_chain) { g_swap_chain->Release(); g_swap_chain = nullptr; } - if (g_device) { g_device->Release(); g_device = nullptr; } + if (g_command_allocator) + { + g_command_allocator->Release(); + g_command_allocator = nullptr; + } + if (g_command_list) + { + g_command_list->Release(); + g_command_list = nullptr; + } + if (g_srv_heap) + { + g_srv_heap->Release(); + g_srv_heap = nullptr; + } + if (g_rtv_heap) + { + g_rtv_heap->Release(); + g_rtv_heap = nullptr; + } + if (g_swap_chain) + { + g_swap_chain->Release(); + g_swap_chain = nullptr; + } + if (g_device) + { + g_device->Release(); + g_device = nullptr; + } } } // namespace @@ -193,17 +220,20 @@ BOOL WINAPI DllMain(HINSTANCE h_instance, DWORD reason, LPVOID) if (reason == DLL_PROCESS_ATTACH) { DisableThreadLibraryCalls(h_instance); - CreateThread(nullptr, 0, [](LPVOID) -> DWORD - { - while (!GetModuleHandle("d3d12.dll")) - Sleep(100); + CreateThread( + nullptr, 0, + [](LPVOID) -> DWORD + { + while (!GetModuleHandle("d3d12.dll")) + Sleep(100); - auto& mgr = omath::hooks::HooksManager::get(); - mgr.set_on_present(on_present); - mgr.set_on_execute_command_lists(on_execute_command_lists); - mgr.hook_dx12(); - return 0; - }, nullptr, 0, nullptr); + auto& mgr = omath::hooks::HooksManager::get(); + mgr.set_on_present(on_present); + mgr.set_on_execute_command_lists(on_execute_command_lists); + mgr.hook_dx12(); + return 0; + }, + nullptr, 0, nullptr); } else if (reason == DLL_PROCESS_DETACH) { From 0215b7e0b722a9050312a39675778c1adc9af898 Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 4 May 2026 04:02:19 +0300 Subject: [PATCH 10/11] using static for windows --- CMakeLists.txt | 1 - CMakePresets.json | 16 +++++++++----- examples/example_dx11_hook/CMakeLists.txt | 1 + examples/example_dx12_hook/CMakeLists.txt | 3 ++- examples/example_dx12_hook/dllmain.cpp | 3 ++- examples/example_dx9_hook/CMakeLists.txt | 3 ++- vcpkg.json | 27 +++++++++-------------- 7 files changed, 28 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d6fdb62..40bfa7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,4 @@ cmake_minimum_required(VERSION 3.26) - file(READ VERSION OMATH_VERSION) project(omath VERSION ${OMATH_VERSION} LANGUAGES CXX) diff --git a/CMakePresets.json b/CMakePresets.json index 8d27ee1..2ed29f2 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -56,7 +56,9 @@ "hidden": true, "inherits": ["windows-base", "vcpkg-base"], "cacheVariables": { - "VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;examples" + "VCPKG_TARGET_TRIPLET": "x64-windows-static", + "VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;examples;hooking", + "OMATH_STATIC_MSVC_RUNTIME_LIBRARY": "ON" } }, { @@ -89,9 +91,10 @@ "strategy": "external" }, "cacheVariables": { - "VCPKG_TARGET_TRIPLET": "x86-windows", + "VCPKG_TARGET_TRIPLET": "x86-windows-static", "VCPKG_HOST_TRIPLET": "x64-windows", - "VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;examples" + "VCPKG_MANIFEST_FEATURES": "tests;imgui;avx2;examples", + "OMATH_STATIC_MSVC_RUNTIME_LIBRARY": "ON" } }, { @@ -114,9 +117,10 @@ "strategy": "external" }, "cacheVariables": { - "VCPKG_TARGET_TRIPLET": "arm64-windows", - "VCPKG_HOST_TRIPLET": "arm64-windows", - "VCPKG_MANIFEST_FEATURES": "tests;imgui;examples" + "VCPKG_TARGET_TRIPLET": "arm64-windows-static", + "VCPKG_HOST_TRIPLET": "arm64-windows-static", + "VCPKG_MANIFEST_FEATURES": "tests;imgui;examples", + "OMATH_STATIC_MSVC_RUNTIME_LIBRARY": "ON" } }, { diff --git a/examples/example_dx11_hook/CMakeLists.txt b/examples/example_dx11_hook/CMakeLists.txt index d0d0978..7952808 100644 --- a/examples/example_dx11_hook/CMakeLists.txt +++ b/examples/example_dx11_hook/CMakeLists.txt @@ -4,6 +4,7 @@ add_library(${PROJECT_NAME} SHARED dllmain.cpp) set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 23 + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}") diff --git a/examples/example_dx12_hook/CMakeLists.txt b/examples/example_dx12_hook/CMakeLists.txt index 2fc353c..cace2b4 100644 --- a/examples/example_dx12_hook/CMakeLists.txt +++ b/examples/example_dx12_hook/CMakeLists.txt @@ -1,9 +1,10 @@ project(example_dx12_hook) -set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + add_library(${PROJECT_NAME} MODULE dllmain.cpp) set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 23 + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}") diff --git a/examples/example_dx12_hook/dllmain.cpp b/examples/example_dx12_hook/dllmain.cpp index 4aafac3..b6a56b6 100644 --- a/examples/example_dx12_hook/dllmain.cpp +++ b/examples/example_dx12_hook/dllmain.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM); @@ -230,7 +231,7 @@ BOOL WINAPI DllMain(HINSTANCE h_instance, DWORD reason, LPVOID) auto& mgr = omath::hooks::HooksManager::get(); mgr.set_on_present(on_present); mgr.set_on_execute_command_lists(on_execute_command_lists); - mgr.hook_dx12(); + std::ignore = mgr.hook_dx12(); return 0; }, nullptr, 0, nullptr); diff --git a/examples/example_dx9_hook/CMakeLists.txt b/examples/example_dx9_hook/CMakeLists.txt index c1dd44c..d4030d8 100644 --- a/examples/example_dx9_hook/CMakeLists.txt +++ b/examples/example_dx9_hook/CMakeLists.txt @@ -1,9 +1,10 @@ project(example_dx9_hook) -set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + add_library(${PROJECT_NAME} MODULE dllmain.cpp) set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 23 + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/out/${CMAKE_BUILD_TYPE}") diff --git a/vcpkg.json b/vcpkg.json index 49a1091..27e3bef 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -42,30 +42,25 @@ ], "supports": "(windows | linux) & !arm & !uwp" }, - "hooking-examples": { - "description": "ImGui overlay examples for DX9/DX11/DX12 hooking (requires hooking feature)", - "dependencies": [ - { - "name": "omath", - "features": ["hooking"] - }, - { - "name": "imgui", - "features": ["dx9-binding", "dx11-binding", "dx12-binding", "win32-binding"], - "default-features": false - } - ], - "supports": "windows & !arm & !uwp" - }, "examples": { "description": "Build examples", "dependencies": [ "glfw3", "glew", "opengl", + { + "name": "omath", + "features": ["hooking"], + "platform": "windows & !arm & !uwp" + }, { "name": "imgui", - "features": ["glfw-binding", "opengl3-binding", "dx12-binding", "dx9-binding", "dx11-binding", "win32-binding"] + "features": ["glfw-binding", "opengl3-binding"] + }, + { + "name": "imgui", + "features": ["dx9-binding", "dx11-binding", "dx12-binding", "win32-binding"], + "platform": "windows & !arm & !uwp" } ] }, From 0515236c6cf64fcbb7354724fc58aaf314ec89ca Mon Sep 17 00:00:00 2001 From: Orange Date: Mon, 4 May 2026 04:21:29 +0300 Subject: [PATCH 11/11] fix --- CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 40bfa7f..35be5e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,10 @@ if(${PROJECT_IS_TOP_LEVEL}) message(STATUS "[${PROJECT_NAME}]: Lua feature status ${OMATH_ENABLE_LUA}") endif() +if(OMATH_STATIC_MSVC_RUNTIME_LIBRARY) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" CACHE STRING "" FORCE) +endif() + file(GLOB_RECURSE OMATH_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp") file(GLOB_RECURSE OMATH_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp") @@ -160,10 +164,6 @@ set_target_properties( CXX_STANDARD 23 CXX_STANDARD_REQUIRED ON) -if(OMATH_STATIC_MSVC_RUNTIME_LIBRARY) - set_target_properties(${PROJECT_NAME} PROPERTIES MSVC_RUNTIME_LIBRARY - "MultiThreaded$<$:Debug>") -endif() if(OMATH_USE_AVX2) if(MSVC)