diff --git a/CMakeLists.txt b/CMakeLists.txt index ff324be..35be5e7 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) @@ -31,9 +30,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 +48,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() @@ -80,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") @@ -100,6 +106,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 d3d9 d3d11 d3d12 dxgi) + endif () +endif () + add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) target_compile_definitions(${PROJECT_NAME} PUBLIC OMATH_VERSION="${PROJECT_VERSION}") @@ -147,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) 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/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..7952808 --- /dev/null +++ b/examples/example_dx11_hook/CMakeLists.txt @@ -0,0 +1,13 @@ +project(example_dx11_hook) + +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}") + +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..cace2b4 --- /dev/null +++ b/examples/example_dx12_hook/CMakeLists.txt @@ -0,0 +1,13 @@ +project(example_dx12_hook) + +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}") + +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..b6a56b6 --- /dev/null +++ b/examples/example_dx12_hook/dllmain.cpp @@ -0,0 +1,254 @@ +#include "omath/hooks/hooks_manager.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM); +bool show_menu = true; + +namespace +{ + struct frame_context + { + 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; + ID3D12CommandAllocator* g_command_allocator = nullptr; + std::vector g_frames; + + 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 = 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; + } + { + 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; + } + + 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) + { + 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 += rtv_size; + } + + 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(); + + ImGui::CreateContext(); + ImGui::StyleColorsDark(); + ImGui::GetIO().IniFilename = nullptr; + ImGui::GetIO().LogFilename = nullptr; + 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 (!show_menu) + return std::nullopt; + + ImGui_ImplWin32_WndProcHandler(h, msg, wp, lp); + return true; + }); + std::ignore = 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) + { + if (!g_initialized) + { + if (!g_init_attempted && g_command_queue) + init(swap_chain); + 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]; + + g_command_allocator->Reset(); + 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.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); + g_command_list->OMSetRenderTargets(1, &fc.rtv_handle, FALSE, nullptr); + g_command_list->SetDescriptorHeaps(1, &g_srv_heap); + + ImGui::Render(); + ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), g_command_list); + + 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.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; + } + } +} // 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); + std::ignore = 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..d4030d8 --- /dev/null +++ b/examples/example_dx9_hook/CMakeLists.txt @@ -0,0 +1,13 @@ +project(example_dx9_hook) + +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}") + +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/include/omath/hooks/hooks_manager.hpp b/include/omath/hooks/hooks_manager.hpp new file mode 100644 index 0000000..e82da9d --- /dev/null +++ b/include/omath/hooks/hooks_manager.hpp @@ -0,0 +1,135 @@ +#pragma once + +#ifdef OMATH_ENABLE_HOOKING +#include +#include +#include +#include + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include +#include +#include +#include + +namespace omath::hooks +{ + class HooksManager final + { + HooksManager() = default; + public: + // 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)>; + + [[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 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); + void unhook_wnd_proc(); + 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, + 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_dx9_hooked = false; + 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_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; + + safetyhook::InlineHook m_dx12_present_hook; + 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; + 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..0787695 --- /dev/null +++ b/source/hooks/hooks_manager.cpp @@ -0,0 +1,588 @@ +#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; } + }; + + void* vtable_fn(void* com_obj, std::size_t index) + { + 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, 10), // ID3D12CommandQueue::ExecuteCommandLists + }; + } +} // namespace + +namespace omath::hooks +{ + HooksManager& HooksManager::get() + { + static HooksManager obj; + return obj; + } + + 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); + 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); + if (m_is_dx12_hooked) + return true; + + const DummyWindow window; + if (!window.valid()) + return false; + + const auto fns = read_dx12_vtable_fns(window.handle()); + if (!fns) + return false; + + m_dx12_present_hook = safetyhook::create_inline( + fns->present, + reinterpret_cast(&dx12_present_detour)); + + m_dx12_resize_buffers_hook = safetyhook::create_inline( + fns->resize_buffers, + reinterpret_cast(&dx12_resize_buffers_detour)); + + m_dx12_execute_command_lists_hook = safetyhook::create_inline( + fns->execute_command_lists, + reinterpret_cast(&dx12_execute_command_lists_detour)); + + if (!m_dx12_present_hook || !m_dx12_resize_buffers_hook || !m_dx12_execute_command_lists_hook) + { + m_dx12_present_hook = {}; + m_dx12_resize_buffers_hook = {}; + m_dx12_execute_command_lists_hook = {}; + return false; + } + + m_is_dx12_hooked = true; + return true; + } + + void HooksManager::unhook_dx12() + { + std::unique_lock lock(m_mutex); + m_dx12_present_hook = {}; + m_dx12_resize_buffers_hook = {}; + m_dx12_execute_command_lists_hook = {}; + 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::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(); + 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_dx11_present_hook.call(p_swap_chain, sync_interval, 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; + { + 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_dx11_resize_buffers_hook.call(p_swap_chain, buffer_count, width, height, + new_format, swap_chain_flags); + } + + 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; + { + 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_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) + { + 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..27e3bef 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,15 +35,32 @@ "benchmark" ] }, + "hooking": { + "description": "Add interface for automatic hooking of DirectX", + "dependencies": [ + "safetyhook" + ], + "supports": "(windows | linux) & !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"] + }, + { + "name": "imgui", + "features": ["dx9-binding", "dx11-binding", "dx12-binding", "win32-binding"], + "platform": "windows & !arm & !uwp" } ] },