#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(); // 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(); log_ring_init(*log_ring_, kLogCapacity); log_cursor_ = 0; } return true; } void IpcServer::publish(const std::array& 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