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