Ctrl K
ComunidadGuías…

server/src/confighttp_rtss.cpp

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * @file src/confighttp_rtss.cpp
 * @brief RTSS-specific HTTP endpoints (Windows-only).
 */

#ifdef _WIN32

// standard includes
  #include <algorithm>
  #include <array>
  #include <cwctype>
  #include <filesystem>
  #include <optional>
  #include <string>
  #include <unordered_set>
  #include <vector>

  // third-party includes
  #include <nlohmann/json.hpp>
  #include <Simple-Web-Server/server_https.hpp>

  // 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<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Response>;
  using req_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::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<std::filesystem::path> &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<std::filesystem::path> resolve_explicit_lossless_path(const std::optional<std::filesystem::path> &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<std::filesystem::path> {
      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::filesystem::path>();

    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 <boost/process/v1/search_path.hpp>
  #include <nlohmann/json.hpp>
  #include <Simple-Web-Server/server_https.hpp>

  #include "confighttp.h"
  #include "src/config.h"
  #include "src/platform/linux/mangohud_policy.h"

namespace confighttp {

  using resp_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::Response>;
  using req_https_t = std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTPS>::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<double>(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__