Compare commits

..

6 Commits

Author SHA1 Message Date
fa52c9e985 added opengl for linux 2026-05-06 22:00:23 +03:00
6ced4acdb6 removed copying 2026-05-06 20:10:34 +03:00
d90164cab8 added opengl hook 2026-05-06 20:05:45 +03:00
29255cbb0e added claud config + skills 2026-05-06 04:04:57 +03:00
8ad936f9f1 added separated mutexes for each call back 2026-05-04 20:55:42 +03:00
57c834ded4 code style fixes 2026-05-04 19:42:39 +03:00
11 changed files with 743 additions and 230 deletions

View File

@@ -0,0 +1,67 @@
---
name: karpathy-guidelines
description: Behavioral guidelines to reduce common LLM coding mistakes. Use when writing, reviewing, or refactoring code to avoid overcomplication, make surgical changes, surface assumptions, and define verifiable success criteria.
license: MIT
---
# Karpathy Guidelines
Behavioral guidelines to reduce common LLM coding mistakes, derived from [Andrej Karpathy's observations](https://x.com/karpathy/status/2015883857489522876) on LLM coding pitfalls.
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
## 1. Think Before Coding
**Don't assume. Don't hide confusion. Surface tradeoffs.**
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
## 2. Simplicity First
**Minimum code that solves the problem. Nothing speculative.**
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
## 3. Surgical Changes
**Touch only what you must. Clean up only your own mess.**
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
## 4. Goal-Driven Execution
**Define success criteria. Loop until verified.**
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
```
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
```
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.

1
.idea/editor.xml generated
View File

@@ -139,6 +139,7 @@
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNotAllPathsReturnValue/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNotAllPathsReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppObjectMemberMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppObjectMemberMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppOutParameterMustBeWritten/@EntryIndexedValue" value="WARNING" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppOutParameterMustBeWritten/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppOverrideWithDifferentVisibility/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConst/@EntryIndexedValue" value="HINT" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConst/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConstPtrOrRef/@EntryIndexedValue" value="SUGGESTION" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConstPtrOrRef/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterNamesMismatch/@EntryIndexedValue" value="HINT" type="string" /> <option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterNamesMismatch/@EntryIndexedValue" value="HINT" type="string" />

2
.idea/omath.iml generated
View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CIDR" type="CPP_MODULE" version="4" />

65
CLAUDE.md Normal file
View File

@@ -0,0 +1,65 @@
# CLAUDE.md
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
## 1. Think Before Coding
**Don't assume. Don't hide confusion. Surface tradeoffs.**
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
## 2. Simplicity First
**Minimum code that solves the problem. Nothing speculative.**
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
## 3. Surgical Changes
**Touch only what you must. Clean up only your own mess.**
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
## 4. Goal-Driven Execution
**Define success criteria. Loop until verified.**
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
```
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
```
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
---
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.

View File

@@ -32,7 +32,7 @@ option(OMATH_ENABLE_FORCE_INLINE
"Will for compiler to make some functions to be force inlined no matter what" ON) "Will for compiler to make some functions to be force inlined no matter what" ON)
option(OMATH_ENABLE_LUA option(OMATH_ENABLE_LUA
"omath bindings for lua" OFF) "omath bindings for lua" OFF)
option(OMATH_ENABLE_HOOKING "omath will HooksManager that can hook DirectX automatically" OFF) option(OMATH_ENABLE_HOOKING "omath will HooksManager that can hook DirectX/OpenGL automatically" OFF)
if(VCPKG_MANIFEST_FEATURES) if(VCPKG_MANIFEST_FEATURES)
foreach(omath_feature IN LISTS VCPKG_MANIFEST_FEATURES) foreach(omath_feature IN LISTS VCPKG_MANIFEST_FEATURES)
@@ -113,7 +113,10 @@ if (OMATH_ENABLE_HOOKING)
target_link_libraries(${PROJECT_NAME} PRIVATE safetyhook::safetyhook) target_link_libraries(${PROJECT_NAME} PRIVATE safetyhook::safetyhook)
if (WIN32) if (WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE d3d9 d3d11 d3d12 dxgi) target_link_libraries(${PROJECT_NAME} PRIVATE d3d9 d3d11 d3d12 dxgi opengl32 gdi32)
elseif (UNIX AND NOT APPLE)
find_package(OpenGL REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE OpenGL::GL ${CMAKE_DL_LIBS})
endif () endif ()
endif () endif ()

View File

@@ -5,15 +5,16 @@ add_subdirectory(example_signature_scan)
add_subdirectory(example_hud) add_subdirectory(example_hud)
if(OMATH_ENABLE_HOOKING AND WIN32) if(OMATH_ENABLE_HOOKING AND WIN32)
# Requires imgui with dx9-binding, dx11-binding, dx12-binding, win32-binding. # Requires imgui with dx9-binding, dx11-binding, dx12-binding, opengl3-binding, win32-binding.
# Install via: vcpkg install imgui[dx9-binding,dx11-binding,dx12-binding,win32-binding] # Install via: vcpkg install imgui[dx9-binding,dx11-binding,dx12-binding,opengl3-binding,win32-binding]
find_package(imgui CONFIG QUIET) find_package(imgui CONFIG QUIET)
if(imgui_FOUND) if(imgui_FOUND)
add_subdirectory(example_dx9_hook) add_subdirectory(example_dx9_hook)
add_subdirectory(example_dx11_hook) add_subdirectory(example_dx11_hook)
add_subdirectory(example_dx12_hook) add_subdirectory(example_dx12_hook)
add_subdirectory(example_opengl_hook)
else() else()
message(STATUS "[omath] imgui not found — DX hook examples skipped") message(STATUS "[omath] imgui not found - hook examples skipped")
endif() endif()
endif() endif()

View File

@@ -1,3 +1,4 @@
#include "omath/hooks/hooks_manager.hpp"
#include <Windows.h> #include <Windows.h>
#include <d3d11.h> #include <d3d11.h>
#include <dxgi.h> #include <dxgi.h>
@@ -5,18 +6,16 @@
#include <imgui_impl_dx11.h> #include <imgui_impl_dx11.h>
#include <imgui_impl_win32.h> #include <imgui_impl_win32.h>
#include "omath/hooks/hooks_manager.hpp"
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM); extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM);
namespace namespace
{ {
bool g_initialized = false; bool g_initialized = false;
bool g_init_attempted = false; bool g_init_attempted = false;
ID3D11Device* g_device = nullptr; ID3D11Device* g_device = nullptr;
ID3D11DeviceContext* g_context = nullptr; ID3D11DeviceContext* g_context = nullptr;
ID3D11RenderTargetView* g_render_target_view = nullptr; ID3D11RenderTargetView* g_render_target_view = nullptr;
void create_render_target(IDXGISwapChain* swap_chain) void create_render_target(IDXGISwapChain* swap_chain)
{ {
@@ -51,12 +50,14 @@ namespace
ImGui_ImplDX11_Init(g_device, g_context); ImGui_ImplDX11_Init(g_device, g_context);
auto& mgr = omath::hooks::HooksManager::get(); auto& mgr = omath::hooks::HooksManager::get();
mgr.set_on_wnd_proc([](HWND h, UINT msg, WPARAM wp, LPARAM lp) -> std::optional<LRESULT> { mgr.set_on_wnd_proc(
if (ImGui_ImplWin32_WndProcHandler(h, msg, wp, lp)) [](HWND h, UINT msg, WPARAM wp, LPARAM lp) -> std::optional<LRESULT>
return 0; {
return std::nullopt; if (ImGui_ImplWin32_WndProcHandler(h, msg, wp, lp))
}); return 0;
mgr.hook_wnd_proc(desc.OutputWindow); return std::nullopt;
});
std::ignore = mgr.hook_wnd_proc(desc.OutputWindow);
g_initialized = true; g_initialized = true;
} }
@@ -105,17 +106,20 @@ BOOL WINAPI DllMain(HINSTANCE h_instance, DWORD reason, LPVOID)
if (reason == DLL_PROCESS_ATTACH) if (reason == DLL_PROCESS_ATTACH)
{ {
DisableThreadLibraryCalls(h_instance); DisableThreadLibraryCalls(h_instance);
CreateThread(nullptr, 0, [](LPVOID) -> DWORD CreateThread(
{ nullptr, 0,
while (!GetModuleHandle("d3d11.dll")) [](LPVOID) -> DWORD
Sleep(100); {
while (!GetModuleHandle("d3d11.dll"))
Sleep(100);
auto& mgr = omath::hooks::HooksManager::get(); auto& mgr = omath::hooks::HooksManager::get();
mgr.set_on_present(on_present); mgr.set_on_present(on_present);
mgr.set_on_resize_buffers(on_resize_buffers); mgr.set_on_resize_buffers(on_resize_buffers);
mgr.hook_dx11(); mgr.hook_dx11();
return 0; return 0;
}, nullptr, 0, nullptr); },
nullptr, 0, nullptr);
} }
else if (reason == DLL_PROCESS_DETACH) else if (reason == DLL_PROCESS_DETACH)
{ {
@@ -130,9 +134,21 @@ BOOL WINAPI DllMain(HINSTANCE h_instance, DWORD reason, LPVOID)
ImGui::DestroyContext(); ImGui::DestroyContext();
} }
if (g_render_target_view) { g_render_target_view->Release(); g_render_target_view = nullptr; } if (g_render_target_view)
if (g_context) { g_context->Release(); g_context = nullptr; } {
if (g_device) { g_device->Release(); g_device = nullptr; } 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; return TRUE;
} }

View File

@@ -0,0 +1,13 @@
project(example_opengl_hook)
add_library(${PROJECT_NAME} MODULE dllmain.cpp)
set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_STANDARD 23
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>: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 opengl32 gdi32)

