Phase 0: donor-launch spike foundation

Scaffold CoopAllTheThings: Remote Play Together for any XInput game via a
mirror app under a donor appid (real game keeps its own appid, so DRM,
achievements, and playtime stay intact).

- Build: CMake skeleton, ImGui + SafetyHook submodules (no vcpkg)
- common/: host<->hook IPC contract (seqlock pad state, shared-memory RAII)
- host/: borderless D3D11 window + ImGui overlay listing visible XInput pads,
  behind an InputSource interface (Steam Input slots in later)
- README documents the Phase 0 donor-launch validation procedure, anti-cheat
  limitation, and XInput/bitness constraints

Phase 0 validates the riskiest assumption (Steam RPT streams an arbitrary
window under a donor appid and routes guest input to it) before capture and
injection are built.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-18 22:48:31 +02:00
commit cf058aecfa
23 changed files with 1129 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
#include "input/xinput_source.hpp"
#include <windows.h>
#include <xinput.h>
namespace coop
{
void XInputSource::poll()
{
for (DWORD i = 0; i < kMaxPads; ++i)
{
XINPUT_STATE state = {};
const DWORD result = XInputGetState(i, &state);
PadInfo& info = pads_[i];
if (result == ERROR_SUCCESS)
{
info.connected = true;
info.source = "XInput #" + std::to_string(i);
// XINPUT_GAMEPAD is laid out identically to the tail of CoopPadState,
// so copy field by field to keep the mapping explicit and safe.
const XINPUT_GAMEPAD& g = state.Gamepad;
info.state.connected = 1;
info.state.packet = state.dwPacketNumber;
info.state.buttons = g.wButtons;
info.state.left_trigger = g.bLeftTrigger;
info.state.right_trigger = g.bRightTrigger;
info.state.thumb_lx = g.sThumbLX;
info.state.thumb_ly = g.sThumbLY;
info.state.thumb_rx = g.sThumbRX;
info.state.thumb_ry = g.sThumbRY;
}
else
{
info = PadInfo{};
}
}
}
} // namespace coop