diff --git a/CMakeLists.txt b/CMakeLists.txt index fa1fa37d2e..702d35eade 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,9 +46,6 @@ endif() cmake_dependent_option(YUZU_STATIC_ROOM "Build a static room executable only (CI only)" OFF "LINUX" OFF) if (YUZU_STATIC_ROOM) - set(YUZU_ROOM ON) - set(YUZU_ROOM_STANDALONE ON) - # disable e v e r y t h i n g set(ENABLE_QT OFF) set(YUZU_CMD OFF) @@ -257,9 +254,6 @@ option(YUZU_LEGACY "Apply patches that improve compatibility with older GPUs (e. option(NIGHTLY_BUILD "Use Nightly qualifiers in the update checker and build metadata" OFF) -cmake_dependent_option(YUZU_ROOM "Enable dedicated room functionality" ON "NOT ANDROID" OFF) -cmake_dependent_option(YUZU_ROOM_STANDALONE "Enable standalone room executable" ON "YUZU_ROOM" OFF) - cmake_dependent_option(YUZU_CMD "Compile the eden-cli executable" ON "NOT ANDROID" OFF) cmake_dependent_option(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF) @@ -300,10 +294,6 @@ if (ARCHITECTURE_arm64 AND (ANDROID OR LINUX)) add_compile_definitions(HAS_NCE=1) endif() -if (YUZU_ROOM) - add_compile_definitions(YUZU_ROOM) -endif() - if (UNIX AND NOT (LINUX OR WIN32)) if(CXX_APPLE OR CXX_CLANG) # libc++ has stop_token and jthread as experimental diff --git a/docs/Options.md b/docs/Options.md index a8552f2f0f..9191802017 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -65,8 +65,6 @@ These options control executables and build flavors. **Desktop only**: - `YUZU_CMD` (ON) Compile the SDL-based frontend (eden-cli) -- `YUZU_ROOM` (OFF) Compile dedicated room functionality into the main executable -- `YUZU_ROOM_STANDALONE` (OFF) Compile a separate executable for room functionality - `YUZU_STATIC_ROOM` (OFF) Compile the room executable *only* as a static, portable executable - This is only usable on Alpine Linux. @@ -101,5 +99,7 @@ The following options were a part of Eden at one point, but have since been reti - `YUZU_USE_CPM` - This option once had a purpose, but that purpose has long since passed us by. *All* builds use CPMUtil to manage dependencies now. - If you want to *force* the usage of system dependencies, use `-DCPMUTIL_FORCE_SYSTEM=ON`. - `YUZU_USE_EXTERNAL_SDL` - This is now handled automatically. It was included even after CPM for purposes that have not applied for a very long time. +- `YUZU_ROOM` - Room functionality is now built-in into eden-cli unconditionally. +- `YUZU_ROOM_STANDALONE` - Likewise. See `src/dynarmic/CMakeLists.txt` for additional options--usually, these don't need changed diff --git a/docs/user/CommandLine.md b/docs/user/CommandLine.md index 51542115e3..443ea5b34c 100644 --- a/docs/user/CommandLine.md +++ b/docs/user/CommandLine.md @@ -17,16 +17,36 @@ There are two main applications, an SDL-based app (`eden-cli`) and a Qt based ap ## eden-cli -- `--debug/-d`: Enter debug mode, allow gdb stub at port `1234` -- `--config/-c`: Specify alternate configuration file. -- `--fullscreen/-f`: Set fullscreen. -- `--help/-h`: Display help. -- `--game/-g`: Specify the game to run. -- `--multiplayer/-m`: Specify multiplayer options. -- `--program/-p`: Specify the program arguments to pass (optional). -- `--user/-u`: Specify the user index. -- `--version/-v`: Display version and quit. -- `--input-profile/-i`: Specifies input profile name to use (for player #0 only). -- `--null-render/-n`: Forces the usage of the "Null" render backend irrespective of settings. -- `--filter/-x`: Sets the debug log filter irrespective of settings. -- `--singlecore/-s`: Forces single-core regardless of settings. +- `-d/--debug`: Enter debug mode, allow gdb stub at port `1234` +- `-c/--config`: Specify alternate configuration file. +- `-f/--fullscreen`: Set fullscreen. +- `-h/--help`: Display help. +- `-g/--game`: Specify the game to run. +- `-m/--multiplayer`: Specify multiplayer options. +- `-p/--program`: Specify the program arguments to pass (optional). +- `-u/--user`: Specify the user index. +- `-v/--version`: Display version and quit. +- `-i/--input-profile`: Specifies input profile name to use (for player #0 only). +- `-n/--null-render`: Forces the usage of the "Null" render backend irrespective of settings. +- `-x/--filter`: Sets the debug log filter irrespective of settings. +- `-s/--singlecore`: Forces single-core regardless of settings. +- `-l/--log-file`: The file for storing the room log. +- `-H/--headless`: Force headless mode (no GUI). Currently only used for rooms. + +Room settings: +- `-N/--name`: The name of the room. +- `-D/--description`: The room description. +- `-S/--bind-address`: The bind address for the room. +- `-P/--port`: The port used for the room. +- `-M/--max-members`: The maximum number of players for this room. +- `-W/--password`: The password for the room. +- `-G/--preferred-game`: The preferred game for this room. +- `-I/--preferred-game-id`: The preferred game-id for this room. +- `-U/--username`: The username used for announce. +- `-T/--token`: The token used for announce. +- `-A/--web-api-url`: yuzu Web API url. +- `-B/--ban-list-file`: The file for storing the room ban list. +- `-h/--help`: Display this help and exit. +- `-v/--version`: Output version information and exit., + +If the name and description of the room is specified, then it will default to headless mode if not already specified. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a2abc7d1b3..96aec1e50b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -251,15 +251,6 @@ endif() add_subdirectory(common) add_subdirectory(network) -if (YUZU_ROOM) - add_subdirectory(dedicated_room) -endif() - -if (YUZU_ROOM_STANDALONE) - add_subdirectory(yuzu_room_standalone) - set_target_properties(yuzu-room PROPERTIES OUTPUT_NAME "eden-room") -endif() - if (YUZU_STATIC_ROOM) return() endif() diff --git a/src/dedicated_room/CMakeLists.txt b/src/dedicated_room/CMakeLists.txt deleted file mode 100644 index 8ca138473f..0000000000 --- a/src/dedicated_room/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -# SPDX-License-Identifier: GPL-3.0-or-later - -# SPDX-FileCopyrightText: 2017 Citra Emulator Project -# SPDX-License-Identifier: GPL-2.0-or-later - -add_library(yuzu-room STATIC EXCLUDE_FROM_ALL - yuzu_room.cpp - yuzu_room.h - yuzu_room.rc) - -target_link_libraries(yuzu-room PRIVATE common network) -if (ENABLE_WEB_SERVICE) - target_compile_definitions(yuzu-room PRIVATE ENABLE_WEB_SERVICE) - target_link_libraries(yuzu-room PRIVATE web_service) -endif() - -target_link_libraries(yuzu-room PRIVATE - OpenSSL::SSL - OpenSSL::Crypto) - -if (MSVC) - target_link_libraries(yuzu-room PRIVATE getopt) -endif() -target_link_libraries(yuzu-room PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) - -create_target_directory_groups(yuzu-room) diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp deleted file mode 100644 index 824fd0f26c..0000000000 --- a/src/dedicated_room/yuzu_room.cpp +++ /dev/null @@ -1,394 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include -#include -#include -#include -#include - -#ifdef _WIN32 -// windows.h needs to be included before shellapi.h -#include - -#include -#endif - -#include -#include "common/common_types.h" -#include "common/fs/file.h" -#include "common/fs/fs.h" -#include "common/fs/path_util.h" -#include "common/logging.h" -#include "common/scm_rev.h" -#include "common/settings.h" -#include "common/string_util.h" -#include "core/core.h" -#include "network/announce_multiplayer_session.h" -#include "network/network.h" -#include "network/room.h" -#include "network/verify_user.h" - -#ifdef ENABLE_WEB_SERVICE -#include "web_service/verify_user_jwt.h" -#endif - -#undef _UNICODE -#include -#ifndef _MSC_VER -#include -#endif - -#include "yuzu_room.h" - -static void PrintHelp(const char* argv0) { - LOG_INFO(Network, - "Usage: {}" - " [options] \n" - "-n, --room-name The name of the room\n" - "-d, --room-description The room description\n" - "-s, --bind-address The bind address for the room\n" - "-p, --port The port used for the room\n" - "-m, --max-members The maximum number of players for this room\n" - "-w, --password The password for the room\n" - "-g, --preferred-game The preferred game for this room\n" - "-i, --preferred-game-id The preferred game-id for this room\n" - "-u, --username The username used for announce\n" - "-t, --token The token used for announce\n" - "-a, --web-api-url yuzu Web API url\n" - "-b, --ban-list-file The file for storing the room ban list\n" - "-l, --log-file The file for storing the room log\n" - "-h, --help Display this help and exit\n" - "-v, --version Output version information and exit\n", - argv0); -} - -static void PrintVersion() { - LOG_INFO(Network, "Eden dedicated room {} {} Libnetwork: {}", Common::g_scm_branch, - Common::g_scm_desc, Network::network_version); -} - -/// The magic text at the beginning of a yuzu-room ban list file. -static constexpr char BanListMagic[] = "YuzuRoom-BanList-1"; - -static constexpr char token_delimiter{':'}; - -static void PadToken(std::string& token) { - std::array output{}; - std::array roundtrip{}; - for (size_t i = 0; i < 3; i++) { - EVP_DecodeBlock(output.data(), reinterpret_cast(token.c_str()), token.size()); - EVP_EncodeBlock(output.data(), roundtrip.data(), roundtrip.size()); - if (memcmp(roundtrip.data(), token.data(), token.size()) == 0) { - break; - } - token.push_back('='); - } -} - -static std::string UsernameFromDisplayToken(const std::string& display_token) { - std::size_t outlen = 4 * ((display_token.length() + 2) / 3); - std::array output{}; - EVP_DecodeBlock(output.data(), reinterpret_cast(display_token.c_str()), display_token.length()); - std::string decoded_display_token(reinterpret_cast(&output), outlen); - return decoded_display_token.substr(0, decoded_display_token.find(token_delimiter)); -} - -static std::string TokenFromDisplayToken(const std::string& display_token) { - std::size_t outlen = 4 * ((display_token.length() + 2) / 3); - std::array output{}; - EVP_DecodeBlock(output.data(), reinterpret_cast(display_token.c_str()), display_token.length()); - std::string decoded_display_token(reinterpret_cast(&output), outlen); - return decoded_display_token.substr(decoded_display_token.find(token_delimiter) + 1); -} - -static Network::Room::BanList LoadBanList(const std::string& path) { - std::ifstream file; - Common::FS::OpenFileStream(file, path, std::ios_base::in); - if (!file || file.eof()) { - LOG_ERROR(Network, "Could not open ban list!"); - return {}; - } - std::string magic; - std::getline(file, magic); - if (magic != BanListMagic) { - LOG_ERROR(Network, "Ban list is not valid!"); - return {}; - } - - // false = username ban list, true = ip ban list - bool ban_list_type = false; - Network::Room::UsernameBanList username_ban_list; - Network::Room::IPBanList ip_ban_list; - while (!file.eof()) { - std::string line; - std::getline(file, line); - line.erase(std::remove(line.begin(), line.end(), '\0'), line.end()); - line = Common::StripSpaces(line); - if (line.empty()) { - // An empty line marks start of the IP ban list - ban_list_type = true; - continue; - } - if (ban_list_type) { - ip_ban_list.emplace_back(line); - } else { - username_ban_list.emplace_back(line); - } - } - - return {username_ban_list, ip_ban_list}; -} - -static void SaveBanList(const Network::Room::BanList& ban_list, const std::string& path) { - std::ofstream file; - Common::FS::OpenFileStream(file, path, std::ios_base::out); - if (!file) { - LOG_ERROR(Network, "Could not save ban list!"); - return; - } - - file << BanListMagic << "\n"; - - // Username ban list - for (const auto& username : ban_list.first) { - file << username << "\n"; - } - file << "\n"; - - // IP ban list - for (const auto& ip : ban_list.second) { - file << ip << "\n"; - } -} - -/// Application entry point -void LaunchRoom(int argc, char** argv, bool called_by_option) { - int option_index = 0; - char* endarg; - - char* new_argv0 = argv[0]; - if (called_by_option) { - strncat(new_argv0, " --room", 8); - } - - std::string room_name; - std::string room_description; - std::string password; - std::string preferred_game; - std::string username; - std::string token; - std::string web_api_url; - std::string ban_list_file; - std::string log_file = "eden-room.log"; - std::string bind_address; - u64 preferred_game_id = 0; - u32 port = Network::DefaultRoomPort; - u32 max_members = 16; - - static struct option long_options[] = { - {"room-name", required_argument, 0, 'n'}, - {"room-description", required_argument, 0, 'd'}, - {"bind-address", required_argument, 0, 's'}, - {"port", required_argument, 0, 'p'}, - {"max-members", required_argument, 0, 'm'}, - {"password", required_argument, 0, 'w'}, - {"preferred-game", required_argument, 0, 'g'}, - {"preferred-game-id", required_argument, 0, 'i'}, - {"username", optional_argument, 0, 'u'}, - {"token", required_argument, 0, 't'}, - {"web-api-url", required_argument, 0, 'a'}, - {"ban-list-file", required_argument, 0, 'b'}, - {"log-file", required_argument, 0, 'l'}, - {"help", no_argument, 0, 'h'}, - {"version", no_argument, 0, 'v'}, - // Entry option - {"room", 0, 0, 0}, - {0, 0, 0, 0}, - }; - - Common::Log::Initialize(); - Common::Log::SetColorConsoleBackendEnabled(true); - Common::Log::Start(); - - while (optind < argc) { - int arg = getopt_long(argc, argv, "n:d:s:p:m:w:g:u:t:a:i:l:hv", long_options, &option_index); - if (arg != -1) { - char carg = static_cast(arg); - - switch (carg) { - case 'n': - room_name.assign(optarg); - break; - case 'd': - room_description.assign(optarg); - break; - case 's': - bind_address.assign(optarg); - break; - case 'p': - port = strtoul(optarg, &endarg, 0); - break; - case 'm': - max_members = strtoul(optarg, &endarg, 0); - break; - case 'w': - password.assign(optarg); - break; - case 'g': - preferred_game.assign(optarg); - break; - case 'i': - preferred_game_id = strtoull(optarg, &endarg, 16); - break; - case 'u': - username.assign(optarg); - break; - case 't': - token.assign(optarg); - break; - case 'a': - web_api_url.assign(optarg); - break; - case 'b': - ban_list_file.assign(optarg); - break; - case 'l': - log_file.assign(optarg); - break; - case 'h': - PrintHelp(argv[0]); - std::exit(0); - case 'v': - PrintVersion(); - std::exit(0); - default: - break; - } - } - } - - if (room_name.empty()) { - LOG_ERROR(Network, "Room name is empty!"); - PrintHelp(argv[0]); - std::exit(-1); - } - if (preferred_game.empty()) { - LOG_ERROR(Network, "Preferred game is empty!"); - PrintHelp(argv[0]); - std::exit(-1); - } - if (preferred_game_id == 0) { - LOG_ERROR(Network, - "preferred-game-id not set!\nThis should get set to allow users to find your " - "room.\nSet with --preferred-game-id id"); - } - if (max_members > Network::MaxConcurrentConnections || max_members < 2) { - LOG_ERROR(Network, - "max_members needs to be in the range 2 - {}!", - Network::MaxConcurrentConnections); - PrintHelp(argv[0]); - std::exit(-1); - } - if (bind_address.empty()) { - LOG_INFO(Network, "Bind address is empty: defaulting to 0.0.0.0"); - } - if (port > UINT16_MAX) { - LOG_ERROR(Network, "Port needs to be in the range 0 - 65535!"); - PrintHelp(argv[0]); - std::exit(-1); - } - if (ban_list_file.empty()) { - LOG_ERROR(Network, - "Ban list file not set!\nThis should get set to load and save room ban " - "list.\nSet with --ban-list-file "); - } - bool announce = true; - if (token.empty() && announce) { - announce = false; - LOG_INFO(Network, "Token is empty: Hosting a private room"); - } - if (web_api_url.empty() && announce) { - announce = false; - LOG_INFO(Network, "Endpoint url is empty: Hosting a private room"); - } - if (announce) { - if (username.empty()) { - LOG_INFO(Network, "Hosting a public room"); - Settings::values.web_api_url = web_api_url; - PadToken(token); - Settings::values.eden_username = UsernameFromDisplayToken(token); - username = Settings::values.eden_username.GetValue(); - Settings::values.eden_token = TokenFromDisplayToken(token); - } else { - LOG_INFO(Network, "Hosting a public room"); - Settings::values.web_api_url = web_api_url; - Settings::values.eden_username = username; - Settings::values.eden_token = token; - } - } - - // Load the ban list - Network::Room::BanList ban_list; - if (!ban_list_file.empty()) { - ban_list = LoadBanList(ban_list_file); - } - - std::unique_ptr verify_backend; - if (announce) { -#ifdef ENABLE_WEB_SERVICE - verify_backend = - std::make_unique(Settings::values.web_api_url.GetValue()); -#else - LOG_INFO(Network, - "Eden Web Services is not available with this build: validation is disabled."); - verify_backend = std::make_unique(); -#endif - } else { - verify_backend = std::make_unique(); - } - - Network::Init(); - if (auto room = Network::GetRoom().lock()) { - AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game, - .id = preferred_game_id}; - if (!room->Create(room_name, room_description, bind_address, static_cast(port), - password, max_members, username, preferred_game_info, - std::move(verify_backend), ban_list)) { - LOG_INFO(Network, "Failed to create room: "); - std::exit(-1); - } - LOG_INFO(Network, "Room is open. Close with Q+Enter..."); - auto announce_session = std::make_unique(); - if (announce) { - announce_session->Start(); - } - while (room->GetState() == Network::Room::State::Open) { - std::string in; - std::cin >> in; - if (in.size() > 0) { - break; - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - if (announce) { - announce_session->Stop(); - } - announce_session.reset(); - // Save the ban list - if (!ban_list_file.empty()) { - SaveBanList(room->GetBanList(), ban_list_file); - } - room->Destroy(); - } - Network::Shutdown(); - std::exit(0); -} diff --git a/src/dedicated_room/yuzu_room.h b/src/dedicated_room/yuzu_room.h deleted file mode 100644 index 4dbd4808bf..0000000000 --- a/src/dedicated_room/yuzu_room.h +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright eden Emulator Project -// Licensed under GPLv3 or any later version -// Refer to the license.txt file included. - -#pragma once - -void LaunchRoom(int argc, char** argv, bool called_by_option); diff --git a/src/dedicated_room/yuzu_room.rc b/src/dedicated_room/yuzu_room.rc deleted file mode 100644 index 0ed520cd9a..0000000000 --- a/src/dedicated_room/yuzu_room.rc +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-FileCopyrightText: 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "winresrc.h" -///////////////////////////////////////////////////////////////////////////// -// -// Icon -// - -// Icon with lowest ID value placed first to ensure application icon -// remains consistent on all systems. -YUZU_ICON ICON "../../dist/eden.ico" - - -///////////////////////////////////////////////////////////////////////////// -// -// RT_MANIFEST -// - -0 RT_MANIFEST "../../dist/yuzu.manifest" diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 021ed9ee3e..fe33cd13ee 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -434,10 +434,6 @@ if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) target_link_libraries(yuzu PRIVATE dynarmic::dynarmic) endif() -if (YUZU_ROOM) - target_link_libraries(yuzu PRIVATE yuzu-room) -endif() - if (NOT MSVC AND (APPLE OR NOT YUZU_STATIC_BUILD)) # needed for vma target_compile_options(yuzu PRIVATE diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e34f8f8d94..353d358a82 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -4,10 +4,6 @@ #include #include "startup_checks.h" -#if YUZU_ROOM -#include -#include "dedicated_room/yuzu_room.h" -#endif #ifdef __unix__ #include "qt_common/gui_settings.h" #endif @@ -78,20 +74,6 @@ static Qt::HighDpiScaleFactorRoundingPolicy GetHighDpiRoundingPolicy() { } int main(int argc, char* argv[]) { -#if YUZU_ROOM - bool launch_room = false; - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--room") == 0) { - launch_room = true; - } - } - - if (launch_room) { - LaunchRoom(argc, argv, true); - return 0; - } -#endif - bool has_broken_vulkan = false; bool is_child = false; if (CheckEnvVars(&is_child)) { diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt index 09a9bd6da1..46254b5f07 100644 --- a/src/yuzu_cmd/CMakeLists.txt +++ b/src/yuzu_cmd/CMakeLists.txt @@ -38,7 +38,16 @@ add_executable(yuzu-cmd ${OPENGL_SOURCES} ) -target_link_libraries(yuzu-cmd PRIVATE common core input_common frontend_common video_core) +target_link_libraries(yuzu-cmd PRIVATE common network core input_common frontend_common video_core) +if (ENABLE_WEB_SERVICE) + target_compile_definitions(yuzu-room PRIVATE ENABLE_WEB_SERVICE) + target_link_libraries(yuzu-room PRIVATE web_service) +endif() + +target_link_libraries(yuzu-room PRIVATE + OpenSSL::SSL + OpenSSL::Crypto) + if (ENABLE_OPENGL) target_link_libraries(yuzu-cmd PRIVATE glad) endif() diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 27b5746ef3..27dd5c1e40 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -8,6 +8,9 @@ #include #include #include +#include +#include +#include "common/fs/file.h" #include "common/settings_enums.h" #ifdef __EMSCRIPTEN__ #include @@ -32,9 +35,17 @@ #include "core/loader/loader.h" #include "frontend_common/config.h" #include "input_common/main.h" +#include "network/announce_multiplayer_session.h" #include "network/network.h" -#include "sdl_config.h" +#include "network/room.h" +#include "network/verify_user.h" +#ifdef ENABLE_WEB_SERVICE +#include "web_service/verify_user_jwt.h" +#endif + +#include "yuzu_cmd/sdl_config.h" #include "video_core/renderer_base.h" + #include "yuzu_cmd/emu_window/emu_window_sdl3.h" #ifdef HAS_OPENGL #include "yuzu_cmd/emu_window/emu_window_sdl3_gl.h" @@ -65,22 +76,44 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; #endif static void PrintHelp(const char* argv0) { - std::cout << "Usage: " << argv0 - << " [options] \n" - "-c, --config Load the specified configuration file\n" - "-f, --fullscreen Start in fullscreen mode\n" - "-g, --game File path of the game to load\n" - "-h, --help Display this help and exit\n" - "-m, --multiplayer=nick:password@address:port" - " Nickname, password, address and port for multiplayer\n" - "-p, --program Pass following string as arguments to executable\n" - "-u, --user Select a specific user profile from 0 to 7\n" - "-d, --debug Run the GDB stub on a port from 1 to 65535\n" - "-v, --version Output version information and exit\n"; + LOG_INFO(Frontend, "Usage: {} [options] \n" + "Core options:\n" + "-c, --config Load the specified configuration file\n" + "-f, --fullscreen Start in fullscreen mode\n" + "-g, --game File path of the game to load\n" + "-h, --help Display this help and exit\n" + "-m, --multiplayer=nick:password@address:port Nickname, password, address and port for multiplayer\n" + "-p, --program Pass following string as arguments to executable\n" + "-u, --user Select a specific user profile from 0 to 7\n" + "-d, --debug Run the GDB stub on a port from 1 to 65535\n" + "-i, --input-profile Specifies input profile name to use (for player #0 only)\n" + "-n, --null-render Forces the usage of the \"Null\" render backend irrespective of settings\n" + "-x, --filter Sets the debug log filter irrespective of settings\n" + "-s, --singlecore Forces single-core regardless of settings\n" + "Shared options:\n" + "-l, --log-file The file for storing the room log\n" + "-H, --headless Force headless mode (no GUI). Currently only used for rooms\n" + "Room options:\n" + "-N, --name The name of the room\n" + "-D, --description The room description\n" + "-S, --bind-address The bind address for the room\n" + "-P, --port The port used for the room\n" + "-M, --max-members The maximum number of players for this room\n" + "-W, --password The password for the room\n" + "-G, --preferred-game The preferred game for this room\n" + "-I, --preferred-game-id The preferred game-id for this room\n" + "-U, --username The username used for announce\n" + "-T, --token The token used for announce\n" + "-A, --web-api-url yuzu Web API url\n" + "-B, --ban-list-file The file for storing the room ban list\n" + "Misc. options:\n" + "-h, --help Display this help and exit\n" + "-v, --version Output version information and exit\n", + argv0); } static void PrintVersion() { - std::cout << "Eden " << Common::g_scm_branch << " " << Common::g_scm_desc << std::endl; + LOG_INFO(Frontend, "Eden {} {}, Libnetwork: {}", Common::g_scm_branch, Common::g_scm_desc, Network::network_version); } static void OnStateChanged(const Network::RoomMember::State& state) { @@ -181,8 +214,381 @@ static void OnStatusMessageReceived(const Network::StatusMessageEntry& msg) { struct SdlState { Core::System system{}; std::unique_ptr emu_window; + // settings + struct { + std::optional config_path{}; + std::optional log_filter{}; + std::string nickname{}; + std::string password{}; + std::string address{}; + std::string input_profile{}; + std::string filepath{}; + std::string program_args{}; + std::string room_name{}; + std::string room_description{}; + std::string preferred_game{}; + std::string username{}; + std::string token{}; + std::string web_api_url{}; + std::string ban_list_file{}; + std::string log_file = "eden-room.log"; + std::string bind_address{}; + std::optional selected_user{}; + std::optional override_gdb_port{}; + std::optional headless{}; + u64 preferred_game_id = 0; + u32 max_members = 16; + u16 port = Network::DefaultRoomPort; + bool use_multiplayer = false; + bool fullscreen = false; + bool force_null_render = false; + bool force_single_core = false; + } opts = {}; }; +static SDL_AppResult ExecuteWithGUI(SdlState& state) { + SdlConfig config{state.opts.config_path}; + + // apply the log_filter setting + // the logger was initialized before and doesn't pick up the filter on its own + Common::Log::Filter filter; + filter.ParseFilterString(state.opts.log_filter.value_or(Settings::values.log_filter.GetValue())); + Common::Log::SetGlobalFilter(filter); + + if (!state.opts.program_args.empty()) { + Settings::values.program_args = state.opts.program_args; + } + + if (!state.opts.input_profile.empty()) { + auto& players = Settings::values.players.GetValue(); + players[0].profile_name = state.opts.input_profile; + } + + if (state.opts.selected_user.has_value()) { + Settings::values.current_user = std::clamp(*state.opts.selected_user, 0, 7); + } + + if (state.opts.override_gdb_port.has_value()) { + Settings::values.use_gdbstub = true; + Settings::values.gdbstub_port = *state.opts.override_gdb_port; + } + + if (state.opts.force_single_core) { + Settings::values.use_multi_core = false; + } + + if (state.opts.force_null_render) { + Settings::values.renderer_backend = Settings::RendererBackend::Null; + } + +#ifdef _WIN32 + LocalFree(argv_w); +#endif + + if (state.opts.filepath.empty()) { + LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified"); + return SDL_APP_FAILURE; + } + + state.system.Initialize(); + + InputCommon::InputSubsystem input_subsystem{}; + + // Apply the command line arguments + state.system.ApplySettings(); + + switch (Settings::values.renderer_backend.GetValue()) { +#ifdef HAS_OPENGL + case Settings::RendererBackend::OpenGL_GLSL: + case Settings::RendererBackend::OpenGL_GLASM: + case Settings::RendererBackend::OpenGL_SPIRV: + state.emu_window = std::make_unique(&input_subsystem, state.system, state.opts.fullscreen); + break; +#endif + case Settings::RendererBackend::Vulkan: + state.emu_window = std::make_unique(&input_subsystem, state.system, state.opts.fullscreen); + break; + case Settings::RendererBackend::Null: + state.emu_window = std::make_unique(&input_subsystem, state.system, state.opts.fullscreen); + break; + default: + LOG_CRITICAL(Frontend, "Invalid renderer backend"); + return SDL_APP_FAILURE; + } + +#ifdef _WIN32 + Common::Windows::SetCurrentTimerResolutionToMaximum(); + state.system.CoreTiming().SetTimerResolutionNs(Common::Windows::GetCurrentTimerResolution()); +#endif + + state.system.SetContentProvider(std::make_unique()); + state.system.SetFilesystem(std::make_shared()); + state.system.GetFileSystemController().CreateFactories(*state.system.GetFilesystem()); + state.system.GetUserChannel().clear(); + + Service::AM::FrontendAppletParameters load_parameters{ + .applet_id = Service::AM::AppletId::Application, + }; + const Core::SystemResultStatus load_result = state.system.Load(*state.emu_window, state.opts.filepath, load_parameters); + switch (load_result) { + case Core::SystemResultStatus::Success: + break; // Expected case + case Core::SystemResultStatus::ErrorGetLoader: + LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", state.opts.filepath); + return SDL_APP_FAILURE; + case Core::SystemResultStatus::ErrorLoader: + LOG_CRITICAL(Frontend, "Failed to load ROM!"); + return SDL_APP_FAILURE; + case Core::SystemResultStatus::ErrorNotInitialized: + LOG_CRITICAL(Frontend, "CPUCore not initialized"); + return SDL_APP_FAILURE; + case Core::SystemResultStatus::ErrorVideoCore: + LOG_CRITICAL(Frontend, "Failed to initialize VideoCore!"); + return SDL_APP_FAILURE; + default: + const u16 loader_id = u16(Core::SystemResultStatus::ErrorLoader); + const u16 error_id = u16(load_result) - loader_id; + LOG_CRITICAL(Frontend, + "While attempting to load the ROM requested, an error occurred. Please " + "refer to the Eden wiki for more information or the Eden discord for " + "additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}", + loader_id, error_id, Loader::ResultStatus(error_id)); + return SDL_APP_FAILURE; + } + + if (state.opts.use_multiplayer) { + if (auto member = Network::GetRoomMember().lock()) { + member->BindOnChatMessageReceived(OnMessageReceived); + member->BindOnStatusMessageReceived(OnStatusMessageReceived); + member->BindOnStateChanged(OnStateChanged); + member->BindOnError(OnNetworkError); + LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", state.opts.address, state.opts.port, state.opts.nickname); + member->Join(state.opts.nickname, state.opts.address.c_str(), state.opts.port, 0, Network::NoPreferredIP, state.opts.password); + } else { + LOG_ERROR(Network, "Could not access RoomMember"); + return SDL_APP_FAILURE; + } + } + + // Core is loaded, start the GPU (makes the GPU contexts current to this thread) + state.system.GPU().Start(); + state.system.GetCpuManager().OnGpuReady(); + + if (Settings::values.use_disk_shader_cache.GetValue()) { + state.system.Renderer().ReadRasterizer()->LoadDiskResources( + state.system.GetApplicationProcessProgramID(), std::stop_token{}, + [](VideoCore::LoadCallbackStage, size_t value, size_t total) {}); + } + + // don't do anything, SDL3 already exists for us :D + state.system.RegisterExitCallback([] {}); + void(state.system.Run()); + if (state.system.DebuggerEnabled()) + state.system.InitializeDebugger(); + return SDL_APP_SUCCESS; +} + +/// The magic text at the beginning of a yuzu-room ban list file. +static constexpr char BAN_LIST_MAGIC[] = "YuzuRoom-BanList-1"; +static constexpr char TOKEN_DELIMITER{':'}; + +static void PadToken(std::string& token) { + std::array output{}; + std::array roundtrip{}; + for (size_t i = 0; i < 3; i++) { + EVP_DecodeBlock(output.data(), reinterpret_cast(token.c_str()), token.size()); + EVP_EncodeBlock(output.data(), roundtrip.data(), roundtrip.size()); + if (memcmp(roundtrip.data(), token.data(), token.size()) == 0) { + break; + } + token.push_back('='); + } +} + +static std::string UsernameFromDisplayToken(const std::string& display_token) { + std::size_t outlen = 4 * ((display_token.length() + 2) / 3); + std::array output{}; + EVP_DecodeBlock(output.data(), reinterpret_cast(display_token.c_str()), display_token.length()); + std::string decoded_display_token(reinterpret_cast(&output), outlen); + return decoded_display_token.substr(0, decoded_display_token.find(TOKEN_DELIMITER)); +} + +static std::string TokenFromDisplayToken(const std::string& display_token) { + std::size_t outlen = 4 * ((display_token.length() + 2) / 3); + std::array output{}; + EVP_DecodeBlock(output.data(), reinterpret_cast(display_token.c_str()), display_token.length()); + std::string decoded_display_token(reinterpret_cast(&output), outlen); + return decoded_display_token.substr(decoded_display_token.find(TOKEN_DELIMITER) + 1); +} + +static Network::Room::BanList LoadBanList(const std::string& path) { + std::ifstream file; + Common::FS::OpenFileStream(file, path, std::ios_base::in); + if (!file || file.eof()) { + LOG_ERROR(Network, "Could not open ban list!"); + return {}; + } + std::string magic; + std::getline(file, magic); + if (magic != BAN_LIST_MAGIC) { + LOG_ERROR(Network, "Ban list is not valid!"); + return {}; + } + + // false = username ban list, true = ip ban list + bool ban_list_type = false; + Network::Room::UsernameBanList username_ban_list; + Network::Room::IPBanList ip_ban_list; + while (!file.eof()) { + std::string line; + std::getline(file, line); + line.erase(std::remove(line.begin(), line.end(), '\0'), line.end()); + line = Common::StripSpaces(line); + if (line.empty()) { + // An empty line marks start of the IP ban list + ban_list_type = true; + continue; + } + if (ban_list_type) { + ip_ban_list.emplace_back(line); + } else { + username_ban_list.emplace_back(line); + } + } + + return {username_ban_list, ip_ban_list}; +} + +static void SaveBanList(const Network::Room::BanList& ban_list, const std::string& path) { + std::ofstream file; + Common::FS::OpenFileStream(file, path, std::ios_base::out); + if (!file) { + LOG_ERROR(Network, "Could not save ban list!"); + return; + } + + file << BAN_LIST_MAGIC << "\n"; + + // Username ban list + for (const auto& username : ban_list.first) { + file << username << "\n"; + } + file << "\n"; + + // IP ban list + for (const auto& ip : ban_list.second) { + file << ip << "\n"; + } +} + +static SDL_AppResult ExecuteHeadlessRoom(SdlState& state) { + if (state.opts.room_name.empty()) { + LOG_ERROR(Network, "Room name is empty!"); + return SDL_APP_FAILURE; + } + if (state.opts.preferred_game.empty()) { + LOG_ERROR(Network, "Preferred game is empty!"); + return SDL_APP_FAILURE; + } + if (state.opts.preferred_game_id == 0) { + LOG_WARNING(Network, + "preferred-game-id not set!\n" + "This should get set to allow users to find your room.\n" + "Set with --preferred-game-id id"); + } + if (state.opts.bind_address.empty()) { + LOG_INFO(Network, "Bind address is empty: defaulting to 0.0.0.0"); + } + if (state.opts.ban_list_file.empty()) { + LOG_WARNING(Network, + "Ban list file not set!\n" + "This should get set to load and save room ban list.\n" + "Set with --ban-list-file "); + } + bool announce = true; + if (state.opts.token.empty() && announce) { + announce = false; + LOG_INFO(Network, "Token is empty: Hosting a private room"); + } + if (state.opts.web_api_url.empty() && announce) { + announce = false; + LOG_INFO(Network, "Endpoint url is empty: Hosting a private room"); + } + if (announce) { + if (state.opts.username.empty()) { + LOG_INFO(Network, "Hosting a public room"); + Settings::values.web_api_url = state.opts.web_api_url; + PadToken(state.opts.token); + Settings::values.eden_username = UsernameFromDisplayToken(state.opts.token); + state.opts.username = Settings::values.eden_username.GetValue(); + Settings::values.eden_token = TokenFromDisplayToken(state.opts.token); + } else { + LOG_INFO(Network, "Hosting a public room"); + Settings::values.web_api_url = state.opts.web_api_url; + Settings::values.eden_username = state.opts.username; + Settings::values.eden_token = state.opts.token; + } + } + + // Load the ban list + Network::Room::BanList ban_list; + if (!state.opts.ban_list_file.empty()) { + ban_list = LoadBanList(state.opts.ban_list_file); + } + + std::unique_ptr verify_backend; + if (announce) { +#ifdef ENABLE_WEB_SERVICE + verify_backend = + std::make_unique(Settings::values.web_api_url.GetValue()); +#else + LOG_INFO(Network, + "Eden Web Services is not available with this build: validation is disabled."); + verify_backend = std::make_unique(); +#endif + } else { + verify_backend = std::make_unique(); + } + + Network::Init(); + if (auto room = Network::GetRoom().lock()) { + AnnounceMultiplayerRoom::GameInfo preferred_game_info{ + .name = state.opts.preferred_game, + .id = state.opts.preferred_game_id + }; + if (!room->Create(state.opts.room_name, state.opts.room_description, state.opts.bind_address, u16(state.opts.port), + state.opts.password, state.opts.max_members, state.opts.username, preferred_game_info, + std::move(verify_backend), ban_list)) { + LOG_INFO(Network, "Failed to create room: "); + std::exit(-1); + } + LOG_INFO(Network, "Room is open. Close with Q+Enter..."); + auto announce_session = std::make_unique(); + if (announce) { + announce_session->Start(); + } + while (room->GetState() == Network::Room::State::Open) { + std::string in; + std::cin >> in; + if (in.size() > 0) { + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + if (announce) { + announce_session->Stop(); + } + announce_session.reset(); + // Save the ban list + if (!state.opts.ban_list_file.empty()) { + SaveBanList(room->GetBanList(), state.opts.ban_list_file); + } + room->Destroy(); + } + Network::Shutdown(); + return SDL_APP_FAILURE; +} + extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) { SdlState* state = new SdlState(); @@ -206,21 +612,6 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) { return SDL_APP_FAILURE; } #endif - std::string filepath; - std::optional config_path{}; - std::string program_args; - std::optional selected_user{}; - std::optional override_gdb_port{}; - bool use_multiplayer = false; - bool fullscreen = false; - bool force_null_render = false; - bool force_single_core = false; - std::string nickname{}; - std::string password{}; - std::string address{}; - std::string input_profile{}; - std::optional log_filter{}; - u16 port = Network::DefaultRoomPort; static struct option long_options[] = { // clang-format off @@ -237,36 +628,55 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) { {"null-render", no_argument, 0, 'n'}, {"singlecore", no_argument, 0, 's'}, {"filter", no_argument, 0, 'x'}, + + {"log-file", required_argument, 0, 'l'}, + {"headless", required_argument, 0, 'H'}, + + {"room-name", required_argument, 0, 'N'}, + {"room-description", required_argument, 0, 'D'}, + {"bind-address", required_argument, 0, 'S'}, + {"port", required_argument, 0, 'P'}, + {"max-members", required_argument, 0, 'M'}, + {"password", required_argument, 0, 'W'}, + {"preferred-game", required_argument, 0, 'G'}, + {"preferred-game-id", required_argument, 0, 'I'}, + {"username", optional_argument, 0, 'U'}, + {"token", required_argument, 0, 'T'}, + {"web-api-url", required_argument, 0, 'A'}, + {"ban-list-file", required_argument, 0, 'B'}, + // Entry option + {"room", 0, 0, 0}, {0, 0, 0, 0}, // clang-format on }; + char *endarg = nullptr; while (optind < argc) { int arg = getopt_long(argc, argv, "g:fhvcip::c:u:d:", long_options, &option_index); if (arg != -1) { switch (char(arg)) { case 'd': - override_gdb_port = uint16_t(atoi(optarg)); + state->opts.override_gdb_port = uint16_t(atoi(optarg)); break; case 'c': - config_path = optarg; + state->opts.config_path = optarg; break; case 'f': - fullscreen = true; + state->opts.fullscreen = true; LOG_INFO(Frontend, "Starting in fullscreen mode..."); break; case 'h': PrintHelp(argv[0]); return SDL_APP_FAILURE; case 'g': - filepath = std::string(optarg); + state->opts.filepath = std::string(optarg); break; case 'i': { - input_profile = std::string(optarg); + state->opts.input_profile = std::string(optarg); break; } case 'm': { - use_multiplayer = true; + state->opts.use_multiplayer = true; const std::string str_arg(optarg); // regex to check if the format is nickname:password@ip:port // with optional :password @@ -280,193 +690,113 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) { std::smatch match; std::regex_search(str_arg, match, re); ASSERT(match.size() == 5); - nickname = match[1]; - password = match[2]; - address = match[3]; + state->opts.nickname = match[1]; + state->opts.password = match[2]; + state->opts.address = match[3]; if (!match[4].str().empty()) { - port = u16(std::strtoul(match[4].str().c_str(), nullptr, 0)); + state->opts.port = u16(std::strtoul(match[4].str().c_str(), nullptr, 0)); } std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$"); - if (!std::regex_match(nickname, nickname_re)) { + if (!std::regex_match(state->opts.nickname, nickname_re)) { LOG_ERROR(Frontend, "Nickname is not valid. Must be 4 to 20 alphanumeric characters"); return SDL_APP_FAILURE; } - if (address.empty()) { + if (state->opts.address.empty()) { LOG_ERROR(Frontend, "Address to room must not be empty"); return SDL_APP_FAILURE; } break; } case 'p': - program_args = argv[optind]; - ++optind; + state->opts.program_args.assign(optarg); break; case 'u': - selected_user = atoi(optarg); + state->opts.selected_user = atoi(optarg); break; case 'v': PrintVersion(); return SDL_APP_FAILURE; case 'n': - force_null_render = true; + state->opts.force_null_render = true; break; case 's': - force_single_core = true; + state->opts.force_single_core = true; break; case 'x': - log_filter = argv[optind]; - ++optind; + state->opts.log_filter.emplace(optarg); + break; + // shared + case 'l': + state->opts.log_file.assign(optarg); + break; + case 'H': + state->opts.headless.emplace(true); + break; + // room + case 'N': + state->opts.room_name.assign(optarg); + break; + case 'D': + state->opts.room_description.assign(optarg); + break; + case 'S': + state->opts.bind_address.assign(optarg); + break; + case 'P': { + auto const value = strtoul(optarg, &endarg, 0); + if (value <= USHRT_MAX) { + state->opts.port = value; + } else { + LOG_ERROR(Frontend, "port must be between 0-{}", USHRT_MAX); + } + break; + } + case 'M': { + auto const value = strtoul(optarg, &endarg, 0); + if (value >= 2 && value <= Network::MaxConcurrentConnections) { + state->opts.max_members = value; + } else { + LOG_ERROR(Frontend, "max members must be between 2-{}", value, Network::MaxConcurrentConnections); + } + break; + } + case 'W': + state->opts.password.assign(optarg); + break; + case 'G': + state->opts.preferred_game.assign(optarg); + break; + case 'I': + state->opts.preferred_game_id = strtoull(optarg, &endarg, 16); + break; + case 'U': + state->opts.nickname.assign(optarg); + break; + case 'T': + state->opts.token.assign(optarg); + break; + case 'A': + state->opts.web_api_url.assign(optarg); + break; + case 'B': + state->opts.ban_list_file.assign(optarg); break; } } else { #ifdef _WIN32 - filepath = Common::UTF16ToUTF8(argv_w[optind]); + state->opts.filepath = Common::UTF16ToUTF8(argv_w[optind]); #else - filepath = argv[optind]; + state->opts.filepath = argv[optind]; #endif optind++; } } - SdlConfig config{config_path}; - - // apply the log_filter setting - // the logger was initialized before and doesn't pick up the filter on its own - Common::Log::Filter filter; - filter.ParseFilterString(log_filter.value_or(Settings::values.log_filter.GetValue())); - Common::Log::SetGlobalFilter(filter); - - if (!program_args.empty()) { - Settings::values.program_args = program_args; + if (!state->opts.room_name.empty() || !state->opts.room_description.empty()) { + LOG_INFO(Frontend, "Assuming (headless) room mode"); + return ExecuteHeadlessRoom(*state); } - - if (!input_profile.empty()) { - auto& players = Settings::values.players.GetValue(); - players[0].profile_name = input_profile; - } - - if (selected_user.has_value()) { - Settings::values.current_user = std::clamp(*selected_user, 0, 7); - } - - if (override_gdb_port.has_value()) { - Settings::values.use_gdbstub = true; - Settings::values.gdbstub_port = *override_gdb_port; - } - - if (force_single_core) { - Settings::values.use_multi_core = false; - } - - if (force_null_render) { - Settings::values.renderer_backend = Settings::RendererBackend::Null; - } - -#ifdef _WIN32 - LocalFree(argv_w); -#endif - - if (filepath.empty()) { - LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified"); - return SDL_APP_FAILURE; - } - - state->system.Initialize(); - - InputCommon::InputSubsystem input_subsystem{}; - - // Apply the command line arguments - state->system.ApplySettings(); - - switch (Settings::values.renderer_backend.GetValue()) { -#ifdef HAS_OPENGL - case Settings::RendererBackend::OpenGL_GLSL: - case Settings::RendererBackend::OpenGL_GLASM: - case Settings::RendererBackend::OpenGL_SPIRV: - state->emu_window = std::make_unique(&input_subsystem, state->system, fullscreen); - break; -#endif - case Settings::RendererBackend::Vulkan: - state->emu_window = std::make_unique(&input_subsystem, state->system, fullscreen); - break; - case Settings::RendererBackend::Null: - state->emu_window = std::make_unique(&input_subsystem, state->system, fullscreen); - break; - default: - LOG_CRITICAL(Frontend, "Invalid renderer backend"); - return SDL_APP_FAILURE; - } - -#ifdef _WIN32 - Common::Windows::SetCurrentTimerResolutionToMaximum(); - state->system.CoreTiming().SetTimerResolutionNs(Common::Windows::GetCurrentTimerResolution()); -#endif - - state->system.SetContentProvider(std::make_unique()); - state->system.SetFilesystem(std::make_shared()); - state->system.GetFileSystemController().CreateFactories(*state->system.GetFilesystem()); - state->system.GetUserChannel().clear(); - - Service::AM::FrontendAppletParameters load_parameters{ - .applet_id = Service::AM::AppletId::Application, - }; - const Core::SystemResultStatus load_result = state->system.Load(*state->emu_window, filepath, load_parameters); - switch (load_result) { - case Core::SystemResultStatus::Success: - break; // Expected case - case Core::SystemResultStatus::ErrorGetLoader: - LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath); - return SDL_APP_FAILURE; - case Core::SystemResultStatus::ErrorLoader: - LOG_CRITICAL(Frontend, "Failed to load ROM!"); - return SDL_APP_FAILURE; - case Core::SystemResultStatus::ErrorNotInitialized: - LOG_CRITICAL(Frontend, "CPUCore not initialized"); - return SDL_APP_FAILURE; - case Core::SystemResultStatus::ErrorVideoCore: - LOG_CRITICAL(Frontend, "Failed to initialize VideoCore!"); - return SDL_APP_FAILURE; - default: - const u16 loader_id = u16(Core::SystemResultStatus::ErrorLoader); - const u16 error_id = u16(load_result) - loader_id; - LOG_CRITICAL(Frontend, - "While attempting to load the ROM requested, an error occurred. Please " - "refer to the Eden wiki for more information or the Eden discord for " - "additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}", - loader_id, error_id, Loader::ResultStatus(error_id)); - return SDL_APP_FAILURE; - } - - if (use_multiplayer) { - if (auto member = Network::GetRoomMember().lock()) { - member->BindOnChatMessageReceived(OnMessageReceived); - member->BindOnStatusMessageReceived(OnStatusMessageReceived); - member->BindOnStateChanged(OnStateChanged); - member->BindOnError(OnNetworkError); - LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port, nickname); - member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredIP, password); - } else { - LOG_ERROR(Network, "Could not access RoomMember"); - return SDL_APP_FAILURE; - } - } - - // Core is loaded, start the GPU (makes the GPU contexts current to this thread) - state->system.GPU().Start(); - state->system.GetCpuManager().OnGpuReady(); - - if (Settings::values.use_disk_shader_cache.GetValue()) { - state->system.Renderer().ReadRasterizer()->LoadDiskResources( - state->system.GetApplicationProcessProgramID(), std::stop_token{}, - [](VideoCore::LoadCallbackStage, size_t value, size_t total) {}); - } - - // don't do anything, SDL3 already exists for us :D - state->system.RegisterExitCallback([] {}); - void(state->system.Run()); - if (state->system.DebuggerEnabled()) - state->system.InitializeDebugger(); - return SDL_APP_SUCCESS; + return ExecuteWithGUI(*state); } extern "C" SDL_AppResult SDL_AppIterate(void *appstate) { SdlState *state = (SdlState *)appstate; diff --git a/src/yuzu_room_standalone/CMakeLists.txt b/src/yuzu_room_standalone/CMakeLists.txt deleted file mode 100644 index 125c7c911d..0000000000 --- a/src/yuzu_room_standalone/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -# SPDX-License-Identifier: GPL-3.0-or-later - -add_executable(yuzu_room_standalone yuzu_room_standalone.cpp) - -set_target_properties(yuzu_room_standalone PROPERTIES OUTPUT_NAME "eden-room") - -target_link_libraries(yuzu_room_standalone PRIVATE yuzu-room) - -if(UNIX AND NOT APPLE) - install(TARGETS yuzu_room_standalone RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") -endif() - -if (YUZU_STATIC_ROOM) - target_link_options(yuzu_room_standalone PRIVATE "-static") -endif() - -create_target_directory_groups(yuzu_room_standalone) diff --git a/src/yuzu_room_standalone/main.cpp b/src/yuzu_room_standalone/main.cpp deleted file mode 100644 index 3129fb9dd9..0000000000 --- a/src/yuzu_room_standalone/main.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include - -using namespace std; - -int main() -{ - cout << "Hello World!" << endl; - return 0; -} diff --git a/src/yuzu_room_standalone/yuzu_room_standalone.cpp b/src/yuzu_room_standalone/yuzu_room_standalone.cpp deleted file mode 100644 index 3063a2962b..0000000000 --- a/src/yuzu_room_standalone/yuzu_room_standalone.cpp +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "dedicated_room/yuzu_room.h" - -int main(int argc, char* argv[]) -{ - LaunchRoom(argc, argv, false); -}