Commit Graph

6 Commits

Author SHA1 Message Date
0935dccfc4 Per-subsystem hook control: install/remove input, focus, audio at runtime
Add a host->hook control channel (protocol v5 -> v6: HookControl in SharedBlock,
per-subsystem "disabled" flags, 0 = install so the zero-filled default is
unchanged). The worker now reconciles each subsystem every tick: install what's
requested-and-missing, remove what's no longer wanted -- so the audio hooks
re-attach the ring and republish format on a reinstall, and XInput/focus clear
their stale status flags on removal.

Injection panel: a checkbox per subsystem (input forwarding / focus spoof /
audio render-hook) toggles it at runtime, showing the requested vs actual
installed state from the registry, plus DLL heartbeat liveness. The hook-status
section now keys off whether a DLL was injected (host-side) rather than the
input-hook "attached" flag, so it stays visible with input unhooked.

Guards for dependent features: the synthetic-input control is disabled when
input forwarding is off, and the Audio panel explains that mirroring uses
loopback (echo) when the render-hook is off.

Verified against Phantom Brave via coop_audio_probe: starting with audio
requested off installs only input+focus (8 hooks, no capture); re-enabling at
runtime installs the audio hooks (13) and capture starts immediately. All four
tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:17:48 +02:00
1f905940ef 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>
2026-06-19 20:09:39 +02:00
31390d8c15 Audio render-hook M4: audio panel source indicator + stream-count debug view
Surfaces the render-hook diagnostics in the Audio panel.

- HookStatusView / IpcServer::hook_status(): carry audio_streams_seen +
  audio_streams[] from the hook's back-channel.
- InjectionPanel: expose hook_status() so the audio panel can read the counts.
- AudioPanel::draw_ui(HookStatusView): show the active Source (green Hooked
  vs amber Loopback); only warn about the local echo on the loopback path. Add
  the required render-stream table: one row per stream with format, cumulative
  frames, the primary tagged, and a live/idle dot derived from frame-count
  deltas. Notes overflow when the game exceeds kMaxAudioStreams slots.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:05:28 +02:00
a0a12d69fe Phase 1a: per-slot poll viz + input-path diagnostics
Surfaced by a game (Life is Strange: Before the Storm) that ignores
controller input when it lacks true OS focus even though it still polls
XInput. To find the focus-gated detection path, instrument the hook.

Protocol v3 status back-channel now reports:
- per-slot XInputGetState and XInputGetCapabilities counters (replacing the
  single aggregate), so the overlay shows exactly which slots the game polls
  and how fast;
- focus-API call counts (GetForegroundWindow/GetActiveWindow/GetFocus) to see
  whether the game consults the APIs we spoof;
- input-path diagnostics: whether the process registered Raw Input for a
  gamepad usage and whether it set RIDEV_INPUTSINK (background delivery), and
  whether a DirectInput dll is loaded.

Host overlay gains a per-slot poll table and an "Input path" section. The DLL
refreshes input diagnostics each worker tick via GetRegisteredRawInputDevices.
hook_selftest updated for per-slot counters; passes.

This is diagnostic-only: once a real run shows which path LiS uses, the
targeted focus fix (e.g. forcing RIDEV_INPUTSINK or DI background coop) follows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 00:38:07 +02:00
df4325d21b 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>
2026-06-19 00:13:06 +02:00
e370c8dcc5 Phase 1a: input forwarding via DLL injection + XInput hook
The host can now inject coop_hook.dll into a running game and forward
controller state to it over shared memory, so the game reads the host's
(eventually the guest's) input and nothing else.

- hook/: coop_hook.dll. DllMain spawns a worker that opens the shared-memory
  channel (named by the game's pid) and installs SafetyHook inline hooks on
  XInputGetState/GetStateEx/GetCapabilities/SetState. Detours synthesize state
  from shared memory; unmanaged slots report disconnected, hiding physical pads.
- host/: process picker (Toolhelp32), CreateRemoteThread(LoadLibraryW) injector
  with an IsWow64Process2 bitness guard, IPC server publishing pads each frame,
  and an ImGui Injection panel wiring it together.
- tests/: hook_selftest exercises the IPC seqlock + hook detours in-process
  (no game/controller needed); passes.

Build: SafetyHook wired in (COOP_BUILD_HOOK=ON), Zydis via FetchContent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 23:21:20 +02:00