Hook registry: list installed hooks + call counts in Injection panel

Add a process-wide hook registry (hook/src/hook_registry) that every hook
module registers its hooks with and bumps a counter from each detour. The
XInput, focus-spoof, and audio render-hooks now register their individual
hooks (XInputGetState/Ex/Caps/SetState; GetForegroundWindow/GetActiveWindow/
GetFocus/WndProc guard; IMMDevice::Activate, IAudioClient::Initialize/
GetService, IAudioRenderClient::GetBuffer/ReleaseBuffer) and count calls.

The worker publishes the table to the host each tick over a new HookStatus
field (protocol v4 -> v5: HookEntry[] + count). The Injection panel shows it
as a collapsible table grouped by subsystem with an installed flag and call
count per hook; coop_audio_probe prints the same table headless.

Verified against Phantom Brave: 13 hooks listed with live counts (focus APIs
polled heavily, GetBuffer/ReleaseBuffer ticking with the audio render loop).
All four tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 20:09:39 +02:00
parent 565934e8cf
commit 1f905940ef
16 changed files with 371 additions and 14 deletions

View File

@@ -6,6 +6,8 @@
#include <safetyhook.hpp>
#include "hook_registry.hpp"
namespace coop::hook
{
@@ -18,6 +20,11 @@ bool g_unicode = true;
std::vector<safetyhook::InlineHook> g_focus_hooks;
IpcClient* g_focus_ipc = nullptr;
int g_id_foreground = -1;
int g_id_active = -1;
int g_id_focus = -1;
int g_id_wndproc = -1;
struct EnumContext
{
DWORD pid;
@@ -67,15 +74,19 @@ LRESULT CALLBACK subclass_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam
if (LOWORD(wparam) == WA_INACTIVE)
{
wparam = MAKEWPARAM(WA_ACTIVE, HIWORD(wparam));
hook_note_call(g_id_wndproc);
}
break;
case WM_ACTIVATEAPP:
wparam = TRUE; // app is "still active"
hook_note_call(g_id_wndproc);
break;
case WM_NCACTIVATE:
wparam = TRUE; // keep the active (non-greyed) appearance
hook_note_call(g_id_wndproc);
break;
case WM_KILLFOCUS:
hook_note_call(g_id_wndproc);
return 0; // swallow: never tell the game it lost keyboard focus
default:
break;
@@ -86,6 +97,7 @@ LRESULT CALLBACK subclass_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam
HWND WINAPI hk_GetForegroundWindow()
{
hook_note_call(g_id_foreground);
if (g_focus_ipc != nullptr)
{
g_focus_ipc->note_focus_query(FocusApi_Foreground);
@@ -95,6 +107,7 @@ HWND WINAPI hk_GetForegroundWindow()
HWND WINAPI hk_GetActiveWindow()
{
hook_note_call(g_id_active);
if (g_focus_ipc != nullptr)
{
g_focus_ipc->note_focus_query(FocusApi_Active);
@@ -104,6 +117,7 @@ HWND WINAPI hk_GetActiveWindow()
HWND WINAPI hk_GetFocus()
{
hook_note_call(g_id_focus);
if (g_focus_ipc != nullptr)
{
g_focus_ipc->note_focus_query(FocusApi_Focus);
@@ -111,11 +125,12 @@ HWND WINAPI hk_GetFocus()
return g_game_hwnd;
}
void hook_export(HMODULE module, const char* name, void* detour)
void hook_export(HMODULE module, const char* name, void* detour, int registry_id)
{
if (void* target = reinterpret_cast<void*>(GetProcAddress(module, name)))
{
g_focus_hooks.emplace_back(safetyhook::create_inline(target, detour));
hook_set_installed(registry_id, true);
}
}
@@ -129,6 +144,11 @@ bool install_focus_spoof(IpcClient& ipc)
return true; // already active
}
g_id_foreground = hook_register("GetForegroundWindow", HookSubsys_Focus);
g_id_active = hook_register("GetActiveWindow", HookSubsys_Focus);
g_id_focus = hook_register("GetFocus", HookSubsys_Focus);
g_id_wndproc = hook_register("WndProc (deactivation guard)", HookSubsys_Focus);
HWND hwnd = find_main_window(GetCurrentProcessId());
if (hwnd == nullptr)
{
@@ -144,12 +164,14 @@ bool install_focus_spoof(IpcClient& ipc)
? SetWindowLongPtrW(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&subclass_proc))
: SetWindowLongPtrA(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&subclass_proc));
g_orig_proc = reinterpret_cast<WNDPROC>(replaced);
hook_set_installed(g_id_wndproc, true);
if (HMODULE user32 = GetModuleHandleW(L"user32.dll"))
{
hook_export(user32, "GetForegroundWindow", reinterpret_cast<void*>(&hk_GetForegroundWindow));
hook_export(user32, "GetActiveWindow", reinterpret_cast<void*>(&hk_GetActiveWindow));
hook_export(user32, "GetFocus", reinterpret_cast<void*>(&hk_GetFocus));
hook_export(user32, "GetForegroundWindow", reinterpret_cast<void*>(&hk_GetForegroundWindow),
g_id_foreground);
hook_export(user32, "GetActiveWindow", reinterpret_cast<void*>(&hk_GetActiveWindow), g_id_active);
hook_export(user32, "GetFocus", reinterpret_cast<void*>(&hk_GetFocus), g_id_focus);
}
ipc.mark_focus_spoof(true, reinterpret_cast<std::uint64_t>(hwnd));
@@ -204,6 +226,10 @@ void remove_focus_spoof()
}
}
g_focus_hooks.clear();
hook_set_installed(g_id_foreground, false);
hook_set_installed(g_id_active, false);
hook_set_installed(g_id_focus, false);
hook_set_installed(g_id_wndproc, false);
g_game_hwnd = nullptr;
g_orig_proc = nullptr;
g_focus_ipc = nullptr;