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:
@@ -14,6 +14,7 @@
|
||||
#include <safetyhook.hpp>
|
||||
|
||||
#include "debug_log.hpp"
|
||||
#include "hook_registry.hpp"
|
||||
|
||||
namespace coop::hook
|
||||
{
|
||||
@@ -55,6 +56,13 @@ safetyhook::InlineHook g_hk_getbuffer;
|
||||
safetyhook::InlineHook g_hk_releasebuffer;
|
||||
bool g_audioclient_hooked = false;
|
||||
|
||||
// Registry ids for the hook list.
|
||||
int g_id_activate = -1;
|
||||
int g_id_initialize = -1;
|
||||
int g_id_getservice = -1;
|
||||
int g_id_getbuffer = -1;
|
||||
int g_id_releasebuffer = -1;
|
||||
|
||||
// Our own probe COM objects, created at anchor time purely to read the shared
|
||||
// IAudioClient / IAudioRenderClient vtables and hook GetBuffer/ReleaseBuffer
|
||||
// *proactively* — so render clients the game created before we injected (the
|
||||
@@ -142,6 +150,7 @@ void try_register_lazy(IAudioRenderClient* rc);
|
||||
|
||||
HRESULT STDMETHODCALLTYPE hk_GetBuffer(IAudioRenderClient* self, UINT32 num_frames, BYTE** data)
|
||||
{
|
||||
hook_note_call(g_id_getbuffer);
|
||||
const HRESULT hr = g_hk_getbuffer.call<HRESULT>(self, num_frames, data);
|
||||
if (SUCCEEDED(hr) && data != nullptr)
|
||||
{
|
||||
@@ -154,6 +163,7 @@ HRESULT STDMETHODCALLTYPE hk_GetBuffer(IAudioRenderClient* self, UINT32 num_fram
|
||||
|
||||
HRESULT STDMETHODCALLTYPE hk_ReleaseBuffer(IAudioRenderClient* self, UINT32 num_frames, DWORD flags)
|
||||
{
|
||||
hook_note_call(g_id_releasebuffer);
|
||||
// A render client we've never seen actively rendering is almost certainly one
|
||||
// the game created before we injected; adopt it now (the first becomes the
|
||||
// primary we capture). Skip our own silent probe client.
|
||||
@@ -300,6 +310,7 @@ HRESULT STDMETHODCALLTYPE hk_Initialize(IAudioClient* self, AUDCLNT_SHAREMODE mo
|
||||
REFERENCE_TIME buffer_duration, REFERENCE_TIME periodicity,
|
||||
const WAVEFORMATEX* format, LPCGUID session)
|
||||
{
|
||||
hook_note_call(g_id_initialize);
|
||||
const HRESULT hr =
|
||||
g_hk_initialize.call<HRESULT>(self, mode, flags, buffer_duration, periodicity, format, session);
|
||||
logf("hk_Initialize: client=%p mode=%d flags=0x%lX hr=0x%08lX fmt=%s", self, mode,
|
||||
@@ -314,6 +325,7 @@ HRESULT STDMETHODCALLTYPE hk_Initialize(IAudioClient* self, AUDCLNT_SHAREMODE mo
|
||||
|
||||
HRESULT STDMETHODCALLTYPE hk_GetService(IAudioClient* self, REFIID riid, void** ppv)
|
||||
{
|
||||
hook_note_call(g_id_getservice);
|
||||
const HRESULT hr = g_hk_getservice.call<HRESULT>(self, riid, ppv);
|
||||
const bool is_render = (riid == __uuidof(IAudioRenderClient));
|
||||
logf("hk_GetService: client=%p hr=0x%08lX render_client=%d", self, static_cast<unsigned long>(hr),
|
||||
@@ -371,6 +383,7 @@ void install_audioclient_hooks(IAudioClient* ac)
|
||||
HRESULT STDMETHODCALLTYPE hk_Activate(IMMDevice* self, REFIID riid, DWORD cls_ctx, PROPVARIANT* params,
|
||||
void** ppv)
|
||||
{
|
||||
hook_note_call(g_id_activate);
|
||||
const HRESULT hr = g_hk_activate.call<HRESULT>(self, riid, cls_ctx, params, ppv);
|
||||
const bool is_audioclient = (riid == __uuidof(IAudioClient) || riid == __uuidof(IAudioClient2) ||
|
||||
riid == __uuidof(IAudioClient3));
|
||||
@@ -395,6 +408,12 @@ bool install_audio_hooks(IpcClient& ipc, AudioRingHeader* ring)
|
||||
return true; // anchor already installed
|
||||
}
|
||||
|
||||
g_id_activate = hook_register("IMMDevice::Activate", HookSubsys_Audio);
|
||||
g_id_initialize = hook_register("IAudioClient::Initialize", HookSubsys_Audio);
|
||||
g_id_getservice = hook_register("IAudioClient::GetService", HookSubsys_Audio);
|
||||
g_id_getbuffer = hook_register("IAudioRenderClient::GetBuffer", HookSubsys_Audio);
|
||||
g_id_releasebuffer = hook_register("IAudioRenderClient::ReleaseBuffer", HookSubsys_Audio);
|
||||
|
||||
// Anchor: instantiate our own enumerator + default render device purely to
|
||||
// read the shared IMMDevice vtable and hook Activate. Every IMMDevice in the
|
||||
// process shares this vtable, so the game's Activate calls are intercepted.
|
||||
@@ -471,6 +490,12 @@ bool install_audio_hooks(IpcClient& ipc, AudioRingHeader* ring)
|
||||
// stay valid; they're released in remove_audio_hooks.
|
||||
}
|
||||
|
||||
hook_set_installed(g_id_activate, static_cast<bool>(g_hk_activate));
|
||||
hook_set_installed(g_id_initialize, static_cast<bool>(g_hk_initialize));
|
||||
hook_set_installed(g_id_getservice, static_cast<bool>(g_hk_getservice));
|
||||
hook_set_installed(g_id_getbuffer, static_cast<bool>(g_hk_getbuffer));
|
||||
hook_set_installed(g_id_releasebuffer, static_cast<bool>(g_hk_releasebuffer));
|
||||
|
||||
logf("install_audio_hooks: activate=%d init=%d getsvc=%d getbuf=%d relbuf=%d (device=%p)",
|
||||
static_cast<bool>(g_hk_activate) ? 1 : 0, static_cast<bool>(g_hk_initialize) ? 1 : 0,
|
||||
static_cast<bool>(g_hk_getservice) ? 1 : 0, static_cast<bool>(g_hk_getbuffer) ? 1 : 0,
|
||||
@@ -522,6 +547,11 @@ void remove_audio_hooks()
|
||||
g_hk_initialize = {};
|
||||
g_hk_activate = {};
|
||||
g_audioclient_hooked = false;
|
||||
hook_set_installed(g_id_activate, false);
|
||||
hook_set_installed(g_id_initialize, false);
|
||||
hook_set_installed(g_id_getservice, false);
|
||||
hook_set_installed(g_id_getbuffer, false);
|
||||
hook_set_installed(g_id_releasebuffer, false);
|
||||
|
||||
// Hooks are gone; safe to drop the probe objects that held the vtables.
|
||||
if (IAudioRenderClient* sr = g_self_render.exchange(nullptr, std::memory_order_acq_rel))
|
||||
|
||||
Reference in New Issue
Block a user