// Hook-side view of the shared-memory IPC channel. The host creates the section // (named by the target game's pid); we open it from inside the game and read the // forwarded pad state the host publishes each frame. #pragma once #include #include #include #include "coop/protocol.hpp" #include "coop/shared_memory.hpp" namespace coop::hook { class IpcClient { public: // Tries to open the section a few times: the host may inject us slightly // before (or after) it creates the mapping. Returns true once connected. bool connect(int attempts, int delay_ms) { const std::wstring name = shared_memory_name(GetCurrentProcessId()); for (int i = 0; i < attempts; ++i) { if (shm_.open(name, sizeof(SharedBlock))) { auto* block = shm_.as(); if (block->magic == kProtocolMagic && block->version == kProtocolVersion) { block_ = block; return true; } shm_.reset(); // present but not a contract we understand; retry } Sleep(static_cast(delay_ms)); } return false; } [[nodiscard]] bool connected() const { return block_ != nullptr; } // Copies a torn-free snapshot of all slots. Returns false only if the host // was mid-write for the whole spin window (caller should reuse its cache). bool snapshot(CoopPadState (&out)[kMaxPads], std::uint32_t& count) const { if (block_ == nullptr) { return false; } return read_pads(*block_, out, count); } // --- Status back-channel (hook -> host diagnostics) -------------------- // Record that the game queried a controller slot via XInputGetState/Ex. void note_state_query(std::uint32_t user_index) { if (block_ != nullptr && user_index < kMaxPads) { block_->status.get_state_calls[user_index].fetch_add(1, std::memory_order_relaxed); } } void note_caps_query(std::uint32_t user_index) { if (block_ != nullptr && user_index < kMaxPads) { block_->status.get_caps_calls[user_index].fetch_add(1, std::memory_order_relaxed); } } void note_focus_query(FocusApi which) { if (block_ != nullptr && which < FocusApi_Count) { block_->status.focus_query_calls[which].fetch_add(1, std::memory_order_relaxed); } } void mark_attached() { if (block_ != nullptr) { block_->status.game_pid = GetCurrentProcessId(); block_->status.attached = 1; } } void mark_focus_spoof(bool active, std::uint64_t game_hwnd) { if (block_ != nullptr) { block_->status.focus_spoof = active ? 1u : 0u; block_->status.game_hwnd = game_hwnd; } } void set_input_diagnostics(bool raw_registered, bool raw_gamepad, bool raw_gamepad_sink, bool dinput) { if (block_ != nullptr) { block_->status.raw_input_registered = raw_registered ? 1u : 0u; block_->status.raw_input_gamepad = raw_gamepad ? 1u : 0u; block_->status.raw_input_gamepad_sink = raw_gamepad_sink ? 1u : 0u; block_->status.dinput_loaded = dinput ? 1u : 0u; } } void heartbeat() { if (block_ != nullptr) { block_->status.heartbeat.fetch_add(1, std::memory_order_relaxed); } } // --- Audio render-hook diagnostics ------------------------------------- // Total distinct render streams the audio hook has observed. void set_audio_streams_seen(std::uint32_t count) { if (block_ != nullptr) { block_->status.audio_streams_seen = count; } } // Publish a tracked stream's format/role into its debug slot. void publish_audio_stream(std::uint32_t slot, const AudioStreamInfo& info) { if (block_ != nullptr && slot < kMaxAudioStreams) { block_->status.audio_streams[slot] = info; } } // Update a tracked stream's cumulative frame count (host derives live/idle). void note_audio_frames(std::uint32_t slot, std::uint64_t frames) { if (block_ != nullptr && slot < kMaxAudioStreams) { block_->status.audio_streams[slot].frames_rendered = frames; } } // --- Hook registry ----------------------------------------------------- // Publish the installed-hooks table (name / subsystem / installed / calls). void publish_hook_entries(const HookEntry* entries, std::uint32_t count) { if (block_ == nullptr) { return; } if (count > kMaxHookEntries) { count = kMaxHookEntries; } for (std::uint32_t i = 0; i < count; ++i) { block_->status.hook_entries[i] = entries[i]; } block_->status.hook_entry_count = count; } private: SharedMemory shm_; SharedBlock* block_ = nullptr; }; } // namespace coop::hook