Files
CoopAllTheThings/host/src/ipc/ipc_server.cpp
BlackMark 327ba1f394 Add per-backend input debug visualization + round-trip view
Makes it possible to isolate an input->tool problem from a tool->game one in the
Controllers panel (under Debug details):
- which backend fed each slot is already shown via the per-pad source tag
  ("XInput #0" / "Steam Input") in the Incoming section;
- a new "Round-trip" table shows, per slot, the state we forwarded (the active
  backend's pad) next to what the game actually read back, highlighting matches.

For the round-trip, the XInput hook now echoes the state it returns to the game into
the status (protocol v9->v10: per-slot read_state in HookStatus).

Verified: hook_selftest (x64 + x86) asserts the hook records the game-read state;
full build x64 + x86 clean; ctest x64 9/9, x86 3/3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 05:15:07 +02:00

137 lines
3.5 KiB
C++

#include "ipc/ipc_server.hpp"
namespace coop
{
bool IpcServer::start(unsigned long target_pid)
{
stop();
if (!shm_.create(shared_memory_name(target_pid), sizeof(SharedBlock)))
{
return false;
}
auto* block = shm_.as<SharedBlock>();
// Fresh CreateFileMapping pages are zero-filled; set the header before the
// hook (which validates magic/version) has a chance to read it.
block->version = kProtocolVersion;
block->pad_count = 0;
block->sequence.store(0, std::memory_order_relaxed);
block->magic = kProtocolMagic; // publish magic last so a racing reader bails
block_ = block;
target_pid_ = target_pid;
// Log ring: the injected hook opens this and streams its log lines back for the
// Log window. Best-effort -- the rest of the tool works without it.
if (log_shm_.create(log_ring_name(target_pid), log_ring_total_size(kLogCapacity)))
{
log_ring_ = log_shm_.as<LogRing>();
log_ring_init(*log_ring_, kLogCapacity);
log_cursor_ = 0;
}
return true;
}
void IpcServer::publish(const std::array<PadInfo, kMaxPads>& pads)
{
if (block_ == nullptr)
{
return;
}
CoopPadState states[kMaxPads];
for (std::size_t i = 0; i < pads.size(); ++i)
{
states[i] = pads[i].state;
states[i].connected = pads[i].connected ? 1 : 0;
}
publish_pads(*block_, states, kMaxPads);
}
HookStatusView IpcServer::hook_status() const
{
HookStatusView view;
if (block_ == nullptr)
{
return view;
}
const HookStatus& s = block_->status;
view.attached = s.attached != 0;
view.focus_spoof = s.focus_spoof != 0;
view.heartbeat = s.heartbeat.load(std::memory_order_relaxed);
for (std::uint32_t i = 0; i < kMaxPads; ++i)
{
view.get_state[i] = s.get_state_calls[i].load(std::memory_order_relaxed);
view.get_caps[i] = s.get_caps_calls[i].load(std::memory_order_relaxed);
}
for (std::uint32_t i = 0; i < FocusApi_Count; ++i)
{
view.focus_calls[i] = s.focus_query_calls[i].load(std::memory_order_relaxed);
}
view.game_pid = s.game_pid;
view.game_hwnd = s.game_hwnd;
view.raw_input_registered = s.raw_input_registered != 0;
view.raw_input_gamepad = s.raw_input_gamepad != 0;
view.raw_input_gamepad_sink = s.raw_input_gamepad_sink != 0;
view.dinput_loaded = s.dinput_loaded != 0;
view.audio_streams_seen = s.audio_streams_seen;
for (std::uint32_t i = 0; i < kMaxAudioStreams; ++i)
{
view.audio_streams[i] = s.audio_streams[i];
}
view.hook_entry_count = s.hook_entry_count;
for (std::uint32_t i = 0; i < kMaxHookEntries; ++i)
{
view.hook_entries[i] = s.hook_entries[i];
}
for (std::uint32_t i = 0; i < kMaxPads; ++i)
{
view.rumble_left[i] = s.rumble_left[i];
view.rumble_right[i] = s.rumble_right[i];
view.read_state[i] = s.read_state[i];
}
return view;
}
VideoShareView IpcServer::video_share() const
{
VideoShareView v;
if (block_ == nullptr)
{
return v;
}
const VideoShare& s = block_->video;
v.generation = s.generation.load(std::memory_order_acquire);
v.width = s.width;
v.height = s.height;
v.format = s.format;
v.present_calls = s.present_calls;
return v;
}
void IpcServer::set_subsystem_enabled(std::uint32_t subsystem, bool enabled)
{
if (block_ != nullptr && subsystem < HookSubsys_Count)
{
// 0 = install, 1 = remove.
block_->control.subsystem_disabled[subsystem].store(enabled ? 0u : 1u, std::memory_order_release);
}
}
void IpcServer::stop()
{
if (block_ != nullptr)
{
block_->magic = 0; // invalidate so a late hook read won't trust stale data
block_ = nullptr;
}
shm_.reset();
log_ring_ = nullptr;
log_shm_.reset();
log_cursor_ = 0;
target_pid_ = 0;
}
} // namespace coop