# CoopAllTheThings Steam **Remote Play Together (RPT)** for any XInput game — without breaking DRM, achievements, or playtime. Existing "donor game" tools (e.g. RemotePlayWhatever) copy a target game's files into a donor game's folder and rename the executable so Steam streams the target under the donor's appid. That breaks DRM-protected games, breaks achievements, and credits playtime to the donor. CoopAllTheThings takes a different approach: the **real game runs normally under its own appid** (so DRM, achievements, and playtime all work), while a lightweight **mirror app runs under the donor appid**. The mirror presents a borderless window that is a live copy of the game's video + audio, and forwards the guests' input back into the real game. Steam's RPT captures the mirror window — so any XInput game becomes Remote-Play-Together-able. See [`docs`](docs) and the in-repo plan for the full design. ## Architecture (target) | Concern | Mechanism | Where | | --- | --- | --- | | Receive guest input | Steam Input / XInput (RPT delivers guests to the focused window) | `coop_host.exe` | | Forward input to game | DLL injection + XInput hook (SafetyHook) — game sees *only* our pad | `coop_hook.dll` | | Mirror video | Windows Graphics Capture first; `IDXGISwapChain::Present` hook as the low-latency upgrade | host (+ hook) | | Mirror audio | WASAPI process-loopback capture of the game, re-rendered | `coop_host.exe` | | Host ↔ hook IPC | Named shared memory (seqlock for input, shared D3D11 texture for video) | `common/` | ## Limitations - **Anti-cheat:** the input path injects `coop_hook.dll` into the target game. Games protected by kernel-level anti-cheat (Easy Anti-Cheat, BattlEye, Vanguard, etc.) will detect the injected module and may **kick the player or issue a ban**. Such games are explicitly **out of scope and unsupported** — do not use CoopAllTheThings with them. The tool targets single-player and co-op/local-multiplayer titles without active anti-cheat. - **XInput only:** the game must read controllers via XInput (the common case). DirectInput-only / RawInput-only games are not handled. - **Architecture match:** the host and hook DLL must match the game's bitness. x64 is supported first; x86 support is a later phase (see the plan). ## Status **Phase 0 — donor spike. ✅ Validated.** `coop_host.exe` is a borderless D3D11 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 + 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. **Phase 1b — video mirror (current).** The host captures the injected game's window with Windows Graphics Capture and draws it letterboxed as its background, so RPT streams a live mirror of the game. See the test steps below. Still ahead (scoped in the plan): a `Present`-hook capture path if WGC latency disappoints, audio (WASAPI process loopback), and x86 support. ## Building Requirements: Windows 10/11, Visual Studio 2022 (MSVC + C++ workload), CMake ≥ 3.21. ```sh git clone --recurse-submodules # or, if already cloned: git submodule update --init --recursive cmake -S . -B build -G "Visual Studio 17 2022" -A x64 cmake --build build --config Debug # output: bin/Debug/coop_host.exe ``` Third-party dependencies (Dear ImGui, SafetyHook) are git submodules under `third_party/`; the Steamworks SDK is vendored manually there when wired. No vcpkg / package manager is used. ## Phase 0: validating the donor-launch assumption This is a manual test — it needs Steam, a second person (or second account), and a donor game that supports Remote Play Together. 1. **Pick a donor game** you own that supports Remote Play Together (check the "Remote Play Together" tag on its store page). The donor only needs RPT support; it is never actually played. 2. **Launch the host under the donor's appid.** Find the donor's appid (the number in its store URL), then run: ```text "C:\Program Files (x86)\Steam\steam.exe" -applaunch "D:\dev\CoopAllTheThings\bin\Debug\coop_host.exe" ``` The borderless overlay window should appear and Steam should consider the donor "running" (green status / "Stop" button in the library). > If the donor ignores the trailing path, set the host as the donor's > **Launch Options** instead (`"D:\...\coop_host.exe" %command%` variants), > or use a launcher such as RemotePlayDetached. Recording which method makes > Steam attribute our window to the donor is the main deliverable of Phase 0. 3. **Start Remote Play Together** from the Steam friends list / overlay and invite a friend (or a second machine/account). 4. **Verify on the guest side:** - The guest sees the borderless overlay window streamed (not a black screen). - The guest presses buttons on their controller and the corresponding slot in the overlay lights up. This proves RPT routes guest input into our window as XInput — the foundation the whole tool relies on. 5. Press **Esc** in the host window to quit. **If steps 2 and 4 both work, the core premise holds** and we proceed to Phase 1 (window capture + input injection). If not, we revisit the donor-attribution approach before building further. ## Phase 1a: testing input forwarding locally 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//`). **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**. 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). For a quick sanity check of the forwarding core without a game, run `bin\Debug\hook_selftest.exe` — it should print `SELFTEST PASS`. ## Phase 1b: video mirror (Windows Graphics Capture) The **Video mirror** panel captures the injected game's window (via Windows Graphics Capture — no injection needed for video) and draws it, letterboxed, as the host window's background. This is what Steam RPT streams to guests. 1. Inject into a game as in Phase 1a (the hook reports the game's window, which the mirror uses as its source). 2. In the **Video mirror** panel, tick **Mirror game window**. The host window should now show a live copy of the game; the panel reports the capture resolution and render FPS. 3. The game can be behind the host window — WGC still captures occluded (but not minimized) windows, and focus spoofing keeps it rendering. Requires Windows 10 1903+ for WGC; hiding the capture border needs build 20348+. This is the place to judge capture latency/stutter; if it's not good enough, the fallback is a `Present`-hook capture path in the same injected DLL.