View File

@@ -0,0 +1,116 @@
#include "omath/hooks/hooks_manager.hpp"
#include <Windows.h>
#include <chrono>
#include <imgui.h>
#include <imgui_impl_opengl3.h>
#include <imgui_impl_win32.h>
#include <optional>
#include <thread>
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND, UINT, WPARAM, LPARAM);
namespace
{
bool g_initialized = false;
bool g_init_attempted = false;
bool g_show_menu = true;
constexpr auto g_module_wait_delay = std::chrono::milliseconds{100};
void init(HDC hdc)
{
g_init_attempted = true;
const HWND hwnd = WindowFromDC(hdc);
if (!hwnd)
return;
ImGui::CreateContext();
ImGui::StyleColorsDark();
ImGui::GetIO().IniFilename = nullptr;
ImGui::GetIO().LogFilename = nullptr;
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
ImGui_ImplWin32_Init(hwnd);
ImGui_ImplOpenGL3_Init();
auto& mgr = omath::hooks::HooksManager::get();
mgr.set_on_wnd_proc(
[](HWND h, UINT msg, WPARAM wp, LPARAM lp) -> std::optional<LRESULT>
{
if (!g_show_menu)
return std::nullopt;
if (ImGui_ImplWin32_WndProcHandler(h, msg, wp, lp))
return 0;
return std::nullopt;
});
(void)mgr.hook_wnd_proc(hwnd);
g_initialized = true;
}
void on_swap_buffers(HDC hdc)
{
if (!g_initialized)
{
if (!g_init_attempted)
init(hdc);
return;
}
if (GetAsyncKeyState(VK_INSERT) & 1)
g_show_menu = !g_show_menu;
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
if (g_show_menu)
{
ImGui::SetNextWindowSize({300.f, 100.f}, ImGuiCond_Once);
ImGui::SetNextWindowPos({10.f, 10.f}, ImGuiCond_Once);
ImGui::Begin("omath | OpenGL hook");
ImGui::Text("Hook active");
ImGui::Text("FPS: %.1f", ImGui::GetIO().Framerate);
ImGui::Text("INSERT toggles this window");
ImGui::End();
}
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}
void hook_when_opengl_is_loaded()
{
while (!GetModuleHandle("opengl32.dll"))
std::this_thread::sleep_for(g_module_wait_delay);
auto& mgr = omath::hooks::HooksManager::get();
mgr.set_on_opengl_swap_buffers(on_swap_buffers);
(void)mgr.hook_opengl();
}
} // namespace
BOOL WINAPI DllMain(HINSTANCE h_instance, DWORD reason, LPVOID)
{
if (reason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(h_instance);
std::thread{hook_when_opengl_is_loaded}.detach();
}
else if (reason == DLL_PROCESS_DETACH)
{
auto& mgr = omath::hooks::HooksManager::get();
mgr.unhook_wnd_proc();
mgr.unhook_opengl();
if (g_initialized)
{
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
}
}
return true;
}

View File

