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:
@@ -11,7 +11,7 @@ namespace coop
|
||||
|
||||
// Bump whenever the layout of SharedBlock or CoopPadState changes. The hook
|
||||
// refuses to attach to a host with a mismatched version.
|
||||
inline constexpr std::uint32_t kProtocolVersion = 1;
|
||||
inline constexpr std::uint32_t kProtocolVersion = 2;
|
||||
|
||||
// 'COOP' little-endian, used to sanity-check the mapping before trusting it.
|
||||
inline constexpr std::uint32_t kProtocolMagic = 0x504F4F43u;
|
||||
@@ -42,6 +42,21 @@ struct CoopPadState
|
||||
|
||||
static_assert(sizeof(CoopPadState) == 20, "CoopPadState layout must stay stable across both modules");
|
||||
|
||||
// Hook -> host back-channel. The injected DLL is the sole writer; the host reads
|
||||
// it for the diagnostics overlay (is the hook attached? is the game actually
|
||||
// polling it? did focus spoofing take?). Diagnostics only, so the few non-atomic
|
||||
// fields tolerate benign cross-process races.
|
||||
struct HookStatus
|
||||
{
|
||||
std::atomic<std::uint32_t> heartbeat; // DLL bumps this ~4x/sec while alive
|
||||
std::atomic<std::uint64_t> xinput_queries; // cumulative XInputGetState/Ex calls served
|
||||
std::uint32_t attached; // 1 once XInput hooks are installed
|
||||
std::uint32_t focus_spoof; // 1 once focus spoofing is active
|
||||
std::uint32_t game_pid; // the DLL's own pid (sanity check)
|
||||
std::uint32_t last_user_index; // last slot the game queried
|
||||
std::uint64_t game_hwnd; // window the DLL subclassed (0 if none yet)
|
||||
};
|
||||
|
||||
// Top-level shared block. The host is the sole writer of pad state; the hook is
|
||||
// the sole reader. A seqlock (even = stable, odd = write in progress) lets the
|
||||
// reader grab a torn-free snapshot without a kernel lock on the hot path.
|
||||
@@ -53,12 +68,17 @@ struct SharedBlock
|
||||
std::atomic<std::uint32_t> sequence;
|
||||
CoopPadState pads[kMaxPads];
|
||||
|
||||
// Hook -> host diagnostics back-channel.
|
||||
HookStatus status;
|
||||
|
||||
// Phase 2 appends the shared-texture handle/dimensions control fields here;
|
||||
// keep new members at the end so existing offsets never shift.
|
||||
};
|
||||
|
||||
static_assert(std::atomic<std::uint32_t>::is_always_lock_free,
|
||||
"seqlock requires a lock-free 32-bit atomic for cross-process use");
|
||||
static_assert(std::atomic<std::uint64_t>::is_always_lock_free,
|
||||
"status counters need a lock-free 64-bit atomic for cross-process use");
|
||||
|
||||
// --- Seqlock helpers -------------------------------------------------------
|
||||
|
||||
|
||||
Reference in New Issue
Block a user