Phase 1a: focus spoofing + hook observability
Two problems surfaced in testing: (1) no way to tell whether the injected hook was actually the input source, and (2) the final design needs the tool window focused for Steam RPT capture, which would pause/silence games that react to focus loss. Both are addressed here. - Focus spoofing (hook/focus_spoof): find the game's main window, subclass it to rewrite/swallow WM_ACTIVATE/ACTIVATEAPP/NCACTIVATE/KILLFOCUS, and inline- hook GetForegroundWindow/GetActiveWindow/GetFocus to always report the game as active. The game keeps running and polling while unfocused. - Status back-channel (protocol v2): the DLL reports attached/focus-spoof flags, game pid/hwnd, a heartbeat, and a cumulative XInputGetState counter. The host overlay turns the counter into a live poll rate, so "is the hook working" is directly observable. - Synthetic test-input toggle in the host: forwards a known automated pattern (stick circle + periodic A) to prove forwarding independent of the physical pad. - hook_selftest extended to assert the status channel; passes. Documented the windowed/borderless requirement and the new observable test flow in the README. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,16 @@
|
||||
// coop_hook.dll -- injected into the target game by the host.
|
||||
//
|
||||
// On load it opens the host's shared-memory channel (named by this process's
|
||||
// pid), then hooks XInput so the game reads the forwarded controller state. All
|
||||
// real work happens on a worker thread; DllMain only kicks it off to stay clear
|
||||
// of the loader lock.
|
||||
// pid), hooks XInput so the game reads the forwarded controller state, and
|
||||
// spoofs focus so the game keeps running while the tool holds the real OS focus.
|
||||
// All real work happens on a worker thread; DllMain only kicks it off to stay
|
||||
// clear of the loader lock.
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "focus_spoof.hpp"
|
||||
#include "ipc_client.hpp"
|
||||
#include "xinput_hook.hpp"
|
||||
|
||||
@@ -14,8 +18,9 @@ namespace
|
||||
{
|
||||
|
||||
coop::hook::IpcClient g_ipc;
|
||||
std::atomic<bool> g_running{true};
|
||||
|
||||
DWORD WINAPI init_thread(LPVOID)
|
||||
DWORD WINAPI worker_thread(LPVOID)
|
||||
{
|
||||
// The host creates the mapping around injection time; give it a few seconds.
|
||||
if (!g_ipc.connect(/*attempts=*/200, /*delay_ms=*/25))
|
||||
@@ -23,11 +28,23 @@ DWORD WINAPI init_thread(LPVOID)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// XInput may not be loaded yet at this point (games often load it lazily on
|
||||
// first controller use), so keep retrying until a module appears.
|
||||
for (int i = 0; i < 400 && !coop::hook::install_xinput_hooks(g_ipc); ++i)
|
||||
bool xinput_installed = false;
|
||||
bool focus_installed = false;
|
||||
|
||||
// Keep retrying the installs (XInput and the game window may both appear
|
||||
// lazily) and beat a heartbeat so the host can show the hook is alive.
|
||||
while (g_running.load(std::memory_order_relaxed))
|
||||
{
|
||||
Sleep(25);
|
||||
if (!xinput_installed)
|
||||
{
|
||||
xinput_installed = coop::hook::install_xinput_hooks(g_ipc);
|
||||
}
|
||||
if (!focus_installed)
|
||||
{
|
||||
focus_installed = coop::hook::install_focus_spoof(g_ipc);
|
||||
}
|
||||
g_ipc.heartbeat();
|
||||
Sleep(250);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -40,7 +57,7 @@ BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
DisableThreadLibraryCalls(module);
|
||||
if (HANDLE thread = CreateThread(nullptr, 0, &init_thread, nullptr, 0, nullptr))
|
||||
if (HANDLE thread = CreateThread(nullptr, 0, &worker_thread, nullptr, 0, nullptr))
|
||||
{
|
||||
CloseHandle(thread);
|
||||
}
|
||||
@@ -50,6 +67,8 @@ BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved)
|
||||
// loader is already unwinding and touching other modules is unsafe.
|
||||
if (reserved == nullptr)
|
||||
{
|
||||
g_running.store(false, std::memory_order_relaxed);
|
||||
coop::hook::remove_focus_spoof();
|
||||
coop::hook::remove_xinput_hooks();
|
||||
}
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user