Commit Graph

3 Commits

Author SHA1 Message Date
8e93312925 Audio render-hook M1: shared audio ring + protocol diag fields
First milestone of the injection render-hook audio path (see
docs/audio-render-hook-plan.md) that fixes the local audio echo without a
virtual device.

- common/include/coop/audio_ring.hpp: new lock-free SPSC shared-memory ring
  for PCM, separate from the input/status SharedBlock. Free-running 64-bit
  positions (release/acquire), format handshake, host-owned capture_enabled
  gate, drop-whole-packet overrun policy.
- common/include/coop/protocol.hpp: add AudioStreamInfo + audio_streams_seen /
  audio_streams[] to the always-present HookStatus for the render-stream-count
  debug view; bump kProtocolVersion 3->4 (new members appended).
- tests/audio_ring_test.cpp: in-process unit test (push/pop integrity,
  wrap-around, format handshake, overrun/drop). No hook or audio device.
- docs/audio-render-hook-plan.md: the green-lit design this implements.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:49:18 +02:00
663d86e6ec Phase 2: audio mirror via WASAPI process loopback
Mirror the real game's audio so Steam Remote Play Together (which streams the
host's own audio session) carries it to the guest. The host captures the game
by PID via WASAPI process loopback and re-renders it on the default endpoint;
the game still plays locally too (accepted "double audio" for now).

- ProcessLoopbackCapture: process-loopback capture client, frame-sink + stats.
  The completion handler must be agile (IAgileObject) or
  ActivateAudioInterfaceAsync rejects every call with E_ILLEGAL_METHOD_CALL.
- AudioMirror: wraps capture with an event-driven render client and a primed
  ring buffer; AudioPanel drives it from the injected game's window/PID.
- coop_tone: standalone WASAPI sine-wave process used as a known audio source.
- audio_loopback_test (CTest): captures coop_tone by PID and asserts non-silent
  audio arrives, so the path is verifiable without a second Steam account.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 10:21:27 +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