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>
104 lines
2.6 KiB
C++
104 lines
2.6 KiB
C++
#include "hook_registry.hpp"
|
|
|
|
#include <atomic>
|
|
#include <cstring>
|
|
#include <mutex>
|
|
|
|
namespace coop::hook
|
|
{
|
|
|
|
namespace
|
|
{
|
|
|
|
struct Slot
|
|
{
|
|
char name[40] = {};
|
|
std::atomic<std::uint32_t> subsystem{0};
|
|
std::atomic<std::uint32_t> installed{0};
|
|
std::atomic<std::uint64_t> calls{0};
|
|
std::atomic<std::uint32_t> used{0};
|
|
};
|
|
|
|
Slot g_slots[kMaxHookEntries];
|
|
std::atomic<std::uint32_t> g_count{0}; // high-water mark of allocated slots
|
|
std::mutex g_register_mutex; // registration only (rare); calls are lock-free
|
|
|
|
} // namespace
|
|
|
|
int hook_register(const char* name, std::uint32_t subsystem)
|
|
{
|
|
std::scoped_lock lock(g_register_mutex);
|
|
const std::uint32_t count = g_count.load(std::memory_order_relaxed);
|
|
for (std::uint32_t i = 0; i < count; ++i)
|
|
{
|
|
if (g_slots[i].used.load(std::memory_order_relaxed) && std::strcmp(g_slots[i].name, name) == 0)
|
|
{
|
|
return static_cast<int>(i); // already registered
|
|
}
|
|
}
|
|
if (count >= kMaxHookEntries)
|
|
{
|
|
return -1; // table full
|
|
}
|
|
Slot& s = g_slots[count];
|
|
std::strncpy(s.name, name, sizeof(s.name) - 1);
|
|
s.name[sizeof(s.name) - 1] = '\0';
|
|
s.subsystem.store(subsystem, std::memory_order_relaxed);
|
|
s.installed.store(0, std::memory_order_relaxed);
|
|
s.calls.store(0, std::memory_order_relaxed);
|
|
s.used.store(1, std::memory_order_release);
|
|
g_count.store(count + 1, std::memory_order_release);
|
|
return static_cast<int>(count);
|
|
}
|
|
|
|
void hook_set_installed(int id, bool installed)
|
|
{
|
|
if (id >= 0 && id < static_cast<int>(kMaxHookEntries))
|
|
{
|
|
g_slots[id].installed.store(installed ? 1u : 0u, std::memory_order_relaxed);
|
|
}
|
|
}
|
|
|
|
void hook_note_call(int id)
|
|
{
|
|
if (id >= 0 && id < static_cast<int>(kMaxHookEntries))
|
|
{
|
|
g_slots[id].calls.fetch_add(1, std::memory_order_relaxed);
|
|
}
|
|
}
|
|
|
|
void hook_publish(IpcClient& ipc)
|
|
{
|
|
const std::uint32_t count = g_count.load(std::memory_order_acquire);
|
|
HookEntry entries[kMaxHookEntries];
|
|
std::uint32_t n = 0;
|
|
for (std::uint32_t i = 0; i < count && i < kMaxHookEntries; ++i)
|
|
{
|
|
if (!g_slots[i].used.load(std::memory_order_acquire))
|
|
{
|
|
continue;
|
|
}
|
|
HookEntry& e = entries[n];
|
|
std::memcpy(e.name, g_slots[i].name, sizeof(e.name));
|
|
e.subsystem = g_slots[i].subsystem.load(std::memory_order_relaxed);
|
|
e.installed = g_slots[i].installed.load(std::memory_order_relaxed);
|
|
e.calls = g_slots[i].calls.load(std::memory_order_relaxed);
|
|
++n;
|
|
}
|
|
ipc.publish_hook_entries(entries, n);
|
|
}
|
|
|
|
void hook_registry_reset()
|
|
{
|
|
std::scoped_lock lock(g_register_mutex);
|
|
for (auto& s : g_slots)
|
|
{
|
|
s.used.store(0, std::memory_order_relaxed);
|
|
s.installed.store(0, std::memory_order_relaxed);
|
|
s.calls.store(0, std::memory_order_relaxed);
|
|
}
|
|
g_count.store(0, std::memory_order_release);
|
|
}
|
|
|
|
} // namespace coop::hook
|