@@ -1,11 +1,13 @@
#pragma once #pragma once
#ifdef OMATH_ENABLE_HOOKING #ifdef OMATH_ENABLE_HOOKING
#include <cstdint>
#include <functional> #include <functional>
#include <memory>
#include <optional> #include <optional>
#include <shared_mutex> #include <shared_mutex>
#include <cstdint>
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN #ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#endif #endif
@@ -13,9 +15,15 @@
#define NOMINMAX #define NOMINMAX
#endif #endif
#include <Windows.h> #include <Windows.h>
#include <dxgi.h>
#include <d3d9.h>
#include <d3d12.h> #include <d3d12.h>
#include <d3d9.h>
#include <dxgi.h>
#endif // _WIN32
#ifdef __linux__
#include <GL/glx.h>
#endif // __linux__
#include <safetyhook.hpp> #include <safetyhook.hpp>
namespace omath::hooks namespace omath::hooks
@@ -23,25 +31,42 @@ namespace omath::hooks
class HooksManager final class HooksManager final
{ {
HooksManager() = default; HooksManager() = default;
public: public:
#ifdef _WIN32
// IDXGISwapChain callbacks — 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<void(IDXGISwapChain*, UINT, UINT)>; using present_callback = std::function<void(IDXGISwapChain*, UINT, UINT)>;
using resize_buffers_callback = std::function<void(IDXGISwapChain*, UINT, UINT, UINT, DXGI_FORMAT, UINT)>; using resize_buffers_callback = std::function<void(IDXGISwapChain*, UINT, UINT, UINT, DXGI_FORMAT, UINT)>;
using execute_command_lists_callback = std::function<void(ID3D12CommandQueue*, UINT, ID3D12CommandList* const*)>; using execute_command_lists_callback =
std::function<void(ID3D12CommandQueue*, UINT, ID3D12CommandList* const*)>;
// IDirect3DDevice9 callbacks — DX9 only. // IDirect3DDevice9 callbacks — DX9 only.
using dx9_present_callback = std::function<void(IDirect3DDevice9*, const RECT*, const RECT*, HWND, const RGNDATA*)>; using dx9_present_callback =
using dx9_reset_callback = std::function<void(IDirect3DDevice9*, D3DPRESENT_PARAMETERS*)>; std::function<void(IDirect3DDevice9*, const RECT*, const RECT*, HWND, const RGNDATA*)>;
using dx9_reset_callback = std::function<void(IDirect3DDevice9*, D3DPRESENT_PARAMETERS*)>;
using dx9_end_scene_callback = std::function<void(IDirect3DDevice9*)>; using dx9_end_scene_callback = std::function<void(IDirect3DDevice9*)>;
// OpenGL callback — Windows. Fires before the hooked buffer swap function calls the original.
using opengl_swap_buffers_callback = std::function<void(HDC)>;
// Return nullopt to pass the message to the original WndProc; return a value to intercept it. // Return nullopt to pass the message to the original WndProc; return a value to intercept it.
using wnd_proc_callback = std::function<std::optional<LRESULT>(HWND, UINT, WPARAM, LPARAM)>; using wnd_proc_callback = std::function<std::optional<LRESULT>(HWND, UINT, WPARAM, LPARAM)>;
#endif // _WIN32
#ifdef __linux__
// OpenGL/GLX callback — Linux. Fires before glXSwapBuffers calls the original.
using opengl_swap_buffers_callback = std::function<void(Display*, GLXDrawable)>;
#endif // __linux__
template<typename Callback>
using callback_ptr = std::shared_ptr<const Callback>;
[[nodiscard]] static HooksManager& get(); [[nodiscard]] static HooksManager& get();
HooksManager(const HooksManager&) = delete; HooksManager(const HooksManager&) = delete;
HooksManager& operator=(const HooksManager&) = delete; HooksManager& operator=(const HooksManager&) = delete;
~HooksManager(); ~HooksManager();
#ifdef _WIN32
[[nodiscard]] bool hook_dx9(); [[nodiscard]] bool hook_dx9();
void unhook_dx9(); void unhook_dx9();
void set_on_dx9_present(dx9_present_callback callback); void set_on_dx9_present(dx9_present_callback callback);
@@ -53,7 +78,13 @@ namespace omath::hooks
[[nodiscard]] bool hook_dx12(); [[nodiscard]] bool hook_dx12();
void unhook_dx12(); void unhook_dx12();
#endif // _WIN32
[[nodiscard]] bool hook_opengl();
void unhook_opengl();
void set_on_opengl_swap_buffers(opengl_swap_buffers_callback callback);
#ifdef _WIN32
// Present and ResizeBuffers callbacks fire for whichever of DX11/DX12 is hooked. // Present and ResizeBuffers callbacks fire for whichever of DX11/DX12 is hooked.
void set_on_present(present_callback callback); void set_on_present(present_callback callback);
void set_on_resize_buffers(resize_buffers_callback callback); void set_on_resize_buffers(resize_buffers_callback callback);
@@ -62,38 +93,70 @@ namespace omath::hooks
[[nodiscard]] bool hook_wnd_proc(HWND hwnd); [[nodiscard]] bool hook_wnd_proc(HWND hwnd);
void unhook_wnd_proc(); void unhook_wnd_proc();
void set_on_wnd_proc(wnd_proc_callback callback); void set_on_wnd_proc(wnd_proc_callback callback);
#endif // _WIN32
private: private:
#ifdef _WIN32
[[nodiscard]]
static HRESULT __stdcall dx9_present_detour(IDirect3DDevice9* p_device, const RECT* p_source_rect, 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 RECT* p_dest_rect, HWND h_dest_window_override,
const RGNDATA* p_dirty_region); const RGNDATA* p_dirty_region);
[[nodiscard]]
static HRESULT __stdcall dx9_reset_detour(IDirect3DDevice9* p_device, static HRESULT __stdcall dx9_reset_detour(IDirect3DDevice9* p_device,
D3DPRESENT_PARAMETERS* p_presentation_parameters); D3DPRESENT_PARAMETERS* p_presentation_parameters);
[[nodiscard]]
static HRESULT __stdcall dx9_end_scene_detour(IDirect3DDevice9* p_device); static HRESULT __stdcall dx9_end_scene_detour(IDirect3DDevice9* p_device);
[[nodiscard]]
static HRESULT __stdcall dx11_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags); 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, [[nodiscard]]
UINT width, UINT height, DXGI_FORMAT new_format, static HRESULT __stdcall dx11_resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count, UINT width,
UINT swap_chain_flags); UINT height, DXGI_FORMAT new_format, UINT swap_chain_flags);
[[nodiscard]]
static HRESULT __stdcall dx12_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT 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, [[nodiscard]]
UINT width, UINT height, DXGI_FORMAT new_format, static HRESULT __stdcall dx12_resize_buffers_detour(IDXGISwapChain* p_swap_chain, UINT buffer_count, UINT width,
UINT swap_chain_flags); UINT height, DXGI_FORMAT new_format, UINT swap_chain_flags);
static void __stdcall dx12_execute_command_lists_detour(ID3D12CommandQueue* p_command_queue, static void __stdcall dx12_execute_command_lists_detour(ID3D12CommandQueue* p_command_queue,
UINT num_command_lists, UINT num_command_lists,
ID3D12CommandList* const* pp_command_lists); ID3D12CommandList* const* pp_command_lists);
[[nodiscard]]
static BOOL __stdcall opengl_wgl_swap_buffers_detour(HDC hdc);
[[nodiscard]]
static BOOL __stdcall opengl_swap_buffers_detour(HDC hdc);
[[nodiscard]]
static LRESULT __stdcall wnd_proc_detour(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param); static LRESULT __stdcall wnd_proc_detour(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param);
#endif // _WIN32
mutable std::shared_mutex m_mutex; #ifdef __linux__
static void opengl_glx_swap_buffers_detour(Display* display, GLXDrawable drawable);
#endif // __linux__
bool m_is_dx9_hooked = false; mutable std::shared_mutex m_hook_state_mutex;
bool m_is_dx11_hooked = false;
bool m_is_dx12_hooked = false; #ifdef _WIN32
mutable std::shared_mutex m_dx9_present_mutex;
mutable std::shared_mutex m_dx9_reset_mutex;
mutable std::shared_mutex m_dx9_end_scene_mutex;
mutable std::shared_mutex m_present_mutex;
mutable std::shared_mutex m_resize_buffers_mutex;
mutable std::shared_mutex m_execute_command_lists_mutex;
mutable std::shared_mutex m_wnd_proc_mutex;
#endif // _WIN32
mutable std::shared_mutex m_opengl_swap_buffers_mutex;
#ifdef _WIN32
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; bool m_is_wnd_proc_hooked = false;
HWND m_hooked_hwnd = nullptr; HWND m_hooked_hwnd = nullptr;
WNDPROC m_original_wndproc = nullptr; WNDPROC m_original_wndproc = nullptr;
safetyhook::InlineHook m_dx9_present_hook; safetyhook::InlineHook m_dx9_present_hook;
@@ -107,16 +170,31 @@ namespace omath::hooks
safetyhook::InlineHook m_dx12_resize_buffers_hook; safetyhook::InlineHook m_dx12_resize_buffers_hook;
safetyhook::InlineHook m_dx12_execute_command_lists_hook; safetyhook::InlineHook m_dx12_execute_command_lists_hook;
dx9_present_callback m_dx9_present_cb; safetyhook::InlineHook m_opengl_wgl_swap_buffers_hook;
dx9_reset_callback m_dx9_reset_cb; safetyhook::InlineHook m_opengl_swap_buffers_hook;
dx9_end_scene_callback m_dx9_end_scene_cb; #endif // _WIN32
present_callback m_present_cb; #ifdef __linux__
resize_buffers_callback m_resize_buffers_cb; safetyhook::InlineHook m_opengl_glx_swap_buffers_hook;
execute_command_lists_callback m_execute_command_lists_cb; #endif // __linux__
wnd_proc_callback m_wnd_proc_cb;
bool m_is_opengl_hooked = false;
#ifdef _WIN32
callback_ptr<dx9_present_callback> m_dx9_present_cb;
callback_ptr<dx9_reset_callback> m_dx9_reset_cb;
callback_ptr<dx9_end_scene_callback> m_dx9_end_scene_cb;
callback_ptr<present_callback> m_present_cb;
callback_ptr<resize_buffers_callback> m_resize_buffers_cb;
callback_ptr<execute_command_lists_callback> m_execute_command_lists_cb;
callback_ptr<wnd_proc_callback> m_wnd_proc_cb;
#endif // _WIN32
callback_ptr<opengl_swap_buffers_callback> m_opengl_swap_buffers_cb;
}; };
} } // namespace omath::hooks
#else // !OMATH_ENABLE_HOOKING #else // !OMATH_ENABLE_HOOKING
@@ -125,11 +203,12 @@ namespace omath::hooks
class HooksManager final class HooksManager final
{ {
HooksManager() = default; HooksManager() = default;
public: public:
[[nodiscard]] static HooksManager& get(); [[nodiscard]] static HooksManager& get();
HooksManager(const HooksManager&) = delete; HooksManager(const HooksManager&) = delete;
~HooksManager(); ~HooksManager();
}; };
} } // namespace omath::hooks
#endif #endif

View File

