Phase 1a: per-slot poll viz + input-path diagnostics
Surfaced by a game (Life is Strange: Before the Storm) that ignores controller input when it lacks true OS focus even though it still polls XInput. To find the focus-gated detection path, instrument the hook. Protocol v3 status back-channel now reports: - per-slot XInputGetState and XInputGetCapabilities counters (replacing the single aggregate), so the overlay shows exactly which slots the game polls and how fast; - focus-API call counts (GetForegroundWindow/GetActiveWindow/GetFocus) to see whether the game consults the APIs we spoof; - input-path diagnostics: whether the process registered Raw Input for a gamepad usage and whether it set RIDEV_INPUTSINK (background delivery), and whether a DirectInput dll is loaded. Host overlay gains a per-slot poll table and an "Input path" section. The DLL refreshes input diagnostics each worker tick via GetRegisteredRawInputDevices. hook_selftest updated for per-slot counters; passes. This is diagnostic-only: once a real run shows which path LiS uses, the targeted focus fix (e.g. forcing RIDEV_INPUTSINK or DI background coop) follows. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ namespace coop
|
||||
|
||||
// Bump whenever the layout of SharedBlock or CoopPadState changes. The hook
|
||||
// refuses to attach to a host with a mismatched version.
|
||||
inline constexpr std::uint32_t kProtocolVersion = 2;
|
||||
inline constexpr std::uint32_t kProtocolVersion = 3;
|
||||
|
||||
// 'COOP' little-endian, used to sanity-check the mapping before trusting it.
|
||||
inline constexpr std::uint32_t kProtocolMagic = 0x504F4F43u;
|
||||
@@ -42,19 +42,37 @@ struct CoopPadState
|
||||
|
||||
static_assert(sizeof(CoopPadState) == 20, "CoopPadState layout must stay stable across both modules");
|
||||
|
||||
// Indices into HookStatus::focus_query_calls.
|
||||
enum FocusApi : std::uint32_t
|
||||
{
|
||||
FocusApi_Foreground = 0, // GetForegroundWindow
|
||||
FocusApi_Active = 1, // GetActiveWindow
|
||||
FocusApi_Focus = 2, // GetFocus
|
||||
FocusApi_Count = 3,
|
||||
};
|
||||
|
||||
// Hook -> host back-channel. The injected DLL is the sole writer; the host reads
|
||||
// it for the diagnostics overlay (is the hook attached? is the game actually
|
||||
// polling it? did focus spoofing take?). Diagnostics only, so the few non-atomic
|
||||
// it for the diagnostics overlay: is the hook attached, which slots is the game
|
||||
// polling, does it use the focus APIs, and does it read input through a
|
||||
// focus-gated path (Raw Input / DirectInput)? Diagnostics only, so the non-atomic
|
||||
// fields tolerate benign cross-process races.
|
||||
struct HookStatus
|
||||
{
|
||||
std::atomic<std::uint32_t> heartbeat; // DLL bumps this ~4x/sec while alive
|
||||
std::atomic<std::uint64_t> xinput_queries; // cumulative XInputGetState/Ex calls served
|
||||
std::uint32_t attached; // 1 once XInput hooks are installed
|
||||
std::uint32_t focus_spoof; // 1 once focus spoofing is active
|
||||
std::uint32_t game_pid; // the DLL's own pid (sanity check)
|
||||
std::uint32_t last_user_index; // last slot the game queried
|
||||
std::uint64_t game_hwnd; // window the DLL subclassed (0 if none yet)
|
||||
std::atomic<std::uint32_t> heartbeat; // DLL bumps ~4x/sec while alive
|
||||
std::atomic<std::uint64_t> get_state_calls[kMaxPads]; // XInputGetState/Ex per slot
|
||||
std::atomic<std::uint64_t> get_caps_calls[kMaxPads]; // XInputGetCapabilities per slot
|
||||
std::atomic<std::uint64_t> focus_query_calls[FocusApi_Count]; // focus API calls, see FocusApi
|
||||
|
||||
std::uint32_t attached; // 1 once XInput hooks are installed
|
||||
std::uint32_t focus_spoof; // 1 once focus spoofing is active
|
||||
std::uint32_t game_pid; // the DLL's own pid (sanity check)
|
||||
std::uint64_t game_hwnd; // window the DLL subclassed (0 if none yet)
|
||||
|
||||
// Input-path diagnostics: which focus-gated mechanism (if any) the game uses.
|
||||
std::uint32_t raw_input_registered; // process has any Raw Input registration
|
||||
std::uint32_t raw_input_gamepad; // ... for a joystick/gamepad usage page
|
||||
std::uint32_t raw_input_gamepad_sink; // ... and that usage has RIDEV_INPUTSINK (bg delivery)
|
||||
std::uint32_t dinput_loaded; // dinput8.dll is present in the process
|
||||
};
|
||||
|
||||
// Top-level shared block. The host is the sole writer of pad state; the hook is
|
||||
|
||||
Reference in New Issue
Block a user