Files
CoopAllTheThings/host/src/ipc/ipc_server.hpp
BlackMark 0935dccfc4 Per-subsystem hook control: install/remove input, focus, audio at runtime
Add a host->hook control channel (protocol v5 -> v6: HookControl in SharedBlock,
per-subsystem "disabled" flags, 0 = install so the zero-filled default is
unchanged). The worker now reconciles each subsystem every tick: install what's
requested-and-missing, remove what's no longer wanted -- so the audio hooks
re-attach the ring and republish format on a reinstall, and XInput/focus clear
their stale status flags on removal.

Injection panel: a checkbox per subsystem (input forwarding / focus spoof /
audio render-hook) toggles it at runtime, showing the requested vs actual
installed state from the registry, plus DLL heartbeat liveness. The hook-status
section now keys off whether a DLL was injected (host-side) rather than the
input-hook "attached" flag, so it stays visible with input unhooked.

Guards for dependent features: the synthetic-input control is disabled when
input forwarding is off, and the Audio panel explains that mirroring uses
loopback (echo) when the render-hook is off.

Verified against Phantom Brave via coop_audio_probe: starting with audio
requested off installs only input+focus (8 hooks, no capture); re-enabling at
runtime installs the audio hooks (13) and capture starts immediately. All four
tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:17:48 +02:00

75 lines
2.1 KiB
C++

// Host side of the IPC channel: creates the shared-memory section (named by the
// target game's pid) and publishes the forwarded controller state each frame.
#pragma once
#include <array>
#include <cstdint>
#include "coop/protocol.hpp"
#include "coop/shared_memory.hpp"
#include "input/input_source.hpp"
namespace coop
{
// Plain (non-atomic) snapshot of the hook's back-channel for the overlay.
struct HookStatusView
{
bool attached = false; // XInput hooks installed in the game
bool focus_spoof = false; // focus spoofing active
std::uint32_t heartbeat = 0; // DLL liveness counter
std::uint64_t get_state[kMaxPads] = {};
std::uint64_t get_caps[kMaxPads] = {};
std::uint64_t focus_calls[FocusApi_Count] = {};
std::uint32_t game_pid = 0;
std::uint64_t game_hwnd = 0;
bool raw_input_registered = false;
bool raw_input_gamepad = false;
bool raw_input_gamepad_sink = false;
bool dinput_loaded = false;
// Audio render-hook diagnostics (for the Audio panel's stream-count view).
std::uint32_t audio_streams_seen = 0;
AudioStreamInfo audio_streams[kMaxAudioStreams] = {};
// Installed-hooks registry (for the Injection panel's hook list).
std::uint32_t hook_entry_count = 0;
HookEntry hook_entries[kMaxHookEntries] = {};
};
class IpcServer
{
public:
// Creates and initializes the section for `target_pid`. The injected hook
// derives the same name from its own pid and opens it.
bool start(unsigned long target_pid);
// Pushes the current pad snapshot to the hook. No-op if not started.
void publish(const std::array<PadInfo, kMaxPads>& pads);
void stop();
// Reads the hook's diagnostics back-channel (zeroed if not started).
[[nodiscard]] HookStatusView hook_status() const;
// Request a hook subsystem be installed (true) or removed (false). The hook
// reconciles on its next tick. No-op if not started.
void set_subsystem_enabled(std::uint32_t subsystem, bool enabled);
[[nodiscard]] bool running() const
{
return block_ != nullptr;
}
[[nodiscard]] unsigned long target_pid() const
{
return target_pid_;
}
private:
SharedMemory shm_;
SharedBlock* block_ = nullptr;
unsigned long target_pid_ = 0;
};
} // namespace coop