@@ -1,10 +1,20 @@
#include "omath/hooks/hooks_manager.hpp" #include "omath/hooks/hooks_manager.hpp"
#ifdef OMATH_ENABLE_HOOKING #ifdef OMATH_ENABLE_HOOKING
#ifdef _WIN32
#include <d3d11.h> #include <d3d11.h>
#endif // _WIN32
#ifdef __linux__
#include <dlfcn.h>
#endif // __linux__
namespace namespace
{ {
#ifdef _WIN32
thread_local bool g_is_inside_opengl_swap_buffers = false;
class DummyWindow final class DummyWindow final
{ {
WNDCLASSEX m_window_class{}; WNDCLASSEX m_window_class{};
@@ -13,14 +23,14 @@ namespace
public: public:
DummyWindow() DummyWindow()
{ {
m_window_class.cbSize = sizeof(WNDCLASSEX); m_window_class.cbSize = sizeof(WNDCLASSEX);
m_window_class.style = CS_HREDRAW | CS_VREDRAW; m_window_class.style = CS_HREDRAW | CS_VREDRAW;
m_window_class.lpfnWndProc = DefWindowProc; m_window_class.lpfnWndProc = DefWindowProc;
m_window_class.hInstance = GetModuleHandle(nullptr); m_window_class.hInstance = GetModuleHandle(nullptr);
m_window_class.lpszClassName = "OM"; m_window_class.lpszClassName = "OM";
RegisterClassEx(&m_window_class); RegisterClassEx(&m_window_class);
m_window_handle = CreateWindow(m_window_class.lpszClassName, "Dummy", WS_OVERLAPPEDWINDOW, m_window_handle = CreateWindow(m_window_class.lpszClassName, "Dummy", WS_OVERLAPPEDWINDOW, 0, 0, 100, 100,
0, 0, 100, 100, nullptr, nullptr, m_window_class.hInstance, nullptr); nullptr, nullptr, m_window_class.hInstance, nullptr);
} }
~DummyWindow() ~DummyWindow()
{ {
@@ -28,8 +38,14 @@ namespace
DestroyWindow(m_window_handle); DestroyWindow(m_window_handle);
UnregisterClass(m_window_class.lpszClassName, m_window_class.hInstance); UnregisterClass(m_window_class.lpszClassName, m_window_class.hInstance);
} }
[[nodiscard]] HWND handle() const { return m_window_handle; } [[nodiscard]] HWND handle() const
[[nodiscard]] bool valid() const { return m_window_handle != nullptr; } {
return m_window_handle;
}
[[nodiscard]] bool valid() const
{
return m_window_handle != nullptr;
}
}; };
void* vtable_fn(void* com_obj, std::size_t index) void* vtable_fn(void* com_obj, std::size_t index)
@@ -37,6 +53,15 @@ namespace
return (*reinterpret_cast<void***>(com_obj))[index]; return (*reinterpret_cast<void***>(com_obj))[index];
} }
void* module_proc(const char* module_name, const char* proc_name)
{
const HMODULE module = GetModuleHandle(module_name);
if (!module)
return nullptr;
return reinterpret_cast<void*>(GetProcAddress(module, proc_name));
}
struct dx12_vtable_fns struct dx12_vtable_fns
{ {
void* present; void* present;
@@ -47,25 +72,31 @@ namespace
// RAII wrapper so all early-return paths release COM objects automatically. // RAII wrapper so all early-return paths release COM objects automatically.
struct dx12_com_objects struct dx12_com_objects
{ {
IDXGIFactory* factory = nullptr; IDXGIFactory* factory = nullptr;
ID3D12Device* device = nullptr; ID3D12Device* device = nullptr;
ID3D12CommandQueue* command_queue = nullptr; ID3D12CommandQueue* command_queue = nullptr;
ID3D12CommandAllocator* command_allocator = nullptr; ID3D12CommandAllocator* command_allocator = nullptr;
ID3D12GraphicsCommandList* command_list = nullptr; ID3D12GraphicsCommandList* command_list = nullptr;
IDXGISwapChain* swap_chain = nullptr; IDXGISwapChain* swap_chain = nullptr;
dx12_com_objects() = default; dx12_com_objects() = default;
dx12_com_objects(const dx12_com_objects&) = delete; dx12_com_objects(const dx12_com_objects&) = delete;
dx12_com_objects& operator=(const dx12_com_objects&) = delete; dx12_com_objects& operator=(const dx12_com_objects&) = delete;
~dx12_com_objects() ~dx12_com_objects()
{ {
if (swap_chain) swap_chain->Release(); if (swap_chain)
if (command_list) command_list->Release(); swap_chain->Release();
if (command_allocator) command_allocator->Release(); if (command_list)
if (command_queue) command_queue->Release(); command_list->Release();
if (device) device->Release(); if (command_allocator)
if (factory) factory->Release(); command_allocator->Release();
if (command_queue)
command_queue->Release();
if (device)
device->Release();
if (factory)
factory->Release();
} }
}; };
@@ -75,14 +106,14 @@ namespace
using d3d12_create_device_fn = HRESULT(__stdcall*)(IUnknown*, D3D_FEATURE_LEVEL, REFIID, void**); using d3d12_create_device_fn = HRESULT(__stdcall*)(IUnknown*, D3D_FEATURE_LEVEL, REFIID, void**);
const HMODULE d3d12_module = GetModuleHandle("d3d12.dll"); const HMODULE d3d12_module = GetModuleHandle("d3d12.dll");
const HMODULE dxgi_module = GetModuleHandle("dxgi.dll"); const HMODULE dxgi_module = GetModuleHandle("dxgi.dll");
if (!d3d12_module || !dxgi_module) if (!d3d12_module || !dxgi_module)
return std::nullopt; return std::nullopt;
const auto create_dxgi_factory = reinterpret_cast<create_dxgi_factory_fn>( const auto create_dxgi_factory =
GetProcAddress(dxgi_module, "CreateDXGIFactory")); reinterpret_cast<create_dxgi_factory_fn>(GetProcAddress(dxgi_module, "CreateDXGIFactory"));
const auto d3d12_create_device = reinterpret_cast<d3d12_create_device_fn>( const auto d3d12_create_device =
GetProcAddress(d3d12_module, "D3D12CreateDevice")); reinterpret_cast<d3d12_create_device_fn>(GetProcAddress(d3d12_module, "D3D12CreateDevice"));
if (!create_dxgi_factory || !d3d12_create_device) if (!create_dxgi_factory || !d3d12_create_device)
return std::nullopt; return std::nullopt;
@@ -96,9 +127,8 @@ namespace
if (objs.factory->EnumAdapters(0, &adapter) == DXGI_ERROR_NOT_FOUND) if (objs.factory->EnumAdapters(0, &adapter) == DXGI_ERROR_NOT_FOUND)
return std::nullopt; return std::nullopt;
const HRESULT device_hr = d3d12_create_device(adapter, D3D_FEATURE_LEVEL_11_0, const HRESULT device_hr = d3d12_create_device(adapter, D3D_FEATURE_LEVEL_11_0, __uuidof(ID3D12Device),
__uuidof(ID3D12Device), reinterpret_cast<void**>(&objs.device));
reinterpret_cast<void**>(&objs.device));
adapter->Release(); adapter->Release();
if (FAILED(device_hr)) if (FAILED(device_hr))
return std::nullopt; return std::nullopt;
@@ -110,39 +140,39 @@ namespace
reinterpret_cast<void**>(&objs.command_queue)))) reinterpret_cast<void**>(&objs.command_queue))))
return std::nullopt; return std::nullopt;
if (FAILED(objs.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, if (FAILED(objs.device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, __uuidof(ID3D12CommandAllocator),
__uuidof(ID3D12CommandAllocator), reinterpret_cast<void**>(&objs.command_allocator))))
reinterpret_cast<void**>(&objs.command_allocator))))
return std::nullopt; return std::nullopt;
if (FAILED(objs.device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, objs.command_allocator, if (FAILED(objs.device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, objs.command_allocator, nullptr,
nullptr, __uuidof(ID3D12GraphicsCommandList), __uuidof(ID3D12GraphicsCommandList),
reinterpret_cast<void**>(&objs.command_list)))) reinterpret_cast<void**>(&objs.command_list))))
return std::nullopt; return std::nullopt;
DXGI_SWAP_CHAIN_DESC swap_chain_desc{}; DXGI_SWAP_CHAIN_DESC swap_chain_desc{};
swap_chain_desc.BufferDesc.Width = 100; swap_chain_desc.BufferDesc.Width = 100;
swap_chain_desc.BufferDesc.Height = 100; swap_chain_desc.BufferDesc.Height = 100;
swap_chain_desc.BufferDesc.RefreshRate = {60, 1}; swap_chain_desc.BufferDesc.RefreshRate = {60, 1};
swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swap_chain_desc.SampleDesc = {1, 0}; swap_chain_desc.SampleDesc = {1, 0};
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.BufferCount = 2; swap_chain_desc.BufferCount = 2;
swap_chain_desc.OutputWindow = hwnd; swap_chain_desc.OutputWindow = hwnd;
swap_chain_desc.Windowed = TRUE; swap_chain_desc.Windowed = TRUE;
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swap_chain_desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; 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))) if (FAILED(objs.factory->CreateSwapChain(objs.command_queue, &swap_chain_desc, &objs.swap_chain)))
return std::nullopt; return std::nullopt;
// objs destructor releases all COM objects after we capture the addresses. // objs destructor releases all COM objects after we capture the addresses.
return dx12_vtable_fns{ return dx12_vtable_fns{
vtable_fn(objs.swap_chain, 8), // IDXGISwapChain::Present vtable_fn(objs.swap_chain, 8), // IDXGISwapChain::Present
vtable_fn(objs.swap_chain, 13), // IDXGISwapChain::ResizeBuffers vtable_fn(objs.swap_chain, 13), // IDXGISwapChain::ResizeBuffers
vtable_fn(objs.command_queue, 10), // ID3D12CommandQueue::ExecuteCommandLists vtable_fn(objs.command_queue, 10), // ID3D12CommandQueue::ExecuteCommandLists
}; };
} }
#endif // _WIN32
} // namespace } // namespace
namespace omath::hooks namespace omath::hooks
@@ -155,15 +185,19 @@ namespace omath::hooks
HooksManager::~HooksManager() HooksManager::~HooksManager()
{ {
#ifdef _WIN32
unhook_wnd_proc(); unhook_wnd_proc();
unhook_dx9(); unhook_dx9();
unhook_dx11(); unhook_dx11();
unhook_dx12(); unhook_dx12();
#endif // _WIN32
unhook_opengl();
} }
#ifdef _WIN32
bool HooksManager::hook_dx9() bool HooksManager::hook_dx9()
{ {
std::unique_lock lock(m_mutex); std::unique_lock lock(m_hook_state_mutex);
if (m_is_dx9_hooked) if (m_is_dx9_hooked)
return true; return true;
@@ -176,8 +210,8 @@ namespace omath::hooks
return false; return false;
using direct3d_create9_fn = IDirect3D9*(__stdcall*)(UINT); using direct3d_create9_fn = IDirect3D9*(__stdcall*)(UINT);
const auto direct3d_create9 = reinterpret_cast<direct3d_create9_fn>( const auto direct3d_create9 =
GetProcAddress(d3d9_module, "Direct3DCreate9")); reinterpret_cast<direct3d_create9_fn>(GetProcAddress(d3d9_module, "Direct3DCreate9"));
if (!direct3d_create9) if (!direct3d_create9)
return false; return false;
@@ -186,9 +220,9 @@ namespace omath::hooks
return false; return false;
D3DPRESENT_PARAMETERS pp{}; D3DPRESENT_PARAMETERS pp{};
pp.SwapEffect = D3DSWAPEFFECT_DISCARD; pp.SwapEffect = D3DSWAPEFFECT_DISCARD;
pp.hDeviceWindow = window.handle(); pp.hDeviceWindow = window.handle();
pp.Windowed = TRUE; pp.Windowed = TRUE;
IDirect3DDevice9* device = nullptr; IDirect3DDevice9* device = nullptr;
if (FAILED(d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, window.handle(), if (FAILED(d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, window.handle(),
@@ -202,25 +236,21 @@ namespace omath::hooks
// Reset = 16 // Reset = 16
// Present = 17 // Present = 17
// EndScene = 42 // EndScene = 42
m_dx9_present_hook = safetyhook::create_inline( m_dx9_present_hook =
vtable_fn(device, 17), safetyhook::create_inline(vtable_fn(device, 17), reinterpret_cast<void*>(&dx9_present_detour));
reinterpret_cast<void*>(&dx9_present_detour));
m_dx9_reset_hook = safetyhook::create_inline( m_dx9_reset_hook = safetyhook::create_inline(vtable_fn(device, 16), reinterpret_cast<void*>(&dx9_reset_detour));
vtable_fn(device, 16),
reinterpret_cast<void*>(&dx9_reset_detour));
m_dx9_end_scene_hook = safetyhook::create_inline( m_dx9_end_scene_hook =
vtable_fn(device, 42), safetyhook::create_inline(vtable_fn(device, 42), reinterpret_cast<void*>(&dx9_end_scene_detour));
reinterpret_cast<void*>(&dx9_end_scene_detour));
device->Release(); device->Release();
d3d9->Release(); d3d9->Release();
if (!m_dx9_present_hook || !m_dx9_reset_hook || !m_dx9_end_scene_hook) if (!m_dx9_present_hook || !m_dx9_reset_hook || !m_dx9_end_scene_hook)
{ {
m_dx9_present_hook = {}; m_dx9_present_hook = {};
m_dx9_reset_hook = {}; m_dx9_reset_hook = {};
m_dx9_end_scene_hook = {}; m_dx9_end_scene_hook = {};
return false; return false;
} }
@@ -231,34 +261,34 @@ namespace omath::hooks
void HooksManager::unhook_dx9() void HooksManager::unhook_dx9()
{ {
std::unique_lock lock(m_mutex); std::unique_lock lock(m_hook_state_mutex);
m_dx9_present_hook = {}; m_dx9_present_hook = {};
m_dx9_reset_hook = {}; m_dx9_reset_hook = {};
m_dx9_end_scene_hook = {}; m_dx9_end_scene_hook = {};
m_is_dx9_hooked = false; m_is_dx9_hooked = false;
} }
void HooksManager::set_on_dx9_present(dx9_present_callback callback) void HooksManager::set_on_dx9_present(dx9_present_callback callback)
{ {
std::unique_lock lock(m_mutex); std::unique_lock lock(m_dx9_present_mutex);
m_dx9_present_cb = std::move(callback); m_dx9_present_cb = callback ? std::make_shared<dx9_present_callback>(std::move(callback)) : nullptr;
} }
void HooksManager::set_on_dx9_reset(dx9_reset_callback callback) void HooksManager::set_on_dx9_reset(dx9_reset_callback callback)
{ {
std::unique_lock lock(m_mutex); std::unique_lock lock(m_dx9_reset_mutex);
m_dx9_reset_cb = std::move(callback); m_dx9_reset_cb = callback ? std::make_shared<dx9_reset_callback>(std::move(callback)) : nullptr;
} }
void HooksManager::set_on_dx9_end_scene(dx9_end_scene_callback callback) void HooksManager::set_on_dx9_end_scene(dx9_end_scene_callback callback)
{ {
std::unique_lock lock(m_mutex); std::unique_lock lock(m_dx9_end_scene_mutex);
m_dx9_end_scene_cb = std::move(callback); m_dx9_end_scene_cb = callback ? std::make_shared<dx9_end_scene_callback>(std::move(callback)) : nullptr;
} }
bool HooksManager::hook_dx11() bool HooksManager::hook_dx11()
{ {
std::unique_lock lock(m_mutex); std::unique_lock lock(m_hook_state_mutex);
if (m_is_dx11_hooked) if (m_is_dx11_hooked)
return true; return true;
@@ -271,46 +301,43 @@ namespace omath::hooks
return false; return false;
using d3d11_create_device_and_swap_chain_fn = using d3d11_create_device_and_swap_chain_fn =
HRESULT(__stdcall*)(IDXGIAdapter*, D3D_DRIVER_TYPE, HMODULE, UINT, HRESULT(__stdcall*)(IDXGIAdapter*, D3D_DRIVER_TYPE, HMODULE, UINT, const D3D_FEATURE_LEVEL*, UINT, UINT,
const D3D_FEATURE_LEVEL*, UINT, UINT, const DXGI_SWAP_CHAIN_DESC*, IDXGISwapChain**, ID3D11Device**, D3D_FEATURE_LEVEL*,
const DXGI_SWAP_CHAIN_DESC*, IDXGISwapChain**, ID3D11DeviceContext**);
ID3D11Device**, D3D_FEATURE_LEVEL*, ID3D11DeviceContext**);
const auto create_device_and_swap_chain = reinterpret_cast<d3d11_create_device_and_swap_chain_fn>( const auto create_device_and_swap_chain = reinterpret_cast<d3d11_create_device_and_swap_chain_fn>(
GetProcAddress(d3d11_module, "D3D11CreateDeviceAndSwapChain")); GetProcAddress(d3d11_module, "D3D11CreateDeviceAndSwapChain"));
if (!create_device_and_swap_chain) if (!create_device_and_swap_chain)
return false; return false;
DXGI_SWAP_CHAIN_DESC swap_chain_desc{}; DXGI_SWAP_CHAIN_DESC swap_chain_desc{};
swap_chain_desc.BufferDesc.Width = 100; swap_chain_desc.BufferDesc.Width = 100;
swap_chain_desc.BufferDesc.Height = 100; swap_chain_desc.BufferDesc.Height = 100;
swap_chain_desc.BufferDesc.RefreshRate = {60, 1}; swap_chain_desc.BufferDesc.RefreshRate = {60, 1};
swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swap_chain_desc.SampleDesc = {1, 0}; swap_chain_desc.SampleDesc = {1, 0};
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.BufferCount = 1; swap_chain_desc.BufferCount = 1;
swap_chain_desc.OutputWindow = window.handle(); swap_chain_desc.OutputWindow = window.handle();
swap_chain_desc.Windowed = TRUE; swap_chain_desc.Windowed = TRUE;
swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
constexpr D3D_FEATURE_LEVEL feature_levels[] = {D3D_FEATURE_LEVEL_11_0}; constexpr D3D_FEATURE_LEVEL feature_levels[] = {D3D_FEATURE_LEVEL_11_0};
ID3D11Device* device = nullptr; ID3D11Device* device = nullptr;
ID3D11DeviceContext* device_context = nullptr; ID3D11DeviceContext* device_context = nullptr;
IDXGISwapChain* swap_chain = nullptr; IDXGISwapChain* swap_chain = nullptr;
if (FAILED(create_device_and_swap_chain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, if (FAILED(create_device_and_swap_chain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, feature_levels, 1,
feature_levels, 1, D3D11_SDK_VERSION, D3D11_SDK_VERSION, &swap_chain_desc, &swap_chain, &device, nullptr,
&swap_chain_desc, &swap_chain, &device_context)))
&device, nullptr, &device_context)))
return false; return false;
m_dx11_present_hook = safetyhook::create_inline( m_dx11_present_hook = safetyhook::create_inline(vtable_fn(swap_chain, 8), // IDXGISwapChain::Present
vtable_fn(swap_chain, 8), // IDXGISwapChain::Present reinterpret_cast<void*>(&dx11_present_detour));
reinterpret_cast<void*>(&dx11_present_detour));
m_dx11_resize_buffers_hook = safetyhook::create_inline( m_dx11_resize_buffers_hook =
vtable_fn(swap_chain, 13), // IDXGISwapChain::ResizeBuffers safetyhook::create_inline(vtable_fn(swap_chain, 13), // IDXGISwapChain::ResizeBuffers
reinterpret_cast<void*>(&dx11_resize_buffers_detour)); reinterpret_cast<void*>(&dx11_resize_buffers_detour));
swap_chain->Release(); swap_chain->Release();
device_context->Release(); device_context->Release();
@@ -318,7 +345,7 @@ namespace omath::hooks
if (!m_dx11_present_hook || !m_dx11_resize_buffers_hook) if (!m_dx11_present_hook || !m_dx11_resize_buffers_hook)
{ {
m_dx11_present_hook = {}; m_dx11_present_hook = {};
m_dx11_resize_buffers_hook = {}; m_dx11_resize_buffers_hook = {};
return false; return false;
} }
@@ -329,15 +356,15 @@ namespace omath::hooks
void HooksManager::unhook_dx11() void HooksManager::unhook_dx11()
{ {
std::unique_lock lock(m_mutex); std::unique_lock lock(m_hook_state_mutex);
m_dx11_present_hook = {}; m_dx11_present_hook = {};
m_dx11_resize_buffers_hook = {}; m_dx11_resize_buffers_hook = {};
m_is_dx11_hooked = false; m_is_dx11_hooked = false;
} }
bool HooksManager::hook_dx12() bool HooksManager::hook_dx12()
{ {
std::unique_lock lock(m_mutex); std::unique_lock lock(m_hook_state_mutex);
if (m_is_dx12_hooked) if (m_is_dx12_hooked)
return true; return true;
@@ -349,22 +376,18 @@ namespace omath::hooks
if (!fns) if (!fns)
return false; return false;
m_dx12_present_hook = safetyhook::create_inline( m_dx12_present_hook = safetyhook::create_inline(fns->present, reinterpret_cast<void*>(&dx12_present_detour));
fns->present,
reinterpret_cast<void*>(&dx12_present_detour));
m_dx12_resize_buffers_hook = safetyhook::create_inline( m_dx12_resize_buffers_hook =
fns->resize_buffers, safetyhook::create_inline(fns->resize_buffers, reinterpret_cast<void*>(&dx12_resize_buffers_detour));
reinterpret_cast<void*>(&dx12_resize_buffers_detour));
m_dx12_execute_command_lists_hook = safetyhook::create_inline( m_dx12_execute_command_lists_hook = safetyhook::create_inline(
fns->execute_command_lists, fns->execute_command_lists, reinterpret_cast<void*>(&dx12_execute_command_lists_detour));
reinterpret_cast<void*>(&dx12_execute_command_lists_detour));
if (!m_dx12_present_hook || !m_dx12_resize_buffers_hook || !m_dx12_execute_command_lists_hook) if (!m_dx12_present_hook || !m_dx12_resize_buffers_hook || !m_dx12_execute_command_lists_hook)
{ {
m_dx12_present_hook = {}; m_dx12_present_hook = {};
m_dx12_resize_buffers_hook = {}; m_dx12_resize_buffers_hook = {};
m_dx12_execute_command_lists_hook = {}; m_dx12_execute_command_lists_hook = {};
return false; return false;
} }
@@ -375,122 +398,161 @@ namespace omath::hooks
void HooksManager::unhook_dx12() void HooksManager::unhook_dx12()
{ {
std::unique_lock lock(m_mutex); std::unique_lock lock(m_hook_state_mutex);
m_dx12_present_hook = {}; m_dx12_present_hook = {};
m_dx12_resize_buffers_hook = {}; m_dx12_resize_buffers_hook = {};
m_dx12_execute_command_lists_hook = {}; m_dx12_execute_command_lists_hook = {};
m_is_dx12_hooked = false; m_is_dx12_hooked = false;
} }
bool HooksManager::hook_opengl()
{
std::unique_lock lock(m_hook_state_mutex);
if (m_is_opengl_hooked)
return true;
if (void* wgl_swap_buffers = module_proc("opengl32.dll", "wglSwapBuffers"))
{
m_opengl_wgl_swap_buffers_hook = safetyhook::create_inline(
wgl_swap_buffers, reinterpret_cast<void*>(&opengl_wgl_swap_buffers_detour));
}
if (void* swap_buffers = module_proc("gdi32.dll", "SwapBuffers"))
{
m_opengl_swap_buffers_hook =
safetyhook::create_inline(swap_buffers, reinterpret_cast<void*>(&opengl_swap_buffers_detour));
}
if (!m_opengl_wgl_swap_buffers_hook && !m_opengl_swap_buffers_hook)
{
m_opengl_wgl_swap_buffers_hook = {};
m_opengl_swap_buffers_hook = {};
return false;
}
m_is_opengl_hooked = true;
return true;
}
void HooksManager::unhook_opengl()
{
std::unique_lock lock(m_hook_state_mutex);
m_opengl_wgl_swap_buffers_hook = {};
m_opengl_swap_buffers_hook = {};
m_is_opengl_hooked = false;
}
void HooksManager::set_on_present(present_callback callback) void HooksManager::set_on_present(present_callback callback)
{ {
std::unique_lock lock(m_mutex); std::unique_lock lock(m_present_mutex);
m_present_cb = std::move(callback); m_present_cb = callback ? std::make_shared<present_callback>(std::move(callback)) : nullptr;
} }
void HooksManager::set_on_resize_buffers(resize_buffers_callback callback) void HooksManager::set_on_resize_buffers(resize_buffers_callback callback)
{ {
std::unique_lock lock(m_mutex); std::unique_lock lock(m_resize_buffers_mutex);
m_resize_buffers_cb = std::move(callback); m_resize_buffers_cb = callback ? std::make_shared<resize_buffers_callback>(std::move(callback)) : nullptr;
} }
void HooksManager::set_on_execute_command_lists(execute_command_lists_callback callback) void HooksManager::set_on_execute_command_lists(execute_command_lists_callback callback)
{ {
std::unique_lock lock(m_mutex); std::unique_lock lock(m_execute_command_lists_mutex);
m_execute_command_lists_cb = std::move(callback); m_execute_command_lists_cb =
callback ? std::make_shared<execute_command_lists_callback>(std::move(callback)) : nullptr;
} }
bool HooksManager::hook_wnd_proc(HWND hwnd) bool HooksManager::hook_wnd_proc(HWND hwnd)
{ {
std::unique_lock lock(m_mutex); std::unique_lock lock(m_hook_state_mutex);
if (m_is_wnd_proc_hooked) if (m_is_wnd_proc_hooked)
return true; return true;
const auto prev = reinterpret_cast<WNDPROC>( const auto prev = reinterpret_cast<WNDPROC>(
SetWindowLongPtr(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&wnd_proc_detour))); SetWindowLongPtr(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&wnd_proc_detour)));
if (!prev) if (!prev)
return false; return false;
m_hooked_hwnd = hwnd; m_hooked_hwnd = hwnd;
m_original_wndproc = prev; m_original_wndproc = prev;
m_is_wnd_proc_hooked = true; m_is_wnd_proc_hooked = true;
return true; return true;
} }
void HooksManager::unhook_wnd_proc() void HooksManager::unhook_wnd_proc()
{ {
std::unique_lock lock(m_mutex); std::unique_lock lock(m_hook_state_mutex);
if (!m_is_wnd_proc_hooked) if (!m_is_wnd_proc_hooked)
return; return;
SetWindowLongPtr(m_hooked_hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(m_original_wndproc)); SetWindowLongPtr(m_hooked_hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(m_original_wndproc));
m_hooked_hwnd = nullptr; m_hooked_hwnd = nullptr;
m_original_wndproc = nullptr; m_original_wndproc = nullptr;
m_is_wnd_proc_hooked = false; m_is_wnd_proc_hooked = false;
} }
void HooksManager::set_on_wnd_proc(wnd_proc_callback callback) void HooksManager::set_on_wnd_proc(wnd_proc_callback callback)
{ {
std::unique_lock lock(m_mutex); std::unique_lock lock(m_wnd_proc_mutex);
m_wnd_proc_cb = std::move(callback); m_wnd_proc_cb = callback ? std::make_shared<wnd_proc_callback>(std::move(callback)) : nullptr;
} }
// Detour implementations: copy callback under shared lock, call it unlocked, // Detour implementations: copy a shared_ptr to the callback under shared lock, call it unlocked,
// then call original. This avoids a deadlock if the callback itself calls set_on_*(). // then call original. This avoids copying captured lambda state every frame and still avoids
// a deadlock if the callback itself calls set_on_*().
HRESULT __stdcall HooksManager::dx9_present_detour(IDirect3DDevice9* p_device, const RECT* p_source_rect, 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 RECT* p_dest_rect, HWND h_dest_window_override,
const RGNDATA* p_dirty_region) const RGNDATA* p_dirty_region)
{ {
auto& mgr = get(); auto& mgr = get();
dx9_present_callback cb; callback_ptr<dx9_present_callback> cb;
{ {
std::shared_lock lock(mgr.m_mutex); std::shared_lock lock(mgr.m_dx9_present_mutex);
cb = mgr.m_dx9_present_cb; cb = mgr.m_dx9_present_cb;
} }
if (cb) if (cb)
cb(p_device, p_source_rect, p_dest_rect, h_dest_window_override, p_dirty_region); (*cb)(p_device, p_source_rect, p_dest_rect, h_dest_window_override, p_dirty_region);
return mgr.m_dx9_present_hook.call<HRESULT>(p_device, p_source_rect, p_dest_rect, return mgr.m_dx9_present_hook.call<HRESULT>(p_device, p_source_rect, p_dest_rect, h_dest_window_override,
h_dest_window_override, p_dirty_region); p_dirty_region);
} }
HRESULT __stdcall HooksManager::dx9_reset_detour(IDirect3DDevice9* p_device, HRESULT __stdcall HooksManager::dx9_reset_detour(IDirect3DDevice9* p_device,
D3DPRESENT_PARAMETERS* p_presentation_parameters) D3DPRESENT_PARAMETERS* p_presentation_parameters)
{ {
auto& mgr = get(); auto& mgr = get();
dx9_reset_callback cb; callback_ptr<dx9_reset_callback> cb;
{ {
std::shared_lock lock(mgr.m_mutex); std::shared_lock lock(mgr.m_dx9_reset_mutex);
cb = mgr.m_dx9_reset_cb; cb = mgr.m_dx9_reset_cb;
} }
if (cb) if (cb)
cb(p_device, p_presentation_parameters); (*cb)(p_device, p_presentation_parameters);
return mgr.m_dx9_reset_hook.call<HRESULT>(p_device, p_presentation_parameters); return mgr.m_dx9_reset_hook.call<HRESULT>(p_device, p_presentation_parameters);
} }
HRESULT __stdcall HooksManager::dx9_end_scene_detour(IDirect3DDevice9* p_device) HRESULT __stdcall HooksManager::dx9_end_scene_detour(IDirect3DDevice9* p_device)
{ {
auto& mgr = get(); auto& mgr = get();
dx9_end_scene_callback cb; callback_ptr<dx9_end_scene_callback> cb;
{ {
std::shared_lock lock(mgr.m_mutex); std::shared_lock lock(mgr.m_dx9_end_scene_mutex);
cb = mgr.m_dx9_end_scene_cb; cb = mgr.m_dx9_end_scene_cb;
} }
if (cb) if (cb)
cb(p_device); (*cb)(p_device);
return mgr.m_dx9_end_scene_hook.call<HRESULT>(p_device); return mgr.m_dx9_end_scene_hook.call<HRESULT>(p_device);
} }
HRESULT __stdcall HooksManager::dx11_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(); auto& mgr = get();
present_callback cb; callback_ptr<present_callback> cb;
{ {
std::shared_lock lock(mgr.m_mutex); std::shared_lock lock(mgr.m_present_mutex);
cb = mgr.m_present_cb; cb = mgr.m_present_cb;
} }
if (cb) if (cb)
cb(p_swap_chain, sync_interval, flags); (*cb)(p_swap_chain, sync_interval, flags);
return mgr.m_dx11_present_hook.call<HRESULT>(p_swap_chain, sync_interval, flags); return mgr.m_dx11_present_hook.call<HRESULT>(p_swap_chain, sync_interval, flags);
} }
@@ -499,27 +561,27 @@ namespace omath::hooks
UINT swap_chain_flags) UINT swap_chain_flags)
{ {
auto& mgr = get(); auto& mgr = get();
resize_buffers_callback cb; callback_ptr<resize_buffers_callback> cb;
{ {
std::shared_lock lock(mgr.m_mutex); std::shared_lock lock(mgr.m_resize_buffers_mutex);
cb = mgr.m_resize_buffers_cb; cb = mgr.m_resize_buffers_cb;
} }
if (cb) if (cb)
cb(p_swap_chain, buffer_count, width, height, new_format, swap_chain_flags); (*cb)(p_swap_chain, buffer_count, width, height, new_format, swap_chain_flags);
return mgr.m_dx11_resize_buffers_hook.call<HRESULT>(p_swap_chain, buffer_count, width, height, return mgr.m_dx11_resize_buffers_hook.call<HRESULT>(p_swap_chain, buffer_count, width, height, new_format,
new_format, swap_chain_flags); swap_chain_flags);
} }
HRESULT __stdcall HooksManager::dx12_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags) HRESULT __stdcall HooksManager::dx12_present_detour(IDXGISwapChain* p_swap_chain, UINT sync_interval, UINT flags)
{ {
auto& mgr = get(); auto& mgr = get();
present_callback cb; callback_ptr<present_callback> cb;
{ {
std::shared_lock lock(mgr.m_mutex); std::shared_lock lock(mgr.m_present_mutex);
cb = mgr.m_present_cb; cb = mgr.m_present_cb;
} }
if (cb) if (cb)
cb(p_swap_chain, sync_interval, flags); (*cb)(p_swap_chain, sync_interval, flags);
return mgr.m_dx12_present_hook.call<HRESULT>(p_swap_chain, sync_interval, flags); return mgr.m_dx12_present_hook.call<HRESULT>(p_swap_chain, sync_interval, flags);
} }
@@ -528,15 +590,15 @@ namespace omath::hooks
UINT swap_chain_flags) UINT swap_chain_flags)
{ {
auto& mgr = get(); auto& mgr = get();
resize_buffers_callback cb; callback_ptr<resize_buffers_callback> cb;
{ {
std::shared_lock lock(mgr.m_mutex); std::shared_lock lock(mgr.m_resize_buffers_mutex);
cb = mgr.m_resize_buffers_cb; cb = mgr.m_resize_buffers_cb;
} }
if (cb) if (cb)
cb(p_swap_chain, buffer_count, width, height, new_format, swap_chain_flags); (*cb)(p_swap_chain, buffer_count, width, height, new_format, swap_chain_flags);
return mgr.m_dx12_resize_buffers_hook.call<HRESULT>(p_swap_chain, buffer_count, width, height, return mgr.m_dx12_resize_buffers_hook.call<HRESULT>(p_swap_chain, buffer_count, width, height, new_format,
new_format, swap_chain_flags); swap_chain_flags);
} }
void __stdcall HooksManager::dx12_execute_command_lists_detour(ID3D12CommandQueue* p_command_queue, void __stdcall HooksManager::dx12_execute_command_lists_detour(ID3D12CommandQueue* p_command_queue,
@@ -544,33 +606,125 @@ namespace omath::hooks
ID3D12CommandList* const* pp_command_lists) ID3D12CommandList* const* pp_command_lists)
{ {
auto& mgr = get(); auto& mgr = get();
execute_command_lists_callback cb; callback_ptr<execute_command_lists_callback> cb;
{ {
std::shared_lock lock(mgr.m_mutex); std::shared_lock lock(mgr.m_execute_command_lists_mutex);
cb = mgr.m_execute_command_lists_cb; cb = mgr.m_execute_command_lists_cb;
} }
if (cb) if (cb)
cb(p_command_queue, num_command_lists, pp_command_lists); (*cb)(p_command_queue, num_command_lists, pp_command_lists);
mgr.m_dx12_execute_command_lists_hook.call<void>(p_command_queue, num_command_lists, pp_command_lists); mgr.m_dx12_execute_command_lists_hook.call<void>(p_command_queue, num_command_lists, pp_command_lists);
} }
BOOL __stdcall HooksManager::opengl_wgl_swap_buffers_detour(HDC hdc)
{
auto& mgr = get();
if (!g_is_inside_opengl_swap_buffers)
return mgr.m_opengl_wgl_swap_buffers_hook.call<BOOL>(hdc);
g_is_inside_opengl_swap_buffers = true;
callback_ptr<opengl_swap_buffers_callback> cb;
{
std::shared_lock lock(mgr.m_opengl_swap_buffers_mutex);
cb = mgr.m_opengl_swap_buffers_cb;
}
if (cb)
(*cb)(hdc);
const BOOL result = mgr.m_opengl_wgl_swap_buffers_hook.call<BOOL>(hdc);
g_is_inside_opengl_swap_buffers = false;
return result;
}
BOOL __stdcall HooksManager::opengl_swap_buffers_detour(HDC hdc)
{
auto& mgr = get();
if (g_is_inside_opengl_swap_buffers)
return mgr.m_opengl_swap_buffers_hook.call<BOOL>(hdc);
g_is_inside_opengl_swap_buffers = true;
callback_ptr<opengl_swap_buffers_callback> cb;
{
std::shared_lock lock(mgr.m_opengl_swap_buffers_mutex);
cb = mgr.m_opengl_swap_buffers_cb;
}
if (cb)
(*cb)(hdc);
const BOOL result = mgr.m_opengl_swap_buffers_hook.call<BOOL>(hdc);
g_is_inside_opengl_swap_buffers = false;
return result;
}
LRESULT __stdcall HooksManager::wnd_proc_detour(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param) LRESULT __stdcall HooksManager::wnd_proc_detour(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param)
{ {
auto& mgr = get(); auto& mgr = get();
wnd_proc_callback cb; callback_ptr<wnd_proc_callback> cb;
WNDPROC original; WNDPROC original;
{ {
std::shared_lock lock(mgr.m_mutex); std::shared_lock lock(mgr.m_wnd_proc_mutex);
cb = mgr.m_wnd_proc_cb; cb = mgr.m_wnd_proc_cb;
original = mgr.m_original_wndproc; original = mgr.m_original_wndproc;
} }
if (cb) if (cb)
{ {
if (const auto result = cb(hwnd, msg, w_param, l_param)) if (const auto result = (*cb)(hwnd, msg, w_param, l_param))
return *result; return *result;
} }
return CallWindowProc(original, hwnd, msg, w_param, l_param); return CallWindowProc(original, hwnd, msg, w_param, l_param);
} }
#endif // _WIN32
#ifdef __linux__
bool HooksManager::hook_opengl()
{
std::unique_lock lock(m_hook_state_mutex);
if (m_is_opengl_hooked)
return true;
void* glx_swap_buffers = dlsym(RTLD_DEFAULT, "glXSwapBuffers");
if (!glx_swap_buffers)
return false;
m_opengl_glx_swap_buffers_hook = safetyhook::create_inline(
glx_swap_buffers, reinterpret_cast<void*>(&opengl_glx_swap_buffers_detour));
if (!m_opengl_glx_swap_buffers_hook)
return false;
m_is_opengl_hooked = true;
return true;
}
void HooksManager::unhook_opengl()
{
std::unique_lock lock(m_hook_state_mutex);
m_opengl_glx_swap_buffers_hook = {};
m_is_opengl_hooked = false;
}
void HooksManager::opengl_glx_swap_buffers_detour(Display* display, GLXDrawable drawable)
{
auto& mgr = get();
callback_ptr<opengl_swap_buffers_callback> cb;
{
std::shared_lock lock(mgr.m_opengl_swap_buffers_mutex);
cb = mgr.m_opengl_swap_buffers_cb;
}
if (cb)
(*cb)(display, drawable);
mgr.m_opengl_glx_swap_buffers_hook.call<void>(display, drawable);
}
#endif // __linux__
void HooksManager::set_on_opengl_swap_buffers(opengl_swap_buffers_callback callback)
{
std::unique_lock lock(m_opengl_swap_buffers_mutex);
m_opengl_swap_buffers_cb =
callback ? std::make_shared<opengl_swap_buffers_callback>(std::move(callback)) : nullptr;
}
} // namespace omath::hooks } // namespace omath::hooks
#else // !OMATH_ENABLE_HOOKING #else // !OMATH_ENABLE_HOOKING