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:
41
README.md
41
README.md
@@ -47,11 +47,15 @@ window with an ImGui overlay listing every controller it sees. Confirmed
|
||||
end-to-end: Steam RPT streams the window under a donor appid, and guest gamepads
|
||||
arrive (with correct slot assignment) as XInput.
|
||||
|
||||
**Phase 1a — input forwarding (current).** The host can inject `coop_hook.dll`
|
||||
into a running game; the DLL hooks XInput (via SafetyHook) so the game reads the
|
||||
controller state the host forwards over shared memory — and *only* that state, so
|
||||
physical/other controllers are hidden from the game. The in-process
|
||||
`hook_selftest` validates the IPC + hook core without needing a game.
|
||||
**Phase 1a — input forwarding + focus spoofing (current).** The host injects
|
||||
`coop_hook.dll`; the DLL hooks XInput (via SafetyHook) so the game reads the
|
||||
forwarded controller state and *only* that state. The DLL also **spoofs focus**
|
||||
(hooks `GetForegroundWindow`/`GetActiveWindow`/`GetFocus` and subclasses the game
|
||||
window to swallow deactivation messages) so the game keeps running and polling
|
||||
while the tool holds the real OS focus — required because Steam RPT only captures
|
||||
the focused window. A hook→host status back-channel shows whether the hook is
|
||||
attached and how fast the game is polling it. The in-process `hook_selftest`
|
||||
validates the IPC + hook core without needing a game.
|
||||
|
||||
Still ahead (scoped in the plan): video mirror (WGC, then a `Present` hook),
|
||||
audio (WASAPI process loopback), and x86 support.
|
||||
@@ -119,15 +123,26 @@ This needs no RPT, donor, or second account — just the host, the hook, a
|
||||
controller, and a target game. `coop_host.exe` and `coop_hook.dll` must sit in
|
||||
the same folder (the build places both in `bin/<Config>/`).
|
||||
|
||||
1. Start a DRM-free, **non-anti-cheat**, XInput game (e.g. a small controller
|
||||
sample or a permissive indie title) and get to a screen that reads the pad.
|
||||
**Requirement:** run the target game **windowed or borderless**, not exclusive
|
||||
fullscreen. Exclusive fullscreen minimizes on focus loss (defeating the focus
|
||||
spoof) and can't be window-captured later. Only **controller** input is
|
||||
forwarded — while the game is unfocused it won't receive OS keyboard/mouse.
|
||||
|
||||
1. Start a DRM-free, **non-anti-cheat**, XInput game in windowed/borderless mode
|
||||
and get to a screen that reads the pad.
|
||||
2. Run `bin\Debug\coop_host.exe`. In the **Injection** panel, filter for the
|
||||
game's `.exe`, select it, and click **Inject & Connect**. The status line
|
||||
should turn green ("Injected … / Forwarding input to pid …").
|
||||
3. Press buttons on your physical controller. The game should respond — its
|
||||
XInput now comes from the host's forwarded state, not the device directly.
|
||||
Unplug-test: other controllers/slots are hidden from the game.
|
||||
4. Click **Stop forwarding** (or quit the host) to tear down the channel.
|
||||
game's `.exe`, select it, and click **Inject & Connect**.
|
||||
3. Watch the **Hook status** section. Once it shows **Attached** and a non-zero
|
||||
**"XInput polled: N/s"**, the game is provably reading our hook — injection
|
||||
works. **Focus spoof: active** confirms the window was found and subclassed.
|
||||
4. **Prove forwarding is the source:** tick **Forward synthetic test input**.
|
||||
The game should now move on its own — left stick sweeping a circle, A pressed
|
||||
every other second — independent of your physical controller. Untick it to
|
||||
return control to your pad.
|
||||
5. Sanity-check the focus spoof: click into another window so the game loses real
|
||||
focus. It should keep running/animating (not pause), and the poll rate should
|
||||
stay non-zero.
|
||||
6. Click **Stop forwarding** (or quit the host) to tear down the channel.
|
||||
|
||||
> If injection fails with an access error, run the host as administrator. If it
|
||||
> reports "target is 32-bit", that game needs the x86 hook (a later phase).
|
||||
|
||||
Reference in New Issue
Block a user