From b6c1306d43e6ef4a2981c87748d4901fe5915dcb Mon Sep 17 00:00:00 2001 From: pigeatgarlic Date: Thu, 26 Oct 2023 19:10:58 +0700 Subject: [PATCH] Clean --- cmake/compile_definitions/common.cmake | 5 - cmake/compile_definitions/windows.cmake | 1 - cmake/targets/common.cmake | 8 + src/config.cpp | 169 ---- src/config.h | 5 - src/httpcommon.cpp | 247 ------ src/httpcommon.h | 35 - src/main.cpp | 254 ++---- src/main.h | 10 - src/nvhttp.cpp | 1000 ----------------------- src/nvhttp.h | 49 -- src/stream.cpp | 17 +- src/version.h.in | 12 - 13 files changed, 71 insertions(+), 1741 deletions(-) delete mode 100644 src/httpcommon.cpp delete mode 100644 src/httpcommon.h delete mode 100644 src/nvhttp.cpp delete mode 100644 src/nvhttp.h delete mode 100644 src/version.h.in diff --git a/cmake/compile_definitions/common.cmake b/cmake/compile_definitions/common.cmake index 6bd694da..1dc14f74 100644 --- a/cmake/compile_definitions/common.cmake +++ b/cmake/compile_definitions/common.cmake @@ -27,7 +27,6 @@ include_directories(SYSTEM third-party/nv-codec-headers/include) file(GLOB NVENC_SOURCES CONFIGURE_DEPENDS "src/nvenc/*.cpp" "src/nvenc/*.h") list(APPEND PLATFORM_TARGET_FILES ${NVENC_SOURCES}) -configure_file(src/version.h.in version.h @ONLY) include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(SUNSHINE_TARGET_FILES @@ -47,10 +46,6 @@ set(SUNSHINE_TARGET_FILES src/main.h src/crypto.cpp src/crypto.h - src/nvhttp.cpp - src/nvhttp.h - src/httpcommon.cpp - src/httpcommon.h src/rtsp.cpp src/rtsp.h src/stream.cpp diff --git a/cmake/compile_definitions/windows.cmake b/cmake/compile_definitions/windows.cmake index ec3e076f..ae3e022d 100644 --- a/cmake/compile_definitions/windows.cmake +++ b/cmake/compile_definitions/windows.cmake @@ -37,7 +37,6 @@ configure_file(src/platform/windows/windows.rs.in windows.rc @ONLY) set(PLATFORM_TARGET_FILES "${CMAKE_CURRENT_BINARY_DIR}/windows.rc" - src/platform/windows/publish.cpp src/platform/windows/misc.h src/platform/windows/misc.cpp src/platform/windows/input.cpp diff --git a/cmake/targets/common.cmake b/cmake/targets/common.cmake index 72f89bf5..566db70d 100644 --- a/cmake/targets/common.cmake +++ b/cmake/targets/common.cmake @@ -2,6 +2,7 @@ # this file will also load platform specific macros add_executable(sunshine ${SUNSHINE_TARGET_FILES}) +add_library(sunshinelib SHARED ${SUNSHINE_TARGET_FILES}) # platform specific target definitions if(WIN32) @@ -22,6 +23,12 @@ if(NOT DEFINED CMAKE_CUDA_STANDARD) set(CMAKE_CUDA_STANDARD_REQUIRED ON) endif() +target_link_libraries(sunshinelib ${SUNSHINE_EXTERNAL_LIBRARIES} ${EXTRA_LIBS}) +target_compile_definitions(sunshinelib PUBLIC ${SUNSHINE_DEFINITIONS}) +set_target_properties(sunshinelib PROPERTIES CXX_STANDARD 17 + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR}) + target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES} ${EXTRA_LIBS}) target_compile_definitions(sunshine PUBLIC ${SUNSHINE_DEFINITIONS}) set_target_properties(sunshine PROPERTIES CXX_STANDARD 17 @@ -33,3 +40,4 @@ foreach(flag IN LISTS SUNSHINE_COMPILE_OPTIONS) endforeach() target_compile_options(sunshine PRIVATE $<$:${SUNSHINE_COMPILE_OPTIONS}>;$<$:${SUNSHINE_COMPILE_OPTIONS_CUDA};-std=c++17>) # cmake-lint: disable=C0301 +target_compile_options(sunshinelib PRIVATE $<$:${SUNSHINE_COMPILE_OPTIONS}>;$<$:${SUNSHINE_COMPILE_OPTIONS_CUDA};-std=c++17>) # cmake-lint: disable=C0301 diff --git a/src/config.cpp b/src/config.cpp index cb032aa1..4dce6af6 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -16,7 +16,6 @@ #include "config.h" #include "main.h" -#include "nvhttp.h" #include "rtsp.h" #include "utility.h" @@ -379,30 +378,6 @@ namespace config { 1 // channels }; - nvhttp_t nvhttp { - "lan", // origin web manager - - PRIVATE_KEY_FILE, - CERTIFICATE_FILE, - - boost::asio::ip::host_name(), // sunshine_name, - "sunshine_state.json"s, // file_state - {}, // external_ip - { - "352x240"s, - "480x360"s, - "858x480"s, - "1280x720"s, - "1920x1080"s, - "2560x1080"s, - "3440x1440"s, - "1920x1200"s, - "3840x2160"s, - "3840x1600"s, - }, // supported resolutions - - { 10, 30, 60, 90, 120 }, // supported fps - }; input_t input { { @@ -525,32 +500,6 @@ namespace config { std::make_pair(to_string(begin, end_name), to_string(begin_val, endl))); } - std::unordered_map - parse_config(const std::string_view &file_content) { - std::unordered_map vars; - - auto pos = std::begin(file_content); - auto end = std::end(file_content); - - while (pos < end) { - // auto newline = std::find_if(pos, end, [](auto ch) { return ch == '\n' || ch == '\r'; }); - TUPLE_2D(endl, var, parse_option(pos, end)); - - pos = endl; - if (pos != end) { - pos += (*pos == '\r') ? 2 : 1; - } - - if (!var) { - continue; - } - - vars.emplace(std::move(*var)); - } - - return vars; - } - void string_f(std::unordered_map &vars, const std::string &name, std::string &input) { auto it = vars.find(name); @@ -977,27 +926,17 @@ namespace config { string_f(vars, "adapter_name", video.adapter_name); string_f(vars, "output_name", video.output_name); - path_f(vars, "pkey", nvhttp.pkey); - path_f(vars, "cert", nvhttp.cert); - string_f(vars, "sunshine_name", nvhttp.sunshine_name); path_f(vars, "log_path", config::sunshine.log_file); - path_f(vars, "file_state", nvhttp.file_state); // Must be run after "file_state" - config::sunshine.credentials_file = config::nvhttp.file_state; path_f(vars, "credentials_file", config::sunshine.credentials_file); - string_f(vars, "external_ip", nvhttp.external_ip); - list_string_f(vars, "resolutions"s, nvhttp.resolutions); - list_int_f(vars, "fps"s, nvhttp.fps); list_prep_cmd_f(vars, "global_prep_cmd", config::sunshine.prep_cmds); string_f(vars, "audio_sink", audio.sink); string_f(vars, "virtual_sink", audio.virtual_sink); bool_f(vars, "install_steam_audio_drivers", audio.install_steam_drivers); - string_restricted_f(vars, "origin_web_ui_allowed", nvhttp.origin_web_ui_allowed, { "pc"sv, "lan"sv, "wan"sv }); - int to = -1; int_between_f(vars, "ping_timeout", to, { -1, std::numeric_limits::max() }); if (to != -1) { @@ -1049,7 +988,6 @@ namespace config { bool_f(vars, "always_send_scancodes", input.always_send_scancodes); int port = sunshine.port; - int_between_f(vars, "port"s, port, { 1024 + nvhttp::PORT_HTTPS, 65535 - rtsp_stream::RTSP_SETUP_PORT }); sunshine.port = (std::uint16_t) port; string_restricted_f(vars, "address_family", sunshine.address_family, { "ipv4"sv, "both"sv }); @@ -1108,111 +1046,4 @@ namespace config { } } } - - int - parse(int argc, char *argv[]) { - std::unordered_map cmd_vars; -#ifdef _WIN32 - bool shortcut_launch = false; - bool service_admin_launch = false; -#endif - - for (auto x = 1; x < argc; ++x) { - auto line = argv[x]; - - if (line == "--help"sv) { - print_help(*argv); - return 1; - } -#ifdef _WIN32 - else if (line == "--shortcut"sv) { - shortcut_launch = true; - } - else if (line == "--shortcut-admin"sv) { - service_admin_launch = true; - } -#endif - else if (*line == '-') { - if (*(line + 1) == '-') { - sunshine.cmd.name = line + 2; - sunshine.cmd.argc = argc - x - 1; - sunshine.cmd.argv = argv + x + 1; - - break; - } - if (apply_flags(line + 1)) { - print_help(*argv); - return -1; - } - } - else { - auto line_end = line + strlen(line); - - auto pos = std::find(line, line_end, '='); - if (pos == line_end) { - sunshine.config_file = line; - } - else { - TUPLE_EL(var, 1, parse_option(line, line_end)); - if (!var) { - print_help(*argv); - return -1; - } - - TUPLE_EL_REF(name, 0, *var); - - auto it = cmd_vars.find(name); - if (it != std::end(cmd_vars)) { - cmd_vars.erase(it); - } - - cmd_vars.emplace(std::move(*var)); - } - } - } - - bool config_loaded = false; - try { - // Create appdata folder if it does not exist - if (!boost::filesystem::exists(platf::appdata().string())) { - boost::filesystem::create_directories(platf::appdata().string()); - } - - // Create empty config file if it does not exist - if (!fs::exists(sunshine.config_file)) { - std::ofstream { sunshine.config_file }; - } - - // Read config file - auto vars = parse_config(read_file(sunshine.config_file.c_str())); - - for (auto &[name, value] : cmd_vars) { - vars.insert_or_assign(std::move(name), std::move(value)); - } - - // Apply the config. Note: This will try to create any paths - // referenced in the config, so we may receive exceptions if - // the path is incorrect or inaccessible. - apply_config(std::move(vars)); - config_loaded = true; - } - catch (const std::filesystem::filesystem_error &err) { - BOOST_LOG(fatal) << "Failed to apply config: "sv << err.what(); - } - catch (const boost::filesystem::filesystem_error &err) { - BOOST_LOG(fatal) << "Failed to apply config: "sv << err.what(); - } - - if (!config_loaded) { -#ifdef _WIN32 - BOOST_LOG(fatal) << "To relaunch Sunshine successfully, use the shortcut in the Start Menu. Do not run Sunshine.exe manually."sv; - std::this_thread::sleep_for(10s); -#endif - return -1; - } - - - - return 0; - } } // namespace config diff --git a/src/config.h b/src/config.h index 090e6b68..9f6f0676 100644 --- a/src/config.h +++ b/src/config.h @@ -171,9 +171,4 @@ namespace config { extern nvhttp_t nvhttp; extern input_t input; extern sunshine_t sunshine; - - int - parse(int argc, char *argv[]); - std::unordered_map - parse_config(const std::string_view &file_content); } // namespace config diff --git a/src/httpcommon.cpp b/src/httpcommon.cpp deleted file mode 100644 index b6ea0958..00000000 --- a/src/httpcommon.cpp +++ /dev/null @@ -1,247 +0,0 @@ -/** - * @file src/httpcommon.cpp - * @brief todo - */ -#define BOOST_BIND_GLOBAL_PLACEHOLDERS - -#include "process.h" - -#include - -#include -#include -#include - -#include - -#include -#include -#include -#include - -#include "config.h" -#include "crypto.h" -#include "httpcommon.h" -#include "main.h" -#include "network.h" -#include "nvhttp.h" -#include "platform/common.h" -#include "rtsp.h" -#include "utility.h" -#include "uuid.h" - -namespace http { - using namespace std::literals; - namespace fs = std::filesystem; - namespace pt = boost::property_tree; - - int - reload_user_creds(const std::string &file); - bool - user_creds_exist(const std::string &file); - - std::string unique_id; - net::net_e origin_web_ui_allowed; - - int - init() { - bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; - origin_web_ui_allowed = net::from_enum_string(config::nvhttp.origin_web_ui_allowed); - - if (clean_slate) { - unique_id = uuid_util::uuid_t::generate().string(); - auto dir = std::filesystem::temp_directory_path() / "Sunshine"sv; - config::nvhttp.cert = (dir / ("cert-"s + unique_id)).string(); - config::nvhttp.pkey = (dir / ("pkey-"s + unique_id)).string(); - } - - if (!fs::exists(config::nvhttp.pkey) || !fs::exists(config::nvhttp.cert)) { - if (create_creds(config::nvhttp.pkey, config::nvhttp.cert)) { - return -1; - } - } - if (user_creds_exist(config::sunshine.credentials_file)) { - if (reload_user_creds(config::sunshine.credentials_file)) return -1; - } - else { - BOOST_LOG(info) << "Open the Web UI to set your new username and password and getting started"; - } - return 0; - } - - int - save_user_creds(const std::string &file, const std::string &username, const std::string &password, bool run_our_mouth) { - pt::ptree outputTree; - - if (fs::exists(file)) { - try { - pt::read_json(file, outputTree); - } - catch (std::exception &e) { - BOOST_LOG(error) << "Couldn't read user credentials: "sv << e.what(); - return -1; - } - } - - auto salt = crypto::rand_alphabet(16); - outputTree.put("username", username); - outputTree.put("salt", salt); - outputTree.put("password", util::hex(crypto::hash(password + salt)).to_string()); - try { - pt::write_json(file, outputTree); - } - catch (std::exception &e) { - BOOST_LOG(error) << "error writing to the credentials file, perhaps try this again as an administrator? Details: "sv << e.what(); - return -1; - } - - BOOST_LOG(info) << "New credentials have been created"sv; - return 0; - } - - bool - user_creds_exist(const std::string &file) { - if (!fs::exists(file)) { - return false; - } - - pt::ptree inputTree; - try { - pt::read_json(file, inputTree); - return inputTree.find("username") != inputTree.not_found() && - inputTree.find("password") != inputTree.not_found() && - inputTree.find("salt") != inputTree.not_found(); - } - catch (std::exception &e) { - BOOST_LOG(error) << "validating user credentials: "sv << e.what(); - } - - return false; - } - - int - reload_user_creds(const std::string &file) { - pt::ptree inputTree; - try { - pt::read_json(file, inputTree); - config::sunshine.username = inputTree.get("username"); - config::sunshine.password = inputTree.get("password"); - config::sunshine.salt = inputTree.get("salt"); - } - catch (std::exception &e) { - BOOST_LOG(error) << "loading user credentials: "sv << e.what(); - return -1; - } - return 0; - } - - int - create_creds(const std::string &pkey, const std::string &cert) { - fs::path pkey_path = pkey; - fs::path cert_path = cert; - - auto creds = crypto::gen_creds("Sunshine Gamestream Host"sv, 2048); - - auto pkey_dir = pkey_path; - auto cert_dir = cert_path; - pkey_dir.remove_filename(); - cert_dir.remove_filename(); - - std::error_code err_code {}; - fs::create_directories(pkey_dir, err_code); - if (err_code) { - BOOST_LOG(error) << "Couldn't create directory ["sv << pkey_dir << "] :"sv << err_code.message(); - return -1; - } - - fs::create_directories(cert_dir, err_code); - if (err_code) { - BOOST_LOG(error) << "Couldn't create directory ["sv << cert_dir << "] :"sv << err_code.message(); - return -1; - } - - if (write_file(pkey.c_str(), creds.pkey)) { - BOOST_LOG(error) << "Couldn't open ["sv << config::nvhttp.pkey << ']'; - return -1; - } - - if (write_file(cert.c_str(), creds.x509)) { - BOOST_LOG(error) << "Couldn't open ["sv << config::nvhttp.cert << ']'; - return -1; - } - - fs::permissions(pkey_path, - fs::perms::owner_read | fs::perms::owner_write, - fs::perm_options::replace, err_code); - - if (err_code) { - BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.pkey << "] :"sv << err_code.message(); - return -1; - } - - fs::permissions(cert_path, - fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read | fs::perms::owner_write, - fs::perm_options::replace, err_code); - - if (err_code) { - BOOST_LOG(error) << "Couldn't change permissions of ["sv << config::nvhttp.cert << "] :"sv << err_code.message(); - return -1; - } - - return 0; - } - - bool - download_file(const std::string &url, const std::string &file) { - CURL *curl = curl_easy_init(); - if (!curl) { - BOOST_LOG(error) << "Couldn't create CURL instance"; - return false; - } - FILE *fp = fopen(file.c_str(), "wb"); - if (!fp) { - BOOST_LOG(error) << "Couldn't open ["sv << file << ']'; - curl_easy_cleanup(curl); - return false; - } - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); -#ifdef _WIN32 - curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); -#endif - CURLcode result = curl_easy_perform(curl); - if (result != CURLE_OK) { - BOOST_LOG(error) << "Couldn't download ["sv << url << ", code:" << result << ']'; - } - curl_easy_cleanup(curl); - fclose(fp); - return result == CURLE_OK; - } - - std::string - url_escape(const std::string &url) { - CURL *curl = curl_easy_init(); - char *string = curl_easy_escape(curl, url.c_str(), url.length()); - std::string result(string); - curl_free(string); - curl_easy_cleanup(curl); - return result; - } - - std::string - url_get_host(const std::string &url) { - CURLU *curlu = curl_url(); - curl_url_set(curlu, CURLUPART_URL, url.c_str(), url.length()); - char *host; - if (curl_url_get(curlu, CURLUPART_HOST, &host, 0) != CURLUE_OK) { - curl_url_cleanup(curlu); - return ""; - } - std::string result(host); - curl_free(host); - curl_url_cleanup(curlu); - return result; - } - -} // namespace http diff --git a/src/httpcommon.h b/src/httpcommon.h deleted file mode 100644 index 9dc8f9b2..00000000 --- a/src/httpcommon.h +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @file src/httpcommon.h - * @brief todo - */ -#pragma once - -#include "network.h" -#include "thread_safe.h" - -namespace http { - - int - init(); - int - create_creds(const std::string &pkey, const std::string &cert); - int - save_user_creds( - const std::string &file, - const std::string &username, - const std::string &password, - bool run_our_mouth = false); - - int - reload_user_creds(const std::string &file); - bool - download_file(const std::string &url, const std::string &file); - std::string - url_escape(const std::string &url); - std::string - url_get_host(const std::string &url); - - extern std::string unique_id; - extern net::net_e origin_web_ui_allowed; - -} // namespace http diff --git a/src/main.cpp b/src/main.cpp index a5c9e6de..5070483c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,7 +23,6 @@ #include "platform/common.h" #include "rtsp.h" #include "thread_pool.h" -#include "version.h" #include "video.h" #include "stream.h" @@ -68,51 +67,6 @@ struct NoDelete { BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", int) -/** - * @brief Print help to stdout. - * @param name The name of the program. - * - * EXAMPLES: - * ```cpp - * print_help("sunshine"); - * ``` - */ -void -print_help(const char *name) { - std::cout - << "Usage: "sv << name << " [options] [/path/to/configuration_file] [--cmd]"sv << std::endl - << " Any configurable option can be overwritten with: \"name=value\""sv << std::endl - << std::endl - << " Note: The configuration will be created if it doesn't exist."sv << std::endl - << std::endl - << " --help | print help"sv << std::endl - << " --creds username password | set user credentials for the Web manager"sv << std::endl - << " --version | print the version of sunshine"sv << std::endl - << std::endl - << " flags"sv << std::endl - << " -0 | Read PIN from stdin"sv << std::endl - << " -1 | Do not load previously saved state and do retain any state after shutdown"sv << std::endl - << " | Effectively starting as if for the first time without overwriting any pairings with your devices"sv << std::endl - << " -2 | Force replacement of headers in video stream"sv << std::endl - << " -p | Enable/Disable UPnP"sv << std::endl - << std::endl; -} - -namespace help { - int - entry(const char *name, int argc, char *argv[]) { - print_help(name); - return 0; - } -} // namespace help - -namespace version { - int - entry(const char *name, int argc, char *argv[]) { - std::cout << PROJECT_NAME << " version: v" << PROJECT_VER << std::endl; - return 0; - } -} // namespace version #ifdef _WIN32 namespace restore_nvprefs_undo { @@ -145,23 +99,31 @@ log_flush() { sink->flush(); } -std::map> signal_handlers; -void -on_signal_forwarder(int sig) { - signal_handlers.at(sig)(); +/** + * @brief Map a specified port based on the base port. + * @param port The port to map as a difference from the base port. + * @return `std:uint16_t` : The mapped port number. + * + * EXAMPLES: + * ```cpp + * std::uint16_t mapped_port = map_port(1); + * ``` + */ +std::uint16_t +map_port(int port) { + // calculate the port from the config port + auto mapped_port = (std::uint16_t)((int) config::sunshine.port + port); + + // Ensure port is in the range of 1024-65535 + if (mapped_port < 1024 || mapped_port > 65535) { + BOOST_LOG(warning) << "Port out of range: "sv << mapped_port; + } + + // TODO: Ensure port is not already in use by another application + + return mapped_port; } -template -void -on_signal(int sig, FN &&fn) { - signal_handlers.emplace(sig, std::forward(fn)); - - std::signal(sig, on_signal_forwarder); -} - - - - @@ -190,20 +152,10 @@ videoBroadcastThreadmain() { shutdown_event->raise(true); } -/** - * @brief Main application entry point. - * @param argc The number of arguments. - * @param argv The arguments. - * - * EXAMPLES: - * ```cpp - * main(1, const char* args[] = {"sunshine", nullptr}); - * ``` - */ -int -main(int argc, char *argv[]) { - task_pool_util::TaskPool::task_id_t force_shutdown = nullptr; + + +void Init() { #ifdef _WIN32 // Switch default C standard library locale to UTF-8 on Windows 10 1803+ setlocale(LC_ALL, ".UTF-8"); @@ -211,12 +163,8 @@ main(int argc, char *argv[]) { // Use UTF-8 conversion for the default C++ locale (used by boost::log) std::locale::global(std::locale(std::locale(), new std::codecvt_utf8)); - mail::man = std::make_shared(); - if (config::parse(argc, argv)) { - return 0; - } if (config::sunshine.min_log_level >= 1) { av_log_set_level(AV_LOG_QUIET); @@ -315,42 +263,11 @@ main(int argc, char *argv[]) { SetProcessShutdownParameters(0x100, SHUTDOWN_NORETRY); #endif - BOOST_LOG(info) << PROJECT_NAME << " version: " << PROJECT_VER << std::endl; - task_pool.start(1); - // Create signal handler after logging has been initialized - auto shutdown_event = mail::man->event(mail::shutdown); - on_signal(SIGINT, [&force_shutdown, shutdown_event]() { - BOOST_LOG(info) << "Interrupt handler called"sv; - - auto task = []() { - BOOST_LOG(fatal) << "10 seconds passed, yet Sunshine's still running: Forcing shutdown"sv; - log_flush(); - std::abort(); - }; - force_shutdown = task_pool.pushDelayed(task, 10s).task_id; - - shutdown_event->raise(true); - }); - - on_signal(SIGTERM, [&force_shutdown, shutdown_event]() { - BOOST_LOG(info) << "Terminate handler called"sv; - - auto task = []() { - BOOST_LOG(fatal) << "10 seconds passed, yet Sunshine's still running: Forcing shutdown"sv; - log_flush(); - std::abort(); - }; - force_shutdown = task_pool.pushDelayed(task, 10s).task_id; - - shutdown_event->raise(true); - }); - // If any of the following fail, we log an error and continue event though sunshine will not function correctly. // This allows access to the UI to fix configuration problems or view the logs. - auto deinit_guard = platf::init(); if (!deinit_guard) { BOOST_LOG(error) << "Platform failed to initialize"sv; @@ -361,9 +278,31 @@ main(int argc, char *argv[]) { if (video::probe_encoders()) { BOOST_LOG(error) << "Video failed to find working encoder"sv; } +} +void DeInit(){ +#ifdef WIN32 + // Restore global NVIDIA control panel settings + if (nvprefs_instance.owning_undo_file() && nvprefs_instance.load()) { + nvprefs_instance.restore_global_profile(); + nvprefs_instance.unload(); + } +#endif +} - +/** + * @brief Main application entry point. + * @param argc The number of arguments. + * @param argv The arguments. + * + * EXAMPLES: + * ```cpp + * main(1, const char* args[] = {"sunshine", nullptr}); + * ``` + */ +int +main(int argc, char *argv[]) { + Init(); auto video = std::thread { videoBroadcastThreadmain }; @@ -376,97 +315,8 @@ main(int argc, char *argv[]) { - shutdown_event->view(); - task_pool.stop(); - task_pool.join(); - - -#ifdef WIN32 - // Restore global NVIDIA control panel settings - if (nvprefs_instance.owning_undo_file() && nvprefs_instance.load()) { - nvprefs_instance.restore_global_profile(); - nvprefs_instance.unload(); - } -#endif - + // Create signal handler after logging has been initialized + mail::man->event(mail::shutdown)->view(); + DeInit(); return 0; } - -/** - * @brief Read a file to string. - * @param path The path of the file. - * @return `std::string` : The contents of the file. - * - * EXAMPLES: - * ```cpp - * std::string contents = read_file("path/to/file"); - * ``` - */ -std::string -read_file(const char *path) { - if (!std::filesystem::exists(path)) { - BOOST_LOG(debug) << "Missing file: " << path; - return {}; - } - - std::ifstream in(path); - - std::string input; - std::string base64_cert; - - while (!in.eof()) { - std::getline(in, input); - base64_cert += input + '\n'; - } - - return base64_cert; -} - -/** - * @brief Writes a file. - * @param path The path of the file. - * @param contents The contents to write. - * @return `int` : `0` on success, `-1` on failure. - * - * EXAMPLES: - * ```cpp - * int write_status = write_file("path/to/file", "file contents"); - * ``` - */ -int -write_file(const char *path, const std::string_view &contents) { - std::ofstream out(path); - - if (!out.is_open()) { - return -1; - } - - out << contents; - - return 0; -} - -/** - * @brief Map a specified port based on the base port. - * @param port The port to map as a difference from the base port. - * @return `std:uint16_t` : The mapped port number. - * - * EXAMPLES: - * ```cpp - * std::uint16_t mapped_port = map_port(1); - * ``` - */ -std::uint16_t -map_port(int port) { - // calculate the port from the config port - auto mapped_port = (std::uint16_t)((int) config::sunshine.port + port); - - // Ensure port is in the range of 1024-65535 - if (mapped_port < 1024 || mapped_port > 65535) { - BOOST_LOG(warning) << "Port out of range: "sv << mapped_port; - } - - // TODO: Ensure port is not already in use by another application - - return mapped_port; -} diff --git a/src/main.h b/src/main.h index f049b46c..1e335fd5 100644 --- a/src/main.h +++ b/src/main.h @@ -38,18 +38,8 @@ int main(int argc, char *argv[]); void log_flush(); -void -print_help(const char *name); -std::string -read_file(const char *path); -int -write_file(const char *path, const std::string_view &contents); std::uint16_t map_port(int port); -void -launch_ui(); -void -launch_ui_with_path(std::string path); // namespaces namespace mail { diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp deleted file mode 100644 index f9e71ed5..00000000 --- a/src/nvhttp.cpp +++ /dev/null @@ -1,1000 +0,0 @@ -/** - * @file src/nvhttp.h - * @brief todo - */ - -// macros -#define BOOST_BIND_GLOBAL_PLACEHOLDERS - -// standard includes -#include - -// lib includes -#include -#include -#include -#include -#include -#include -#include -#include - -// local includes -#include "config.h" -#include "crypto.h" -#include "httpcommon.h" -#include "main.h" -#include "network.h" -#include "nvhttp.h" -#include "platform/common.h" -#include "process.h" -#include "rtsp.h" -#include "utility.h" -#include "uuid.h" -#include "video.h" - -using namespace std::literals; -namespace nvhttp { - - namespace fs = std::filesystem; - namespace pt = boost::property_tree; - - class SunshineHttpsServer: public SimpleWeb::Server { - public: - SunshineHttpsServer(const std::string &certification_file, const std::string &private_key_file): - SimpleWeb::Server::Server(certification_file, private_key_file) {} - - std::function verify; - std::function, std::shared_ptr)> on_verify_failed; - - protected: - void - after_bind() override { - SimpleWeb::Server::after_bind(); - - if (verify) { - context.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert | boost::asio::ssl::verify_client_once); - context.set_verify_callback([](int verified, boost::asio::ssl::verify_context &ctx) { - // To respond with an error message, a connection must be established - return 1; - }); - } - } - - // This is Server::accept() with SSL validation support added - void - accept() override { - auto connection = create_connection(*io_service, context); - - acceptor->async_accept(connection->socket->lowest_layer(), [this, connection](const SimpleWeb::error_code &ec) { - auto lock = connection->handler_runner->continue_lock(); - if (!lock) - return; - - if (ec != SimpleWeb::error::operation_aborted) - this->accept(); - - auto session = std::make_shared(config.max_request_streambuf_size, connection); - - if (!ec) { - boost::asio::ip::tcp::no_delay option(true); - SimpleWeb::error_code ec; - session->connection->socket->lowest_layer().set_option(option, ec); - - session->connection->set_timeout(config.timeout_request); - session->connection->socket->async_handshake(boost::asio::ssl::stream_base::server, [this, session](const SimpleWeb::error_code &ec) { - session->connection->cancel_timeout(); - auto lock = session->connection->handler_runner->continue_lock(); - if (!lock) - return; - if (!ec) { - if (verify && !verify(session->connection->socket->native_handle())) - this->write(session, on_verify_failed); - else - this->read(session); - } - else if (this->on_error) - this->on_error(session->request, ec); - }); - } - else if (this->on_error) - this->on_error(session->request, ec); - }); - } - }; - - using https_server_t = SunshineHttpsServer; - using http_server_t = SimpleWeb::Server; - - struct conf_intern_t { - std::string servercert; - std::string pkey; - } conf_intern; - - struct client_t { - std::string uniqueID; - std::vector certs; - }; - - struct pair_session_t { - struct { - std::string uniqueID; - std::string cert; - } client; - - std::unique_ptr cipher_key; - std::vector clienthash; - - std::string serversecret; - std::string serverchallenge; - - struct { - util::Either< - std::shared_ptr::Response>, - std::shared_ptr::Response>> - response; - std::string salt; - } async_insert_pin; - }; - - // uniqueID, session - std::unordered_map map_id_sess; - std::unordered_map map_id_client; - - using args_t = SimpleWeb::CaseInsensitiveMultimap; - using resp_https_t = std::shared_ptr::Response>; - using req_https_t = std::shared_ptr::Request>; - using resp_http_t = std::shared_ptr::Response>; - using req_http_t = std::shared_ptr::Request>; - - enum class op_e { - ADD, - REMOVE - }; - - std::string - get_arg(const args_t &args, const char *name, const char *default_value = nullptr) { - auto it = args.find(name); - if (it == std::end(args)) { - if (default_value != NULL) { - return std::string(default_value); - } - - throw std::out_of_range(name); - } - return it->second; - } - - void - save_state() { - pt::ptree root; - - if (fs::exists(config::nvhttp.file_state)) { - try { - pt::read_json(config::nvhttp.file_state, root); - } - catch (std::exception &e) { - BOOST_LOG(error) << "Couldn't read "sv << config::nvhttp.file_state << ": "sv << e.what(); - return; - } - } - - root.erase("root"s); - - root.put("root.uniqueid", http::unique_id); - auto &nodes = root.add_child("root.devices", pt::ptree {}); - for (auto &[_, client] : map_id_client) { - pt::ptree node; - - node.put("uniqueid"s, client.uniqueID); - - pt::ptree cert_nodes; - for (auto &cert : client.certs) { - pt::ptree cert_node; - cert_node.put_value(cert); - cert_nodes.push_back(std::make_pair(""s, cert_node)); - } - node.add_child("certs"s, cert_nodes); - - nodes.push_back(std::make_pair(""s, node)); - } - - try { - pt::write_json(config::nvhttp.file_state, root); - } - catch (std::exception &e) { - BOOST_LOG(error) << "Couldn't write "sv << config::nvhttp.file_state << ": "sv << e.what(); - return; - } - } - - void - load_state() { - if (!fs::exists(config::nvhttp.file_state)) { - BOOST_LOG(info) << "File "sv << config::nvhttp.file_state << " doesn't exist"sv; - http::unique_id = uuid_util::uuid_t::generate().string(); - return; - } - - pt::ptree root; - try { - pt::read_json(config::nvhttp.file_state, root); - } - catch (std::exception &e) { - BOOST_LOG(error) << "Couldn't read "sv << config::nvhttp.file_state << ": "sv << e.what(); - - return; - } - - auto unique_id_p = root.get_optional("root.uniqueid"); - if (!unique_id_p) { - // This file doesn't contain moonlight credentials - http::unique_id = uuid_util::uuid_t::generate().string(); - return; - } - http::unique_id = std::move(*unique_id_p); - - auto device_nodes = root.get_child("root.devices"); - - for (auto &[_, device_node] : device_nodes) { - auto uniqID = device_node.get("uniqueid"); - auto &client = map_id_client.emplace(uniqID, client_t {}).first->second; - - client.uniqueID = uniqID; - - for (auto &[_, el] : device_node.get_child("certs")) { - client.certs.emplace_back(el.get_value()); - } - } - } - - void - update_id_client(const std::string &uniqueID, std::string &&cert, op_e op) { - switch (op) { - case op_e::ADD: { - auto &client = map_id_client[uniqueID]; - client.certs.emplace_back(std::move(cert)); - client.uniqueID = uniqueID; - } break; - case op_e::REMOVE: - map_id_client.erase(uniqueID); - break; - } - - if (!config::sunshine.flags[config::flag::FRESH_STATE]) { - save_state(); - } - } - - rtsp_stream::launch_session_t - make_launch_session(bool host_audio, const args_t &args) { - rtsp_stream::launch_session_t launch_session; - - launch_session.host_audio = host_audio; - launch_session.gcm_key = util::from_hex(get_arg(args, "rikey"), true); - std::stringstream mode = std::stringstream(get_arg(args, "mode", "0x0x0")); - // Split mode by the char "x", to populate width/height/fps - int x = 0; - std::string segment; - while (std::getline(mode, segment, 'x')) { - if (x == 0) launch_session.width = atoi(segment.c_str()); - if (x == 1) launch_session.height = atoi(segment.c_str()); - if (x == 2) launch_session.fps = atoi(segment.c_str()); - x++; - } - launch_session.unique_id = (get_arg(args, "uniqueid", "unknown")); - launch_session.appid = util::from_view(get_arg(args, "appid", "unknown")); - launch_session.enable_sops = util::from_view(get_arg(args, "sops", "0")); - launch_session.surround_info = util::from_view(get_arg(args, "surroundAudioInfo", "196610")); - launch_session.gcmap = util::from_view(get_arg(args, "gcmap", "0")); - launch_session.enable_hdr = util::from_view(get_arg(args, "hdrMode", "0")); - - uint32_t prepend_iv = util::endian::big(util::from_view(get_arg(args, "rikeyid"))); - auto prepend_iv_p = (uint8_t *) &prepend_iv; - - auto next = std::copy(prepend_iv_p, prepend_iv_p + sizeof(prepend_iv), std::begin(launch_session.iv)); - std::fill(next, std::end(launch_session.iv), 0); - return launch_session; - } - - void - getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) { - if (sess.async_insert_pin.salt.size() < 32) { - tree.put("root.paired", 0); - tree.put("root..status_code", 400); - tree.put("root..status_message", "Salt too short"); - return; - } - - std::string_view salt_view { sess.async_insert_pin.salt.data(), 32 }; - - auto salt = util::from_hex>(salt_view, true); - - auto key = crypto::gen_aes_key(salt, pin); - sess.cipher_key = std::make_unique(key); - - tree.put("root.paired", 1); - tree.put("root.plaincert", util::hex_vec(conf_intern.servercert, true)); - tree.put("root..status_code", 200); - } - void - serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const args_t &args) { - auto encrypted_response = util::from_hex_vec(get_arg(args, "serverchallengeresp"), true); - - std::vector decrypted; - crypto::cipher::ecb_t cipher(*sess.cipher_key, false); - - cipher.decrypt(encrypted_response, decrypted); - - sess.clienthash = std::move(decrypted); - - auto serversecret = sess.serversecret; - auto sign = crypto::sign256(crypto::pkey(conf_intern.pkey), serversecret); - - serversecret.insert(std::end(serversecret), std::begin(sign), std::end(sign)); - - tree.put("root.pairingsecret", util::hex_vec(serversecret, true)); - tree.put("root.paired", 1); - tree.put("root..status_code", 200); - } - - void - clientchallenge(pair_session_t &sess, pt::ptree &tree, const args_t &args) { - auto challenge = util::from_hex_vec(get_arg(args, "clientchallenge"), true); - - crypto::cipher::ecb_t cipher(*sess.cipher_key, false); - - std::vector decrypted; - cipher.decrypt(challenge, decrypted); - - auto x509 = crypto::x509(conf_intern.servercert); - auto sign = crypto::signature(x509); - auto serversecret = crypto::rand(16); - - decrypted.insert(std::end(decrypted), std::begin(sign), std::end(sign)); - decrypted.insert(std::end(decrypted), std::begin(serversecret), std::end(serversecret)); - - auto hash = crypto::hash({ (char *) decrypted.data(), decrypted.size() }); - auto serverchallenge = crypto::rand(16); - - std::string plaintext; - plaintext.reserve(hash.size() + serverchallenge.size()); - - plaintext.insert(std::end(plaintext), std::begin(hash), std::end(hash)); - plaintext.insert(std::end(plaintext), std::begin(serverchallenge), std::end(serverchallenge)); - - std::vector encrypted; - cipher.encrypt(plaintext, encrypted); - - sess.serversecret = std::move(serversecret); - sess.serverchallenge = std::move(serverchallenge); - - tree.put("root.paired", 1); - tree.put("root.challengeresponse", util::hex_vec(encrypted, true)); - tree.put("root..status_code", 200); - } - - void - clientpairingsecret(std::shared_ptr> &add_cert, pair_session_t &sess, pt::ptree &tree, const args_t &args) { - auto &client = sess.client; - - auto pairingsecret = util::from_hex_vec(get_arg(args, "clientpairingsecret"), true); - - std::string_view secret { pairingsecret.data(), 16 }; - std::string_view sign { pairingsecret.data() + secret.size(), crypto::digest_size }; - - assert((secret.size() + sign.size()) == pairingsecret.size()); - - auto x509 = crypto::x509(client.cert); - auto x509_sign = crypto::signature(x509); - - std::string data; - data.reserve(sess.serverchallenge.size() + x509_sign.size() + secret.size()); - - data.insert(std::end(data), std::begin(sess.serverchallenge), std::end(sess.serverchallenge)); - data.insert(std::end(data), std::begin(x509_sign), std::end(x509_sign)); - data.insert(std::end(data), std::begin(secret), std::end(secret)); - - auto hash = crypto::hash(data); - - // if hash not correct, probably MITM - if (!std::memcmp(hash.data(), sess.clienthash.data(), hash.size()) && crypto::verify256(crypto::x509(client.cert), secret, sign)) { - tree.put("root.paired", 1); - add_cert->raise(crypto::x509(client.cert)); - - auto it = map_id_sess.find(client.uniqueID); - - update_id_client(client.uniqueID, std::move(client.cert), op_e::ADD); - map_id_sess.erase(it); - } - else { - map_id_sess.erase(client.uniqueID); - tree.put("root.paired", 0); - } - - tree.put("root..status_code", 200); - } - - template - struct tunnel; - - template <> - struct tunnel { - static auto constexpr to_string = "HTTPS"sv; - }; - - template <> - struct tunnel { - static auto constexpr to_string = "NONE"sv; - }; - - template - void - print_req(std::shared_ptr::Request> request) { - BOOST_LOG(debug) << "TUNNEL :: "sv << tunnel::to_string; - - BOOST_LOG(debug) << "METHOD :: "sv << request->method; - BOOST_LOG(debug) << "DESTINATION :: "sv << request->path; - - for (auto &[name, val] : request->header) { - BOOST_LOG(debug) << name << " -- " << val; - } - - BOOST_LOG(debug) << " [--] "sv; - - for (auto &[name, val] : request->parse_query_string()) { - BOOST_LOG(debug) << name << " -- " << val; - } - - BOOST_LOG(debug) << " [--] "sv; - } - - template - void - not_found(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { - print_req(request); - - pt::ptree tree; - tree.put("root..status_code", 404); - - std::ostringstream data; - - pt::write_xml(data, tree); - response->write(data.str()); - - *response - << "HTTP/1.1 404 NOT FOUND\r\n" - << data.str(); - - response->close_connection_after_response = true; - } - - template - void - pair(std::shared_ptr> &add_cert, std::shared_ptr::Response> response, std::shared_ptr::Request> request) { - print_req(request); - - pt::ptree tree; - - auto fg = util::fail_guard([&]() { - std::ostringstream data; - - pt::write_xml(data, tree); - response->write(data.str()); - response->close_connection_after_response = true; - }); - - auto args = request->parse_query_string(); - if (args.find("uniqueid"s) == std::end(args)) { - tree.put("root..status_code", 400); - tree.put("root..status_message", "Missing uniqueid parameter"); - - return; - } - - auto uniqID { get_arg(args, "uniqueid") }; - auto sess_it = map_id_sess.find(uniqID); - - args_t::const_iterator it; - if (it = args.find("phrase"); it != std::end(args)) { - if (it->second == "getservercert"sv) { - pair_session_t sess; - - sess.client.uniqueID = std::move(uniqID); - sess.client.cert = util::from_hex_vec(get_arg(args, "clientcert"), true); - - BOOST_LOG(debug) << sess.client.cert; - auto ptr = map_id_sess.emplace(sess.client.uniqueID, std::move(sess)).first; - - ptr->second.async_insert_pin.salt = std::move(get_arg(args, "salt")); - if (config::sunshine.flags[config::flag::PIN_STDIN]) { - std::string pin; - - std::cout << "Please insert pin: "sv; - std::getline(std::cin, pin); - - getservercert(ptr->second, tree, pin); - } - else { - ptr->second.async_insert_pin.response = std::move(response); - - fg.disable(); - return; - } - } - else if (it->second == "pairchallenge"sv) { - tree.put("root.paired", 1); - tree.put("root..status_code", 200); - } - } - else if (it = args.find("clientchallenge"); it != std::end(args)) { - clientchallenge(sess_it->second, tree, args); - } - else if (it = args.find("serverchallengeresp"); it != std::end(args)) { - serverchallengeresp(sess_it->second, tree, args); - } - else if (it = args.find("clientpairingsecret"); it != std::end(args)) { - clientpairingsecret(add_cert, sess_it->second, tree, args); - } - else { - tree.put("root..status_code", 404); - tree.put("root..status_message", "Invalid pairing request"); - } - } - - /** - * @brief Compare the user supplied pin to the Moonlight pin. - * @param pin The user supplied pin. - * @return `true` if the pin is correct, `false` otherwise. - * - * EXAMPLES: - * ```cpp - * bool pin_status = nvhttp::pin("1234"); - * ``` - */ - bool - pin(std::string pin) { - pt::ptree tree; - if (map_id_sess.empty()) { - return false; - } - - auto &sess = std::begin(map_id_sess)->second; - getservercert(sess, tree, pin); - - // response to the request for pin - std::ostringstream data; - pt::write_xml(data, tree); - - auto &async_response = sess.async_insert_pin.response; - if (async_response.has_left() && async_response.left()) { - async_response.left()->write(data.str()); - } - else if (async_response.has_right() && async_response.right()) { - async_response.right()->write(data.str()); - } - else { - return false; - } - - // reset async_response - async_response = std::decay_t(); - // response to the current request - return true; - } - - template - void - serverinfo(std::shared_ptr::Response> response, std::shared_ptr::Request> request) { - print_req(request); - - int pair_status = 0; - if constexpr (std::is_same_v) { - auto args = request->parse_query_string(); - auto clientID = args.find("uniqueid"s); - - if (clientID != std::end(args)) { - if (auto it = map_id_client.find(clientID->second); it != std::end(map_id_client)) { - pair_status = 1; - } - } - } - - auto local_endpoint = request->local_endpoint(); - - pt::ptree tree; - - tree.put("root..status_code", 200); - tree.put("root.hostname", config::nvhttp.sunshine_name); - - tree.put("root.appversion", VERSION); - tree.put("root.GfeVersion", GFE_VERSION); - tree.put("root.uniqueid", http::unique_id); - tree.put("root.HttpsPort", map_port(PORT_HTTPS)); - tree.put("root.ExternalPort", map_port(PORT_HTTP)); - tree.put("root.mac", platf::get_mac_address(net::addr_to_normalized_string(local_endpoint.address()))); - tree.put("root.MaxLumaPixelsHEVC", video::active_hevc_mode > 1 ? "1869449984" : "0"); - - // Moonlight clients track LAN IPv6 addresses separately from LocalIP which is expected to - // always be an IPv4 address. If we return that same IPv6 address here, it will clobber the - // stored LAN IPv4 address. To avoid this, we need to return an IPv4 address in this field - // when we get a request over IPv6. - // - // HACK: We should return the IPv4 address of local interface here, but we don't currently - // have that implemented. For now, we will emulate the behavior of GFE+GS-IPv6-Forwarder, - // which returns 127.0.0.1 as LocalIP for IPv6 connections. Moonlight clients with IPv6 - // support know to ignore this bogus address. - if (local_endpoint.address().is_v6() && !local_endpoint.address().to_v6().is_v4_mapped()) { - tree.put("root.LocalIP", "127.0.0.1"); - } - else { - tree.put("root.LocalIP", net::addr_to_normalized_string(local_endpoint.address())); - } - - uint32_t codec_mode_flags = SCM_H264; - if (video::active_hevc_mode >= 2) { - codec_mode_flags |= SCM_HEVC; - } - if (video::active_hevc_mode >= 3) { - codec_mode_flags |= SCM_HEVC_MAIN10; - } - if (video::active_av1_mode >= 2) { - codec_mode_flags |= SCM_AV1_MAIN8; - } - if (video::active_av1_mode >= 3) { - codec_mode_flags |= SCM_AV1_MAIN10; - } - tree.put("root.ServerCodecModeSupport", codec_mode_flags); - - pt::ptree display_nodes; - for (auto &resolution : config::nvhttp.resolutions) { - auto pred = [](auto ch) { return ch == ' ' || ch == '\t' || ch == 'x'; }; - - auto middle = std::find_if(std::begin(resolution), std::end(resolution), pred); - if (middle == std::end(resolution)) { - BOOST_LOG(warning) << resolution << " is not in the proper format for a resolution: WIDTHxHEIGHT"sv; - continue; - } - - auto width = util::from_chars(&*std::begin(resolution), &*middle); - auto height = util::from_chars(&*(middle + 1), &*std::end(resolution)); - for (auto fps : config::nvhttp.fps) { - pt::ptree display_node; - display_node.put("Width", width); - display_node.put("Height", height); - display_node.put("RefreshRate", fps); - - display_nodes.add_child("DisplayMode", display_node); - } - } - - if (!config::nvhttp.resolutions.empty()) { - tree.add_child("root.SupportedDisplayMode", display_nodes); - } - auto current_appid = proc::proc.running(); - tree.put("root.PairStatus", pair_status); - tree.put("root.currentgame", current_appid); - tree.put("root.state", current_appid > 0 ? "SUNSHINE_SERVER_BUSY" : "SUNSHINE_SERVER_FREE"); - - std::ostringstream data; - - pt::write_xml(data, tree); - response->write(data.str()); - response->close_connection_after_response = true; - } - - void - applist(resp_https_t response, req_https_t request) { - print_req(request); - - pt::ptree tree; - - auto g = util::fail_guard([&]() { - std::ostringstream data; - - pt::write_xml(data, tree); - response->write(data.str()); - response->close_connection_after_response = true; - }); - - auto &apps = tree.add_child("root", pt::ptree {}); - - apps.put(".status_code", 200); - - for (auto &proc : proc::proc.get_apps()) { - pt::ptree app; - - app.put("IsHdrSupported"s, video::active_hevc_mode == 3 ? 1 : 0); - app.put("AppTitle"s, proc.name); - app.put("ID", proc.id); - - apps.push_back(std::make_pair("App", std::move(app))); - } - } - - void - launch(bool &host_audio, resp_http_t response, req_http_t request) { - print_req(request); - - pt::ptree tree; - auto g = util::fail_guard([&]() { - std::ostringstream data; - - pt::write_xml(data, tree); - response->write(data.str()); - response->close_connection_after_response = true; - }); - - if (rtsp_stream::session_count() == config::stream.channels) { - tree.put("root.resume", 0); - tree.put("root..status_code", 503); - tree.put("root..status_message", "The host's concurrent stream limit has been reached. Stop an existing stream or increase the 'Channels' value in the Sunshine Web UI."); - - return; - } - - auto args = request->parse_query_string(); - if ( - args.find("rikey"s) == std::end(args) || - args.find("rikeyid"s) == std::end(args) || - args.find("localAudioPlayMode"s) == std::end(args) || - args.find("appid"s) == std::end(args)) { - tree.put("root.resume", 0); - tree.put("root..status_code", 400); - tree.put("root..status_message", "Missing a required launch parameter"); - - return; - } - - auto appid = util::from_view(get_arg(args, "appid")); - - auto current_appid = proc::proc.running(); - if (current_appid > 0) { - tree.put("root.resume", 0); - tree.put("root..status_code", 400); - tree.put("root..status_message", "An app is already running on this host"); - - return; - } - - // Probe encoders again before streaming to ensure our chosen - // encoder matches the active GPU (which could have changed - // due to hotplugging, driver crash, primary monitor change, - // or any number of other factors). - if (rtsp_stream::session_count() == 0) { - if (video::probe_encoders()) { - tree.put("root..status_code", 503); - tree.put("root..status_message", "Failed to initialize video capture/encoding. Is a display connected and turned on?"); - tree.put("root.gamesession", 0); - - return; - } - } - - host_audio = util::from_view(get_arg(args, "localAudioPlayMode")); - auto launch_session = make_launch_session(host_audio, args); - - if (appid > 0) { - auto err = proc::proc.execute(appid, launch_session); - if (err) { - tree.put("root..status_code", err); - tree.put("root..status_message", "Failed to start the specified application"); - tree.put("root.gamesession", 0); - - return; - } - } - - rtsp_stream::launch_session_raise(launch_session); - - tree.put("root..status_code", 200); - tree.put("root.sessionUrl0", "rtsp://"s + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + std::to_string(map_port(rtsp_stream::RTSP_SETUP_PORT))); - tree.put("root.gamesession", 1); - } - - void - resume(bool &host_audio, resp_http_t response, req_http_t request) { - print_req(request); - - pt::ptree tree; - auto g = util::fail_guard([&]() { - std::ostringstream data; - - pt::write_xml(data, tree); - response->write(data.str()); - response->close_connection_after_response = true; - }); - - // It is possible that due a race condition that this if-statement gives a false negative, - // that is automatically resolved in rtsp_server_t - if (rtsp_stream::session_count() == config::stream.channels) { - tree.put("root.resume", 0); - tree.put("root..status_code", 503); - tree.put("root..status_message", "The host's concurrent stream limit has been reached. Stop an existing stream or increase the 'Channels' value in the Sunshine Web UI."); - - return; - } - - auto current_appid = proc::proc.running(); - if (current_appid == 0) { - tree.put("root.resume", 0); - tree.put("root..status_code", 503); - tree.put("root..status_message", "No running app to resume"); - - return; - } - - auto args = request->parse_query_string(); - if ( - args.find("rikey"s) == std::end(args) || - args.find("rikeyid"s) == std::end(args)) { - tree.put("root.resume", 0); - tree.put("root..status_code", 400); - tree.put("root..status_message", "Missing a required resume parameter"); - - return; - } - - if (rtsp_stream::session_count() == 0) { - // Probe encoders again before streaming to ensure our chosen - // encoder matches the active GPU (which could have changed - // due to hotplugging, driver crash, primary monitor change, - // or any number of other factors). - if (video::probe_encoders()) { - tree.put("root.resume", 0); - tree.put("root..status_code", 503); - tree.put("root..status_message", "Failed to initialize video capture/encoding. Is a display connected and turned on?"); - - return; - } - - // Newer Moonlight clients send localAudioPlayMode on /resume too, - // so we should use it if it's present in the args and there are - // no active sessions we could be interfering with. - if (args.find("localAudioPlayMode"s) != std::end(args)) { - host_audio = util::from_view(get_arg(args, "localAudioPlayMode")); - } - } - - rtsp_stream::launch_session_raise(make_launch_session(host_audio, args)); - - tree.put("root..status_code", 200); - tree.put("root.sessionUrl0", "rtsp://"s + net::addr_to_url_escaped_string(request->local_endpoint().address()) + ':' + std::to_string(map_port(rtsp_stream::RTSP_SETUP_PORT))); - tree.put("root.resume", 1); - } - - void - cancel(resp_http_t response, req_http_t request) { - print_req(request); - - pt::ptree tree; - auto g = util::fail_guard([&]() { - std::ostringstream data; - - pt::write_xml(data, tree); - response->write(data.str()); - response->close_connection_after_response = true; - }); - - // It is possible that due a race condition that this if-statement gives a false positive, - // the client should try again - if (rtsp_stream::session_count() != 0) { - tree.put("root.resume", 0); - tree.put("root..status_code", 503); - tree.put("root..status_message", "All sessions must be disconnected before quitting"); - - return; - } - - tree.put("root.cancel", 1); - tree.put("root..status_code", 200); - - if (proc::proc.running() > 0) { - proc::proc.terminate(); - } - } - - void - appasset(resp_https_t response, req_https_t request) { - print_req(request); - - auto args = request->parse_query_string(); - auto app_image = proc::proc.get_app_image(util::from_view(get_arg(args, "appid"))); - - std::ifstream in(app_image, std::ios::binary); - SimpleWeb::CaseInsensitiveMultimap headers; - headers.emplace("Content-Type", "image/png"); - response->write(SimpleWeb::StatusCode::success_ok, in, headers); - response->close_connection_after_response = true; - } - - /** - * @brief Start the nvhttp server. - * - * EXAMPLES: - * ```cpp - * nvhttp::start(); - * ``` - */ - void - start() { - // char* secret = ""; - auto shutdown_event = mail::man->event(mail::shutdown); - - auto port_http = map_port(PORT_HTTP); - auto port_https = map_port(PORT_HTTPS); - auto address_family = net::af_from_enum_string(config::sunshine.address_family); - - bool clean_slate = config::sunshine.flags[config::flag::FRESH_STATE]; - - if (!clean_slate) { - load_state(); - } - - conf_intern.pkey = read_file(config::nvhttp.pkey.c_str()); - conf_intern.servercert = read_file(config::nvhttp.cert.c_str()); - - crypto::cert_chain_t cert_chain; - for (auto &[_, client] : map_id_client) { - for (auto &cert : client.certs) { - cert_chain.add(crypto::x509(cert)); - } - } - - auto add_cert = std::make_shared>(30); - - // /resume doesn't always get the parameter "localAudioPlayMode" - // /launch will store it in host_audio - bool host_audio {}; - - http_server_t http_server; - - http_server.default_resource["GET"] = not_found; - http_server.resource["^/serverinfo$"]["GET"] = serverinfo; - http_server.resource["^/launch$"]["GET"] = [&host_audio](auto resp, auto req) { launch(host_audio, resp, req); }; - http_server.resource["^/resume$"]["GET"] = [&host_audio](auto resp, auto req) { resume(host_audio, resp, req); }; - http_server.resource["^/cancel$"]["GET"] = cancel; - - http_server.config.reuse_address = true; - http_server.config.address = net::af_to_any_address_string(address_family); - http_server.config.port = port_http; - - auto accept_and_run = [&](auto *http_server) { - try { - http_server->start(); - } - catch (boost::system::system_error &err) { - // It's possible the exception gets thrown after calling http_server->stop() from a different thread - if (shutdown_event->peek()) { - return; - } - - BOOST_LOG(fatal) << "Couldn't start http server on ports ["sv << port_https << ", "sv << port_https << "]: "sv << err.what(); - shutdown_event->raise(true); - return; - } - }; - std::thread tcp { accept_and_run, &http_server }; - - // Wait for any event - shutdown_event->view(); - - http_server.stop(); - - tcp.join(); - } - - /** - * @brief Remove all paired clients. - * - * EXAMPLES: - * ```cpp - * nvhttp::erase_all_clients(); - * ``` - */ - void - erase_all_clients() { - map_id_client.clear(); - save_state(); - } -} // namespace nvhttp diff --git a/src/nvhttp.h b/src/nvhttp.h deleted file mode 100644 index 3be24b3d..00000000 --- a/src/nvhttp.h +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @file src/nvhttp.h - * @brief todo - */ - -// macros -#pragma once - -// standard includes -#include - -// local includes -#include "thread_safe.h" - -/** - * @brief This namespace contains all the functions and variables related to the nvhttp (GameStream) server. - */ -namespace nvhttp { - - /** - * @brief The protocol version. - * @details The version of the GameStream protocol we are mocking. - * @note The negative 4th number indicates to Moonlight that this is Sunshine. - */ - constexpr auto VERSION = "7.1.431.-1"; - - /** - * @brief The GFE version we are replicating. - */ - constexpr auto GFE_VERSION = "3.23.0.74"; - - /** - * @brief The HTTP port, as a difference from the config port. - */ - constexpr auto PORT_HTTP = 0; - - /** - * @brief The HTTPS port, as a difference from the config port. - */ - constexpr auto PORT_HTTPS = -5; - - // functions - void - start(); - bool - pin(std::string pin); - void - erase_all_clients(); -} // namespace nvhttp diff --git a/src/stream.cpp b/src/stream.cpp index e9f8b7a0..d3849c74 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -1674,15 +1674,20 @@ namespace stream { // Current Nvidia drivers have a bug where NVENC can deadlock the encoder thread with hardware-accelerated // GPU scheduling enabled. If this happens, we will terminate ourselves and the service can restart. // The alternative is that Sunshine can never start another session until it's manually restarted. - auto task = []() { + + bool do_kill = false; + std::thread{[&do_kill]() { + Sleep(10000); + if(do_kill) + return; + BOOST_LOG(fatal) << "Hang detected! Session failed to terminate in 10 seconds."sv; log_flush(); std::abort(); - }; - auto force_kill = task_pool.pushDelayed(task, 10s).task_id; - auto fg = util::fail_guard([&force_kill]() { - // Cancel the kill task if we manage to return from this function - task_pool.cancel(force_kill); + }}; + + auto fg = util::fail_guard([&do_kill]() { + do_kill = true; }); BOOST_LOG(debug) << "Waiting for video to end..."sv; diff --git a/src/version.h.in b/src/version.h.in deleted file mode 100644 index badb5aa0..00000000 --- a/src/version.h.in +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @file src/version.h.in - * @brief Version definitions for Sunshine. - * @note The final `version.h` is generated from this file during the CMake build. - */ -#pragma once - -#define PROJECT_NAME "@PROJECT_NAME@" -#define PROJECT_VER "@PROJECT_VERSION@" -#define PROJECT_VER_MAJOR "@PROJECT_VERSION_MAJOR@" -#define PROJECT_VER_MINOR "@PROJECT_VERSION_MINOR@" -#define PROJECT_VER_PATCH "@PROJECT_VERSION_PATCH@"