The render-hook only installed IAudioClient/IAudioRenderClient hooks reactively, when it saw the game call IMMDevice::Activate -> GetService. But we attach to a game that is already running and playing audio, so its render client was created before injection: those calls never fire again, no primary stream is ever registered, nothing is captured, and the host always falls back to process loopback (the echo). Every game tested did so. Fix: at anchor time, build our own probe IAudioClient + IAudioRenderClient with raw calls and hook GetBuffer/ReleaseBuffer (plus Initialize/GetService) on their vtables. Every instance of a COM coclass shares one vtable, so this patches the shared vtables and intercepts the game's pre-existing render client too. The first render client seen actively releasing buffers is adopted as primary on the audio thread (try-lock, one-time) using the device mix format as its assumed format (we never saw its Initialize). Streams created after injection still register via the reactive path with their real format. Also fixes a self-deadlock: installing the Activate hook before the probe's own device->Activate call re-entered hk_Activate -> install_audioclient_hooks, which blocked on the setup mutex the installer already held, freezing the worker (and any game thread that later called Activate -> crash). The probe objects are now created raw, before any hook is installed. Validated against Phantom Brave (injected while already playing): the pre-existing 48 kHz/2ch/float render client is detected and registered as primary, real non-silent audio reaches the ring (peak tracks the game's levels), and a draining consumer sees zero overruns. Tooling for iterating on real games without Steam/RPT/the host UI: - tools/audio_probe: creates the IPC block + audio ring, injects the hook, drains the ring and prints stream/format/peak/overrun diagnostics by pid. - hook/src/debug_log: opt-in file trace (%TEMP%\coop_hook.log), enabled by the COOP_HOOK_LOG env var or the %TEMP%\coop_hook.log.on sentinel the probe drops; off in normal use. All four tests still pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
51 lines
1.8 KiB
CMake
51 lines
1.8 KiB
CMake
cmake_minimum_required(VERSION 3.21)
|
|
|
|
project(CoopAllTheThings
|
|
VERSION 0.0.1
|
|
DESCRIPTION "Steam Remote Play Together for any XInput game"
|
|
LANGUAGES CXX)
|
|
|
|
if(NOT WIN32)
|
|
message(FATAL_ERROR "CoopAllTheThings targets Windows only.")
|
|
endif()
|
|
|
|
set(CMAKE_CXX_STANDARD 20)
|
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
|
|
# Emit compile_commands.json so clangd resolves includes/flags. Ignored by the
|
|
# Visual Studio generator, so configure the Ninja build (build-clangd/) for this;
|
|
# .clangd points clangd at that compilation database.
|
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|
|
|
# Single output dir keeps the donor-launch / inject workflow simple: host exe and
|
|
# hook dll land next to each other under /bin (already in .gitignore).
|
|
set(COOP_OUTPUT_DIR "${CMAKE_SOURCE_DIR}/bin/$<CONFIG>")
|
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${COOP_OUTPUT_DIR}")
|
|
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${COOP_OUTPUT_DIR}")
|
|
|
|
if(MSVC)
|
|
add_compile_options(/W4 /permissive- /Zc:__cplusplus /utf-8 /MP)
|
|
add_compile_definitions(UNICODE _UNICODE WIN32_LEAN_AND_MEAN NOMINMAX)
|
|
endif()
|
|
|
|
add_subdirectory(third_party)
|
|
add_subdirectory(common)
|
|
add_subdirectory(host)
|
|
|
|
# The injected hook DLL pulls in SafetyHook, which fetches Zydis via FetchContent
|
|
# at configure time (allowed for transitive deps).
|
|
option(COOP_BUILD_HOOK "Build the injected game-side hook DLL" ON)
|
|
if(COOP_BUILD_HOOK)
|
|
# SafetyHook's own tests/examples/docs are noise for us.
|
|
set(SAFETYHOOK_BUILD_TEST OFF CACHE BOOL "" FORCE)
|
|
set(SAFETYHOOK_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
|
set(SAFETYHOOK_BUILD_DOCS OFF CACHE BOOL "" FORCE)
|
|
add_subdirectory(third_party/safetyhook)
|
|
add_subdirectory(hook)
|
|
enable_testing()
|
|
add_subdirectory(tools/audio_tone) # coop_tone: audio source for the loopback test
|
|
add_subdirectory(tools/audio_probe) # coop_audio_probe: inject + diagnose the render-hook
|
|
add_subdirectory(tests)
|
|
endif()
|