// CoopAllTheThings host. // // Borderless window that Steam Remote Play Together captures, plus the overlay // that drives the tool: it forwards controller input into an injected game // (Injection panel), spoofs the game's focus, and mirrors the game's window into // this window via Windows Graphics Capture (Video mirror panel). #include #include #include #include "imgui.h" #include "audio_panel.hpp" #include "capture_panel.hpp" #include "controllers_panel.hpp" #include "d3d11_window.hpp" #include "imgui_layer.hpp" #include "injection_panel.hpp" #include "input/xinput_source.hpp" #include "log_panel.hpp" #include "ui/app_chrome.hpp" #ifdef COOP_WITH_STEAM #include #include "input/steam_input_source.hpp" #endif namespace { #ifdef COOP_WITH_STEAM // Absolute path to the bundled Steam Input action manifest (next to the exe). std::string steam_manifest_path() { char buffer[MAX_PATH] = {}; const DWORD len = GetModuleFileNameA(nullptr, buffer, MAX_PATH); std::string path(buffer, len); const std::size_t slash = path.find_last_of("\\/"); if (slash != std::string::npos) { path.resize(slash + 1); } return path + "steam_input_actions.vdf"; } #endif // When the overlay is hidden, briefly show a fading hint so the operator can find // the way back. The window is borderless and non-interactive so it never steals a // click or a frame from the mirror underneath. void draw_overlay_hidden_hint(double seconds_hidden) { const float fade = 1.0f - static_cast(seconds_hidden) / 4.0f; if (fade <= 0.0f) { return; // fully faded -> truly clean window for RPT capture } ImGui::SetNextWindowPos(ImVec2(12.0f, 12.0f)); ImGui::SetNextWindowBgAlpha(0.35f * fade); const ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; ImGui::Begin("##overlay_hint", nullptr, flags); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, fade)); ImGui::TextUnformatted("F1: show overlay"); ImGui::PopStyleColor(); ImGui::End(); } int run() { coop::D3D11Window window; if (!window.create(L"CoopAllTheThings")) { MessageBoxW(nullptr, L"Failed to create the D3D11 window.", L"CoopAllTheThings", MB_ICONERROR); return 1; } coop::ImGuiLayer imgui; if (!imgui.init(window.hwnd(), window.device(), window.context())) { MessageBoxW(nullptr, L"Failed to initialize ImGui.", L"CoopAllTheThings", MB_ICONERROR); return 1; } // Steam Input is the plan's primary guest-input path (it falls back to XInput // per slot, and to pure XInput if Steam isn't available). When the host is built // without the Steamworks SDK, use XInput directly. #ifdef COOP_WITH_STEAM std::unique_ptr input = [] { auto steam = std::make_unique(); steam->init(steam_manifest_path()); return steam; }(); #else std::unique_ptr input = std::make_unique(); #endif coop::ControllersPanel controllers; coop::InjectionPanel injection; coop::AudioPanel audio; coop::CapturePanel capture; coop::LogPanel log; if (!capture.init(window.device())) { MessageBoxW(nullptr, L"Failed to initialize the video mirror.", L"CoopAllTheThings", MB_ICONERROR); return 1; } capture.set_injection(&injection); // for the Present-hook (Hooked) video source // The overlay can be hidden (F1) so the window is a clean mirror for Remote // Play Together; the pipelines keep running underneath either way. bool show_overlay = true; double overlay_hidden_at = 0.0; coop::UiState ui; coop::FrameStats stats; while (window.pump_messages()) { input->poll(); injection.publish(input->pads()); const HWND game = injection.game_hwnd(); capture.set_target(game); audio.set_target(game); imgui.begin_frame(); stats.tick(ImGui::GetIO().DeltaTime * 1000.0f); log.pull(injection); // drain hook log lines even while the Log window is hidden if (ImGui::IsKeyPressed(ImGuiKey_F1, false)) { show_overlay = !show_overlay; if (!show_overlay) { overlay_hidden_at = ImGui::GetTime(); } } if (show_overlay) { coop::draw_main_menu_bar(ui, stats); if (ui.show_controllers) { controllers.draw(*input, injection.hook_status(), ui.debug_details); } if (ui.show_injection) { injection.draw(ui.debug_details); } if (ui.show_audio) { audio.draw_ui(injection.hook_status(), ui.debug_details); } if (ui.show_video) { capture.draw_ui(stats); } if (ui.show_log) { log.draw(); } } else { draw_overlay_hidden_hint(ImGui::GetTime() - overlay_hidden_at); } RECT client = {}; GetClientRect(window.hwnd(), &client); const auto dst_w = static_cast(client.right - client.left); const auto dst_h = static_cast(client.bottom - client.top); // Mirrored frame first (the window background), ImGui overlay on top. window.render_frame([&]() { capture.render(window.context(), dst_w, dst_h); imgui.end_frame(); }); } return 0; } } // namespace int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) { // WGC requires an initialized apartment; multi-threaded suits the // free-threaded frame pool. winrt::init_apartment(winrt::apartment_type::multi_threaded); const int result = run(); winrt::uninit_apartment(); return result; }