/** * @file src/confighttp_rtss.cpp * @brief RTSS-specific HTTP endpoints (Windows-only). */ #ifdef _WIN32 // standard includes #include #include #include #include #include #include #include #include // third-party includes #include #include // local includes #include "confighttp.h" #include "src/config.h" #include "src/logging.h" #include "src/platform/windows/frame_limiter.h" #include "src/platform/windows/ipc/misc_utils.h" #include "src/platform/windows/lossless_scaling_paths.h" #include "src/platform/windows/misc.h" namespace confighttp { using resp_https_t = std::shared_ptr::Response>; using req_https_t = std::shared_ptr::Request>; // Forward declarations for helpers defined in confighttp.cpp bool authenticate(resp_https_t response, req_https_t request); void print_req(const req_https_t &request); void send_response(resp_https_t response, const nlohmann::json &output_tree); using lossless_paths::default_steam_lossless_path; using lossless_paths::discover_lossless_candidates; using lossless_paths::resolve_lossless_candidate; namespace { constexpr auto k_lossless_default_hint = "C:\\\\Program Files (x86)\\\\Steam\\\\steamapps\\\\common\\\\Lossless Scaling\\\\LosslessScaling.exe"; bool is_regular_exe(const std::optional &path) { if (!path) { return false; } std::error_code ec; auto ext = path->extension().wstring(); std::transform(ext.begin(), ext.end(), ext.begin(), [](wchar_t ch) { return std::towlower(ch); }); return ext == L".exe" && std::filesystem::is_regular_file(*path, ec); } std::optional resolve_explicit_lossless_path(const std::optional &path) { if (!path) { return std::nullopt; } if (auto resolved = resolve_lossless_candidate(*path)) { return resolved; } if (is_regular_exe(path)) { return path->lexically_normal(); } return std::nullopt; } nlohmann::json make_lossless_status_unavailable_response() { return { {"configured_path", config::lossless_scaling.exe_path}, {"checked_path", config::lossless_scaling.exe_path}, {"configured_exists", false}, {"checked_exists", false}, {"configured_is_directory", false}, {"checked_is_directory", false}, {"default_path", k_lossless_default_hint}, {"default_exists", false}, {"default_is_directory", false}, {"suggested_path", k_lossless_default_hint}, {"candidates", nlohmann::json::array()}, {"status", "unavailable"} }; } } // namespace void getRtssStatus(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) { return; } print_req(request); nlohmann::json out; auto rtss = platf::rtss_get_status(); auto fl = platf::frame_limiter_get_status(); out["enabled"] = fl.enabled; out["configured_provider"] = platf::frame_limiter_provider_to_string(fl.configured_provider); out["active_provider"] = platf::frame_limiter_provider_to_string(fl.active_provider); out["nvidia_available"] = fl.nvidia_available; out["nvcp_ready"] = fl.nvcp_ready; out["rtss_available"] = fl.rtss_available; out["disable_vsync"] = fl.disable_vsync; out["disable_vsync_ullm"] = fl.disable_vsync; // legacy key for older clients out["nv_overrides_supported"] = fl.nv_overrides_supported; out["configured_path"] = rtss.configured_path; out["path_configured"] = rtss.path_configured; out["resolved_path"] = rtss.resolved_path; out["path_exists"] = rtss.path_exists; out["hooks_found"] = rtss.hooks_found; out["profile_found"] = rtss.profile_found; out["can_bootstrap_profile"] = rtss.can_bootstrap_profile; out["process_running"] = rtss.process_running; // A user-friendly message hinting required action std::string message; auto provider_to_string = [](platf::frame_limiter_provider provider) -> const char * { switch (provider) { case platf::frame_limiter_provider::nvidia_control_panel: return "nvidia-control-panel"; case platf::frame_limiter_provider::rtss: return "rtss"; case platf::frame_limiter_provider::auto_detect: return "auto"; case platf::frame_limiter_provider::none: default: return "none"; } }; auto describe_provider = [&](const std::string &id) -> std::string { if (id == "nvidia-control-panel") { return "NVIDIA Control Panel"; } if (id == "rtss") { return "RTSS"; } if (id == "auto") { return "Auto"; } return "None"; }; const auto add_segment = [](std::string &dest, const std::string &segment) { if (segment.empty()) { return; } if (!dest.empty()) { dest.push_back(' '); } dest += segment; }; const std::string configured_id = provider_to_string(fl.configured_provider); const bool prefer_rtss = fl.configured_provider == platf::frame_limiter_provider::rtss || fl.configured_provider == platf::frame_limiter_provider::auto_detect; const bool rtss_ready = rtss.path_exists && rtss.hooks_found; const bool rtss_bootstrap_pending = rtss_ready && !rtss.profile_found && rtss.can_bootstrap_profile; std::string provider_message; if (fl.enabled) { if (fl.active_provider == platf::frame_limiter_provider::nvidia_control_panel) { add_segment(provider_message, "NVIDIA Control Panel frame limiter active (not recommended; it cannot guarantee perfect frame pacing)."); } else if (fl.active_provider == platf::frame_limiter_provider::rtss) { add_segment(provider_message, "RTSS frame limiter active for this stream."); } else { if (fl.configured_provider == platf::frame_limiter_provider::nvidia_control_panel) { if (!fl.nvidia_available) { add_segment(provider_message, "No NVIDIA GPU detected. Switch to RTSS or install NVIDIA drivers."); } else if (!fl.nvcp_ready) { add_segment(provider_message, "NVIDIA Control Panel integration unavailable (NvAPI not ready)."); } else { add_segment(provider_message, "NVIDIA Control Panel limiter selected (not recommended). Sunshine recommends RTSS for smoother pacing."); } } else if (prefer_rtss) { if (!rtss.path_exists) { add_segment(provider_message, "RTSS not found at the resolved path. Install RTSS for the smoothest streaming experience."); } else if (!rtss.hooks_found) { add_segment(provider_message, "RTSSHooks DLL not found. Reinstall RTSS to restore frame limiter support."); } else { add_segment(provider_message, std::string("Frame limiter configured for ") + describe_provider(configured_id) + "; awaiting next stream."); if (!rtss.process_running) { add_segment(provider_message, "Sunshine will launch RTSS automatically when streaming starts."); } if (rtss_bootstrap_pending) { add_segment(provider_message, "Sunshine will refresh RTSS configuration automatically on the next stream."); } } } else { add_segment(provider_message, "Frame limiter enabled but no provider applied."); } } } else { add_segment(provider_message, "Frame limiter disabled; enable in settings to activate."); } if (prefer_rtss) { add_segment(provider_message, "RTSS provides the smoothest pacing; NVIDIA's limiter is not recommended because it cannot guarantee perfect frame pacing."); } else if (fl.configured_provider == platf::frame_limiter_provider::nvidia_control_panel) { add_segment(provider_message, "Sunshine recommends installing RTSS for the smoothest streaming experience; NVIDIA's limiter is not recommended because it cannot guarantee perfect frame pacing."); } std::string override_message; if (fl.disable_vsync) { if (fl.nv_overrides_supported) { override_message = "NVIDIA overrides ready: Sunshine will force VSYNC off during streams."; } else if (fl.nvidia_available && !fl.nvcp_ready) { override_message = "NvAPI unavailable; Sunshine will fall back to forcing the highest available refresh rate during streams."; } else if (!fl.nvidia_available) { override_message = "No NVIDIA GPU detected; Sunshine will force the highest available refresh rate during streams as a best-effort VSYNC workaround."; } } if (!override_message.empty() && !provider_message.empty()) { message = provider_message + " " + override_message; } else if (!override_message.empty()) { message = override_message; } else { message = provider_message; } out["message"] = message; send_response(response, out); } void getFrameLimiterStatus(resp_https_t response, req_https_t request) { getRtssStatus(std::move(response), std::move(request)); } void getLosslessScalingStatus(resp_https_t response, req_https_t request) try { if (!authenticate(response, request)) { return; } print_req(request); auto query = request->parse_query_string(); std::string override_path; if (auto it = query.find("path"); it != query.end()) { override_path = it->second; } auto to_path = [](const std::string &utf8) -> std::optional { if (utf8.empty()) { return std::nullopt; } try { std::wstring wide = platf::dxgi::utf8_to_wide(utf8); if (wide.empty()) { return std::nullopt; } return std::filesystem::path(wide); } catch (...) { return std::nullopt; } }; auto path_to_utf8 = [](const std::filesystem::path &path) -> std::string { try { return platf::dxgi::wide_to_utf8(path.wstring()); } catch (...) { return std::string(); } }; const std::string configured_utf8 = config::lossless_scaling.exe_path; std::string default_hint = path_to_utf8(default_steam_lossless_path()); if (default_hint.empty()) { default_hint = "C:\\\\Program Files (x86)\\\\Steam\\\\steamapps\\\\common\\\\Lossless Scaling\\\\LosslessScaling.exe"; } const std::string check_utf8 = !override_path.empty() ? override_path : configured_utf8; const auto configured_path = to_path(configured_utf8); const auto checked_path = to_path(check_utf8); const auto default_path = to_path(default_hint); const bool has_explicit_path = !check_utf8.empty(); auto resolved_configured = resolve_explicit_lossless_path(configured_path); auto resolved_checked = resolve_explicit_lossless_path(checked_path); auto resolved_default = default_path ? resolve_lossless_candidate(*default_path) : std::optional(); std::error_code configured_ec; const bool configured_is_directory = configured_path && std::filesystem::is_directory(*configured_path, configured_ec); std::error_code checked_ec; const bool checked_is_directory = checked_path && std::filesystem::is_directory(*checked_path, checked_ec); std::error_code default_ec; const bool default_is_directory = default_path && std::filesystem::is_directory(*default_path, default_ec); std::error_code default_exists_ec; const bool default_exists = default_path && std::filesystem::exists(*default_path, default_exists_ec); nlohmann::json out; out["configured_path"] = configured_utf8; out["checked_path"] = check_utf8; out["configured_exists"] = resolved_configured.has_value(); out["checked_exists"] = resolved_checked.has_value(); out["configured_is_directory"] = configured_is_directory; out["checked_is_directory"] = checked_is_directory; out["default_path"] = default_hint; out["default_exists"] = resolved_default.has_value() || default_exists; out["default_is_directory"] = default_is_directory; out["using_configured_path"] = has_explicit_path; std::string suggested_utf8 = configured_utf8; if (!suggested_utf8.empty()) { if (resolved_configured) { suggested_utf8 = path_to_utf8(*resolved_configured); } } else if (resolved_default) { suggested_utf8 = path_to_utf8(*resolved_default); } else { suggested_utf8 = default_hint; } out["suggested_path"] = suggested_utf8; if (resolved_checked) { out["resolved_path"] = path_to_utf8(*resolved_checked); } auto candidates = discover_lossless_candidates(configured_path, checked_path, has_explicit_path ? std::nullopt : default_path); if (!candidates.empty()) { nlohmann::json arr = nlohmann::json::array(); for (const auto &candidate : candidates) { arr.emplace_back(path_to_utf8(candidate)); } out["candidates"] = arr; } if (resolved_checked || (!has_explicit_path && (resolved_default || !candidates.empty()))) { out["status"] = "detected"; } else if (check_utf8.empty()) { out["status"] = "not-configured"; } else if (checked_is_directory) { out["status"] = "path-is-directory"; } else { out["status"] = "path-not-found"; } send_response(response, out); } catch (const std::exception &e) { BOOST_LOG(warning) << "Lossless Scaling status check failed: " << e.what(); send_response(response, make_lossless_status_unavailable_response()); } catch (...) { BOOST_LOG(warning) << "Lossless Scaling status check failed with an unknown exception"; send_response(response, make_lossless_status_unavailable_response()); } } // namespace confighttp #endif // _WIN32 #ifdef __linux__ #include #include #include #include "confighttp.h" #include "src/config.h" #include "src/platform/linux/mangohud_policy.h" namespace confighttp { using resp_https_t = std::shared_ptr::Response>; using req_https_t = std::shared_ptr::Request>; bool authenticate(resp_https_t response, req_https_t request); void print_req(const req_https_t &request); void send_response(resp_https_t response, const nlohmann::json &output_tree); void getFrameLimiterStatus(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) { return; } print_req(request); const auto path = boost::process::v1::search_path("mangohud"); const bool available = !path.empty(); const bool selected = platf::mangohud::provider_selected(config::frame_limiter.provider); const bool proton_selected = platf::mangohud::proton_provider_selected(config::frame_limiter.provider); const bool proton_overlay_selected = platf::mangohud::proton_overlay_provider_selected(config::frame_limiter.provider); const bool active = config::frame_limiter.enable && ((selected && available) || proton_selected); send_response(response, { {"enabled", config::frame_limiter.enable}, {"configured_provider", config::frame_limiter.provider.empty() ? "auto" : config::frame_limiter.provider}, {"active_provider", active ? (proton_overlay_selected && available ? "mangohud-proton" : proton_selected ? "proton" : "mangohud") : "none"}, {"fps_limit", static_cast(config::frame_limiter.fps_limit_millihz) / 1000.0}, {"fps_limit_millihz", config::frame_limiter.fps_limit_millihz}, {"overlay_preset", config::frame_limiter.mangohud_preset}, {"always_show_graph", config::frame_limiter.mangohud_always_show_graph}, {"limiter_method", config::frame_limiter.mangohud_limiter_method}, {"mangohud_available", available}, {"resolved_path", path.string()}, {"message", proton_overlay_selected ? (available ? "The MangoHUD overlay and Proton DXVK/VKD3D limiter are available for managed Steam games and supported external Proton launches during a stream." : "The Proton DXVK/VKD3D limiter is ready, but MangoHUD was not found in PATH; the overlay will be unavailable.") : proton_selected ? "The Proton DXVK/VKD3D frame limiter is selected for managed Steam games and supported external Proton D3D9-12 launches during a stream." : available ? "MangoHUD is installed and ready for launched games." : "MangoHUD was not found in PATH; install it to enable Linux frame limiting."} }); } } // namespace confighttp #endif // __linux__