diff --git a/host/CMakeLists.txt b/host/CMakeLists.txt index 930d477..db409e9 100644 --- a/host/CMakeLists.txt +++ b/host/CMakeLists.txt @@ -2,7 +2,7 @@ add_executable(coop_host WIN32 src/main.cpp src/d3d11_window.cpp src/imgui_layer.cpp - src/debug_overlay.cpp + src/controllers_panel.cpp src/injection_panel.cpp src/capture_panel.cpp src/audio_panel.cpp diff --git a/host/src/controllers_panel.cpp b/host/src/controllers_panel.cpp new file mode 100644 index 0000000..03b37a7 --- /dev/null +++ b/host/src/controllers_panel.cpp @@ -0,0 +1,157 @@ +#include "controllers_panel.hpp" + +#include + +namespace coop +{ + +namespace +{ + +const ImVec4 kGreen(0.4f, 1.0f, 0.4f, 1.0f); +const ImVec4 kGrey(0.7f, 0.7f, 0.7f, 1.0f); + +struct ButtonBit +{ + std::uint16_t mask; + const char* label; +}; + +// XINPUT_GAMEPAD_* bit values (kept local so this file needn't include Xinput.h). +constexpr ButtonBit kButtons[] = { + {0x0001, "Up"}, {0x0002, "Down"}, {0x0004, "Left"}, {0x0008, "Right"}, {0x0010, "Start"}, + {0x0020, "Back"}, {0x0040, "LS"}, {0x0080, "RS"}, {0x0100, "LB"}, {0x0200, "RB"}, + {0x1000, "A"}, {0x2000, "B"}, {0x4000, "X"}, {0x8000, "Y"}, +}; + +void draw_pad(int index, const PadInfo& pad, bool debug_details) +{ + ImGui::PushID(index); + if (!pad.connected) + { + ImGui::TextDisabled("Slot %d: disconnected", index); + ImGui::PopID(); + return; + } + + ImGui::TextColored(kGreen, "Slot %d [%s]", index, pad.source.c_str()); + + bool first = true; + ImGui::TextUnformatted("Buttons: "); + for (const ButtonBit& b : kButtons) + { + if ((pad.state.buttons & b.mask) != 0) + { + ImGui::SameLine(); + ImGui::TextColored(kGreen, "%s%s", first ? "" : ", ", b.label); + first = false; + } + } + if (first) + { + ImGui::SameLine(); + ImGui::TextDisabled("(none)"); + } + + if (debug_details) + { + ImGui::Text("LT %3u RT %3u", pad.state.left_trigger, pad.state.right_trigger); + ImGui::Text("L (%6d, %6d) R (%6d, %6d)", pad.state.thumb_lx, pad.state.thumb_ly, + pad.state.thumb_rx, pad.state.thumb_ry); + } + ImGui::Separator(); + ImGui::PopID(); +} + +} // namespace + +void ControllersPanel::draw(const InputSource& input, const HookStatusView& status, bool debug_details) +{ + ImGui::SetNextWindowPos(ImVec2(24, 40), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(420, 0), ImGuiCond_FirstUseEver); + ImGui::Begin("Controllers"); + + ImGui::Text("Input backend: %s", input.name()); + ImGui::TextDisabled("This window is what Remote Play Together captures."); + ImGui::TextDisabled("F1: hide overlay (clean mirror) Esc: quit"); + + // --- Guest pads the host receives from RPT ----------------------------- + ImGui::SeparatorText("Incoming (host receives)"); + const auto& pads = input.pads(); + for (int i = 0; i < static_cast(pads.size()); ++i) + { + draw_pad(i, pads[i], debug_details); + } + + // --- What the injected game reads back via the XInput hook -------------- + ImGui::SeparatorText("Game polling (hook reports)"); + if (!status.attached) + { + ImGui::TextDisabled("Not injected (no XInput hook)."); + ImGui::End(); + return; + } + + // Convert the cumulative per-slot counters into rates every half second. + const double now = ImGui::GetTime(); + if (now - last_sample_time_ >= 0.5) + { + const double dt = now - last_sample_time_; + for (int i = 0; i < static_cast(kMaxPads); ++i) + { + const unsigned long long delta = + status.get_state[i] >= last_state_count_[i] ? status.get_state[i] - last_state_count_[i] : 0; + state_rate_[i] = dt > 0.0 ? static_cast(delta) / dt : 0.0; + last_state_count_[i] = status.get_state[i]; + } + last_sample_time_ = now; + } + + double total_rate = 0.0; + for (int i = 0; i < static_cast(kMaxPads); ++i) + { + total_rate += state_rate_[i]; + } + if (total_rate > 0.0) + { + ImGui::TextColored(kGreen, "Game reading controller: %.0f polls/s", total_rate); + } + else + { + ImGui::TextColored(kGrey, "Game reading controller: idle"); + } + + if (debug_details && + ImGui::BeginTable("slots", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingStretchProp)) + { + ImGui::TableSetupColumn("Slot"); + ImGui::TableSetupColumn("GetState/s"); + ImGui::TableSetupColumn("GetState total"); + ImGui::TableSetupColumn("GetCaps total"); + ImGui::TableHeadersRow(); + for (int i = 0; i < static_cast(kMaxPads); ++i) + { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%d", i); + ImGui::TableNextColumn(); + if (state_rate_[i] > 0.0) + { + ImGui::TextColored(kGreen, "%.0f", state_rate_[i]); + } + else + { + ImGui::TextDisabled("0"); + } + ImGui::TableNextColumn(); + ImGui::Text("%llu", static_cast(status.get_state[i])); + ImGui::TableNextColumn(); + ImGui::Text("%llu", static_cast(status.get_caps[i])); + } + ImGui::EndTable(); + } + + ImGui::End(); +} + +} // namespace coop diff --git a/host/src/controllers_panel.hpp b/host/src/controllers_panel.hpp new file mode 100644 index 0000000..a1edad9 --- /dev/null +++ b/host/src/controllers_panel.hpp @@ -0,0 +1,31 @@ +// Controllers panel: the controller view of the pipeline. Shows what the host +// receives from Remote Play Together (the guest pads, via the input backend) and +// what the injected game actually reads back (per-slot XInput poll counts from the +// hook). This is how we verify RPT routes a guest's gamepad in and the game polls +// it out. The per-axis breakdown and per-slot poll table are debug-only. +#pragma once + +#include + +#include "coop/protocol.hpp" +#include "input/input_source.hpp" +#include "ipc/ipc_server.hpp" + +namespace coop +{ + +class ControllersPanel +{ +public: + // `status` is the hook's back-channel (per-slot poll counters); `debug_details` + // reveals the raw axis values and the per-slot poll-rate table. + void draw(const InputSource& input, const HookStatusView& status, bool debug_details); + +private: + // Sampled to turn the hook's cumulative per-slot counters into poll rates. + unsigned long long last_state_count_[kMaxPads] = {}; + double state_rate_[kMaxPads] = {}; + double last_sample_time_ = 0.0; +}; + +} // namespace coop diff --git a/host/src/debug_overlay.cpp b/host/src/debug_overlay.cpp deleted file mode 100644 index c094067..0000000 --- a/host/src/debug_overlay.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include "debug_overlay.hpp" - -#include - -namespace coop -{ - -namespace -{ - -struct ButtonBit -{ - std::uint16_t mask; - const char* label; -}; - -// XINPUT_GAMEPAD_* bit values (kept local so this file needn't include Xinput.h). -constexpr ButtonBit kButtons[] = { - {0x0001, "Up"}, {0x0002, "Down"}, {0x0004, "Left"}, {0x0008, "Right"}, {0x0010, "Start"}, - {0x0020, "Back"}, {0x0040, "LS"}, {0x0080, "RS"}, {0x0100, "LB"}, {0x0200, "RB"}, - {0x1000, "A"}, {0x2000, "B"}, {0x4000, "X"}, {0x8000, "Y"}, -}; - -void draw_pad(int index, const PadInfo& pad, bool debug_details) -{ - ImGui::PushID(index); - if (!pad.connected) - { - ImGui::TextDisabled("Slot %d: disconnected", index); - ImGui::PopID(); - return; - } - - ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "Slot %d [%s]", index, pad.source.c_str()); - - // Live button state is useful general feedback (is the guest pressing - // anything?); the raw axis/trigger numbers are debug detail. - bool first = true; - ImGui::TextUnformatted("Buttons: "); - for (const ButtonBit& b : kButtons) - { - if ((pad.state.buttons & b.mask) != 0) - { - ImGui::SameLine(); - ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "%s%s", first ? "" : ", ", b.label); - first = false; - } - } - if (first) - { - ImGui::SameLine(); - ImGui::TextDisabled("(none)"); - } - - if (debug_details) - { - ImGui::Text("LT %3u RT %3u", pad.state.left_trigger, pad.state.right_trigger); - ImGui::Text("L (%6d, %6d) R (%6d, %6d)", pad.state.thumb_lx, pad.state.thumb_ly, - pad.state.thumb_rx, pad.state.thumb_ry); - } - ImGui::Separator(); - ImGui::PopID(); -} - -} // namespace - -void draw_controllers_panel(const InputSource& input, bool debug_details) -{ - ImGui::SetNextWindowPos(ImVec2(24, 40), ImGuiCond_FirstUseEver); - ImGui::SetNextWindowSize(ImVec2(420, 0), ImGuiCond_FirstUseEver); - ImGui::Begin("Controllers"); - - ImGui::Text("Input backend: %s", input.name()); - ImGui::TextDisabled("This window is what Remote Play Together captures."); - ImGui::TextDisabled("F1: hide overlay (clean mirror) Esc: quit"); - ImGui::Separator(); - - const auto& pads = input.pads(); - for (int i = 0; i < static_cast(pads.size()); ++i) - { - draw_pad(i, pads[i], debug_details); - } - - ImGui::End(); -} - -} // namespace coop diff --git a/host/src/debug_overlay.hpp b/host/src/debug_overlay.hpp deleted file mode 100644 index c40e46b..0000000 --- a/host/src/debug_overlay.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "input/input_source.hpp" - -namespace coop -{ - -// Controllers panel: shows which controllers are visible to the host -- how we -// verify Remote Play Together is routing a guest's gamepad into our window. With -// `debug_details` on it also shows the full per-axis / per-button breakdown. -void draw_controllers_panel(const InputSource& input, bool debug_details); - -} // namespace coop diff --git a/host/src/injection_panel.cpp b/host/src/injection_panel.cpp index e627bac..efc368f 100644 --- a/host/src/injection_panel.cpp +++ b/host/src/injection_panel.cpp @@ -148,21 +148,6 @@ void InjectionPanel::draw_hook_status(bool debug_details) const HookStatusView status = server_.hook_status(); - // Convert the cumulative per-slot counters into rates every half second. - const double now = ImGui::GetTime(); - if (now - last_sample_time_ >= 0.5) - { - const double dt = now - last_sample_time_; - for (int i = 0; i < static_cast(kMaxPads); ++i) - { - const unsigned long long delta = - status.get_state[i] >= last_state_count_[i] ? status.get_state[i] - last_state_count_[i] : 0; - state_rate_[i] = dt > 0.0 ? static_cast(delta) / dt : 0.0; - last_state_count_[i] = status.get_state[i]; - } - last_sample_time_ = now; - } - ImGui::SeparatorText("Hook status"); if (!status.attached) { @@ -173,57 +158,13 @@ void InjectionPanel::draw_hook_status(bool debug_details) ImGui::TextColored(kGreen, "Attached (game pid %u)", status.game_pid); ImGui::TextColored(status.focus_spoof ? kGreen : kGrey, "Focus spoof: %s", status.focus_spoof ? "active" : "inactive"); - - // General summary: is the game actually polling our pad, and how fast. - double total_rate = 0.0; - for (int i = 0; i < static_cast(kMaxPads); ++i) - { - total_rate += state_rate_[i]; - } - if (total_rate > 0.0) - { - ImGui::TextColored(kGreen, "Game reading controller: %.0f polls/s", total_rate); - } - else - { - ImGui::TextColored(kGrey, "Game reading controller: idle"); - } + ImGui::TextDisabled("Controller poll rates are in the Controllers panel."); if (!debug_details) { return; // everything below is diagnostic detail } - // Per-slot XInput polling: shows exactly which slots the game reads and how fast. - if (ImGui::BeginTable("slots", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingStretchProp)) - { - ImGui::TableSetupColumn("Slot"); - ImGui::TableSetupColumn("GetState/s"); - ImGui::TableSetupColumn("GetState total"); - ImGui::TableSetupColumn("GetCaps total"); - ImGui::TableHeadersRow(); - for (int i = 0; i < static_cast(kMaxPads); ++i) - { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("%d", i); - ImGui::TableNextColumn(); - if (state_rate_[i] > 0.0) - { - ImGui::TextColored(kGreen, "%.0f", state_rate_[i]); - } - else - { - ImGui::TextDisabled("0"); - } - ImGui::TableNextColumn(); - ImGui::Text("%llu", static_cast(status.get_state[i])); - ImGui::TableNextColumn(); - ImGui::Text("%llu", static_cast(status.get_caps[i])); - } - ImGui::EndTable(); - } - // Focus-API usage: tells us whether the game even consults these (which we // spoof) when deciding it lost focus. ImGui::Text("Focus API calls FG:%llu Active:%llu Focus:%llu", diff --git a/host/src/injection_panel.hpp b/host/src/injection_panel.hpp index a62791b..7df3028 100644 --- a/host/src/injection_panel.hpp +++ b/host/src/injection_panel.hpp @@ -57,11 +57,6 @@ private: ImVec4 status_color_; bool test_input_ = false; - - // Sampled to turn the hook's cumulative per-slot counters into poll rates. - unsigned long long last_state_count_[kMaxPads] = {}; - double state_rate_[kMaxPads] = {}; - double last_sample_time_ = 0.0; }; } // namespace coop diff --git a/host/src/main.cpp b/host/src/main.cpp index 9148173..7722afd 100644 --- a/host/src/main.cpp +++ b/host/src/main.cpp @@ -15,8 +15,8 @@ #include "audio_panel.hpp" #include "capture_panel.hpp" +#include "controllers_panel.hpp" #include "d3d11_window.hpp" -#include "debug_overlay.hpp" #include "imgui_layer.hpp" #include "injection_panel.hpp" #include "input/xinput_source.hpp" @@ -64,6 +64,7 @@ int run() } auto input = std::make_unique(); + coop::ControllersPanel controllers; coop::InjectionPanel injection; coop::AudioPanel audio; coop::CapturePanel capture; @@ -105,7 +106,7 @@ int run() coop::draw_main_menu_bar(ui, stats); if (ui.show_controllers) { - coop::draw_controllers_panel(*input, ui.debug_details); + controllers.draw(*input, injection.hook_status(), ui.debug_details); } if (ui.show_injection) {