aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/user/CommandLine.md50
-rw-r--r--src/core/CMakeLists.txt5
-rw-r--r--src/core/launch_params.cpp205
-rw-r--r--src/core/launch_params.h43
-rw-r--r--src/yuzu/main_window.cpp86
-rw-r--r--src/yuzu_cmd/CMakeLists.txt3
-rw-r--r--src/yuzu_cmd/yuzu.cpp204
7 files changed, 317 insertions, 279 deletions
diff --git a/docs/user/CommandLine.md b/docs/user/CommandLine.md
index b8d71ac61f..a63a41a27c 100644
--- a/docs/user/CommandLine.md
+++ b/docs/user/CommandLine.md
@@ -1,32 +1,44 @@
# User Handbook - Command Line
-There are two main applications, an SDL-based app (`eden-cli`) and a Qt based app (`eden`); both accept command line arguments.
-
-## eden
+There are two main applications, an SDL-based app (`eden-cli`) and a Qt based app (`eden`); both accept the same command line arguments.
- `./eden <path>`: Running with a single argument and nothing else, will make the emulator look for the given file and load it, this behavior is similar to `eden-cli`; allows dragging and dropping games into the application.
-- `-g <path>`: Alternate way to specify what to load, overrides. However let it be noted that arguments that use `-` will be treated as options/ignored, if your game, for some reason, starts with `-`, in order to safely handle it you may need to specify it as an argument.
-- `-f`: Use fullscreen.
-- `-u <number>`: Select the index of the user to load as.
-- `-input-profile <name>`: Specifies input profile name to use (for player #0 only).
-- `-qlaunch`: Launch QLaunch.
-- `-hlaunch`: Launch homebrew launcher `nx-hbloader`.
- - Requires a copy of Atmosphere to be extracted onto `sdmc`.
- - This is a shorthand for `<eden folder>/sdmc/atmosphere/hbl.nsp`.
-- `-setup`: Launch setup applet.
-
-## eden-cli
-
- `--debug/-d`: Enter debug mode, allow gdb stub at port `1234`
- `--config/-c`: Specify alternate configuration file.
-- `--fullscreen/-f`: Set fullscreen.
+- `--fullscreen/-f`: Use fullscreen.
- `--help/-h`: Display help.
-- `--game/-g`: Specify the game to run.
+- `--game/-g <path>`: Alternate way to specify what to load, overrides. However let it be noted that arguments that use `-` will be treated as options/ignored, if your game, for some reason, starts with `-`, in order to safely handle it you may need to specify it as an argument.
- `--multiplayer/-m`: Specify multiplayer options.
- `--program/-p`: Specify the program arguments to pass (optional).
-- `--user/-u`: Specify the user index.
+- `--user/-u <number>`: Specify the user index.
- `--version/-v`: Display version and quit.
-- `--input-profile/-i`: Specifies input profile name to use (for player #0 only).
+- `--input-profile/-i <name>`: 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.
+
+Only the Qt frontend supports the following arguments:
+
+- `-qlaunch`: Launch QLaunch.
+- `-hlaunch`: Launch homebrew launcher `nx-hbloader`.
+ - Requires a copy of Atmosphere to be extracted onto `sdmc`.
+ - This is a shorthand for `<eden folder>/sdmc/atmosphere/hbl.nsp`.
+- `-setup`: Launch setup applet.
+
+`eden` (provided with `--room`), and `eden-room` supports the following options as well:
+
+- `-n/--room-name`: The name of the room.
+- `-d/--room-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.
+- `-l/--log-file`: The file for storing the room log.
+- `-h/--help`: Display this help and exit.
+- `-v/--version`: Output version information and exit.
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 5f9e9bd654..31f9931d3a 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -23,6 +23,8 @@ add_library(core STATIC
core_timing.h
cpu_manager.cpp
cpu_manager.h
+ launch_params.cpp
+ launch_params.h
crypto/aes_util.cpp
crypto/aes_util.h
crypto/ctr_encryption_layer.cpp
@@ -1203,6 +1205,9 @@ endif()
target_include_directories(core PRIVATE ${OPUS_INCLUDE_DIRS})
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz)
+if (MSVC)
+ target_link_libraries(core PRIVATE getopt)
+endif()
if (BOOST_NO_HEADERS)
target_link_libraries(core PUBLIC Boost::container Boost::heap Boost::asio Boost::process Boost::crc)
diff --git a/src/core/launch_params.cpp b/src/core/launch_params.cpp
new file mode 100644
index 0000000000..a0483226ec
--- /dev/null
+++ b/src/core/launch_params.cpp
@@ -0,0 +1,205 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <regex>
+#include <cctype>
+
+#include "common/assert.h"
+#include "common/logging.h"
+#include "common/settings.h"
+#include "common/string_util.h"
+#include "common/scm_rev.h"
+#include "core/core.h"
+#include "core/hle/service/acc/profile_manager.h"
+#include "core/launch_params.h"
+
+#undef _UNICODE
+#include <getopt.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#endif
+
+namespace Core {
+LaunchParams ParseLaunchParams(Core::System& system, int argc, char *argv[], wchar_t *argv_w[]) noexcept {
+ LaunchParams p{};
+
+ int option_index = 0;
+ static struct option long_options[] = {
+ // clang-format off
+ {"debug", no_argument, 0, 'd'},
+ {"config", required_argument, 0, 'c'},
+ {"fullscreen", no_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {"game", required_argument, 0, 'g'},
+ {"multiplayer", required_argument, 0, 'm'},
+ {"program", optional_argument, 0, 'p'},
+ {"user", required_argument, 0, 'u'},
+ {"version", no_argument, 0, 'v'},
+ {"input-profile", no_argument, 0, 'i'},
+ {"null-render", no_argument, 0, 'n'},
+ {"singlecore", no_argument, 0, 's'},
+ {"filter", no_argument, 0, 'x'},
+ {0, 0, 0, 0},
+ // clang-format on
+ };
+
+ while (optind < argc) {
+ if (int arg = getopt_long(argc, argv, "dc:fhg:m:p:u:vinsx", long_options, &option_index); arg != -1) {
+ switch (char(arg)) {
+ case 'd':
+ p.override_gdb_port = uint16_t(atoi(optarg));
+ break;
+ case 'c':
+ p.config_path = optarg;
+ break;
+ case 'f':
+ p.fullscreen = true;
+ LOG_INFO(Frontend, "Starting in fullscreen mode...");
+ break;
+ case 'h':
+ p.print_help = true;
+ break;
+ case 'g':
+ p.filepath = std::string(optarg);
+ break;
+ case 'i': {
+ p.input_profile = std::string(optarg);
+ break;
+ }
+ case 'm': {
+ p.use_multiplayer = true;
+ const std::string str_arg(optarg);
+ // regex to check if the format is nickname:password@ip:port
+ // with optional :password
+ const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$");
+ if (std::regex_match(str_arg, re)) {
+ std::smatch match;
+ std::regex_search(str_arg, match, re);
+ ASSERT(match.size() == 5);
+ p.nickname = match[1];
+ p.password = match[2];
+ p.address = match[3];
+ if (!match[4].str().empty()) {
+ p.port = u16(std::strtoul(match[4].str().c_str(), nullptr, 0));
+ }
+ std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
+ ASSERT(std::regex_match(p.nickname, nickname_re) && "Nickname is not valid. Must be 4 to 20 alphanumeric characters");
+ ASSERT(!p.address.empty() && "Address is empty");
+ }
+ break;
+ }
+ case 'p':
+ p.program_args = argv[optind];
+ ++optind;
+ break;
+ case 'u': {
+ // Launch game with a specific user
+ bool argument_ok = isdigit(optarg[0]);
+ p.selected_user = atoi(optarg);
+ if (!argument_ok) {
+ // try to look it up by username, only finds the first username that matches.
+ auto const user_idx = system.GetProfileManager().GetUserIndex(optarg);
+ if (user_idx != std::nullopt) {
+ p.selected_user = user_idx.value();
+ } else {
+ LOG_ERROR(Frontend, "Invalid user argument '{}'", optarg);
+ break;
+ }
+ }
+ if (system.GetProfileManager().UserExistsIndex(*p.selected_user)) {
+ Settings::values.current_user = s32(*p.selected_user);
+ } else {
+ LOG_ERROR(Frontend, "Selected user {} doesn't exist", *p.selected_user);
+ }
+ break;
+ }
+ case 'v':
+ p.print_version = true;
+ break;
+ case 'n':
+ p.force_null_render = true;
+ break;
+ case 's':
+ p.force_single_core = true;
+ break;
+ case 'x':
+ p.log_filter = argv[optind];
+ ++optind;
+ break;
+ }
+ } else {
+ // only kept due to shortcuts made by Qt frontend which use "-qlaunch"
+ if (strcmp(argv[optind], "-hlaunch") == 0) {
+ p.launch_hlaunch = true;
+ } else if (strcmp(argv[optind], "-qlaunch") == 0) {
+ p.launch_qlaunch = true;
+ } else if (strcmp(argv[optind], "-setup") == 0) {
+ p.launch_setup = true;
+ } else {
+ // qt feeds utf8 data, sdl frontend feeds raw utf16 data
+#ifdef _WIN32
+ p.filepath = argv_w != nullptr
+ ? Common::UTF16ToUTF8(argv_w[optind])
+ : argv[optind];
+#else
+ p.filepath = argv[optind];
+#endif
+ }
+ optind++;
+ }
+ }
+ p.argv0 = argv[0];
+ return p;
+}
+
+void ApplyLaunchParams(LaunchParams const& lp) noexcept {
+ // 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(lp.log_filter.value_or(Settings::values.log_filter.GetValue()));
+ Common::Log::SetGlobalFilter(filter);
+
+ if (!lp.program_args.empty()) {
+ Settings::values.program_args = lp.program_args;
+ }
+ if (!lp.input_profile.empty()) {
+ auto& players = Settings::values.players.GetValue();
+ players[0].profile_name = lp.input_profile;
+ }
+ if (lp.selected_user.has_value()) {
+ Settings::values.current_user = std::clamp(*lp.selected_user, 0, 7);
+ }
+ if (lp.override_gdb_port.has_value()) {
+ Settings::values.use_gdbstub = true;
+ Settings::values.gdbstub_port = *lp.override_gdb_port;
+ }
+ if (lp.force_single_core) {
+ Settings::values.use_multi_core = false;
+ }
+ if (lp.force_null_render) {
+ Settings::values.renderer_backend = Settings::RendererBackend::Null;
+ }
+
+ if (lp.print_version) {
+ LOG_INFO(Frontend, "Eden {} {}", Common::g_scm_branch, Common::g_scm_desc);
+ }
+ if (lp.print_help) {
+ LOG_INFO(Frontend,
+ "Usage: {}"
+ " [options] <filename>\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",
+ lp.argv0
+ );
+ }
+}
+
+}
diff --git a/src/core/launch_params.h b/src/core/launch_params.h
new file mode 100644
index 0000000000..4da30fc1c2
--- /dev/null
+++ b/src/core/launch_params.h
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <string>
+#include <optional>
+#include "common/common_types.h"
+#include "network/room.h"
+
+namespace Core {
+
+class System;
+struct LaunchParams {
+ std::string argv0{};
+ std::string filepath{};
+ std::string nickname{};
+ std::string password{};
+ std::string address{};
+ std::string input_profile{};
+ std::string program_args{};
+ std::optional<std::string> config_path{};
+ std::optional<int> selected_user{};
+ std::optional<u16> override_gdb_port{};
+ std::optional<std::string> log_filter{};
+ u16 port = Network::DefaultRoomPort;
+ bool use_multiplayer = false;
+ bool fullscreen = false;
+ bool force_null_render = false;
+ bool force_single_core = false;
+ // print
+ bool print_version = false;
+ bool print_help = false;
+ // qt
+ bool launch_hlaunch = false;
+ bool launch_qlaunch = false;
+ bool launch_setup = false;
+};
+
+LaunchParams ParseLaunchParams(Core::System& system, int argc, char *argv[], wchar_t *argv_w[]) noexcept;
+void ApplyLaunchParams(LaunchParams const& lp) noexcept;
+
+}
diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp
index 1d579239a1..e2807e80ff 100644
--- a/src/yuzu/main_window.cpp
+++ b/src/yuzu/main_window.cpp
@@ -3,6 +3,7 @@
// Static Qt on macOS doesn't use Vulkan
// Other platforms do, and thus have conflicting VulkanMemoryAllocator symbols
+#include "core/launch_params.h"
#if defined(QT_STATICPLUGIN) && !defined(__APPLE__)
#undef VMA_IMPLEMENTATION
#endif
@@ -515,79 +516,28 @@ MainWindow::MainWindow(bool has_broken_vulkan)
// to prevent the UI from blowing up.
UpdateUITheme();
- QStringList args = QApplication::arguments();
+ if (QStringList args = QApplication::arguments(); args.size() >= 2) {
+ QList<QByteArray> qba{};
+ for (auto const& e : args)
+ qba.push_back(e.toUtf8());
+ std::vector<char*> args_cstr{};
+ for (auto& e : qba)
+ args_cstr.push_back(e.data());
- if (args.size() < 2) {
- return;
- }
+ auto const lp = Core::ParseLaunchParams(*QtCommon::system, args_cstr.size(), args_cstr.data(), nullptr);
- QString game_path;
- bool should_launch_qlaunch = false;
- bool should_launch_hlaunch = false;
- bool should_launch_setup = false;
- bool has_gamepath = false;
- bool is_fullscreen = false;
-
- // Preserves drag/drop functionality
- for (int i = 1; i < args.size(); ++i) {
- if (args[i] == QStringLiteral("-f")) {
- // Launch game in fullscreen mode
- is_fullscreen = true;
- } else if (args[i] == QStringLiteral("-u") && i < args.size() - 1) {
- // Launch game with a specific user
- int user_arg_idx = ++i;
- bool argument_ok;
- std::size_t selected_user = args[user_arg_idx].toUInt(&argument_ok);
- if (!argument_ok) {
- // try to look it up by username, only finds the first username that matches.
- std::string const user_arg_str = args[user_arg_idx].toStdString();
- auto const user_idx =
- QtCommon::system->GetProfileManager().GetUserIndex(user_arg_str);
- if (user_idx != std::nullopt) {
- selected_user = user_idx.value();
- } else {
- LOG_ERROR(Frontend, "Invalid user argument '{}'", user_arg_str);
- continue;
- }
- }
- if (QtCommon::system->GetProfileManager().UserExistsIndex(selected_user)) {
- Settings::values.current_user = s32(selected_user);
- user_flag_cmd_line = true;
- } else {
- LOG_ERROR(Frontend, "Selected user {} doesn't exist", selected_user);
- }
- } else if (args[i] == QStringLiteral("-g") && i < args.size() - 1) {
- // Launch game at path
- game_path = args[++i];
- has_gamepath = true;
- } else if (args[i] == QStringLiteral("-input-profile") && i < args.size() - 1) {
- auto& players = Settings::values.players.GetValue();
- players[0].profile_name = args[++i].toStdString();
- } else if (args[i] == QStringLiteral("-qlaunch")) {
- should_launch_qlaunch = true;
- } else if (args[i] == QStringLiteral("-hlaunch")) {
- should_launch_hlaunch = true;
- } else if (args[i] == QStringLiteral("-setup")) {
- should_launch_setup = true;
- } else {
- game_path = args[i];
- has_gamepath = true;
+ // Override fullscreen setting if gamepath or argument is provided
+ if (!lp.filepath.empty() || lp.fullscreen) {
+ ui->action_Fullscreen->setChecked(lp.fullscreen);
}
- }
- // Override fullscreen setting if gamepath or argument is provided
- if (has_gamepath || is_fullscreen) {
- ui->action_Fullscreen->setChecked(is_fullscreen);
- }
-
- if (should_launch_setup) {
- LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Starter), std::nullopt);
- } else {
- if (!game_path.isEmpty()) {
- BootGame(game_path, ApplicationAppletParameters());
- } else if (should_launch_qlaunch) {
+ if (!lp.filepath.empty()) {
+ BootGame(QString::fromStdString(lp.filepath), ApplicationAppletParameters());
+ } else if (lp.launch_setup) {
+ LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Starter), std::nullopt);
+ } else if (lp.launch_qlaunch) {
LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::QLaunch), std::nullopt);
- } else if (should_launch_hlaunch) {
+ } else if (lp.launch_hlaunch) {
std::filesystem::path const sd_dir =
Common::FS::GetEdenPathString(Common::FS::EdenPath::SDMCDir);
auto const hbl_path = (sd_dir / "atmosphere" / "hbl.nsp").string();
diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt
index 09a9bd6da1..17a1545167 100644
--- a/src/yuzu_cmd/CMakeLists.txt
+++ b/src/yuzu_cmd/CMakeLists.txt
@@ -42,9 +42,6 @@ target_link_libraries(yuzu-cmd PRIVATE common core input_common frontend_common
if (ENABLE_OPENGL)
target_link_libraries(yuzu-cmd PRIVATE glad)
endif()
-if (MSVC)
- target_link_libraries(yuzu-cmd PRIVATE getopt)
-endif()
target_link_libraries(yuzu-cmd PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
create_resource("../../dist/eden.bmp" "yuzu_cmd/yuzu_icon.h" "yuzu_icon")
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index a161418289..473a5b1014 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -10,6 +10,7 @@
#include <regex>
#include <string>
#include "common/settings_enums.h"
+#include "core/launch_params.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
@@ -52,12 +53,6 @@
#include "common/windows/timer_resolution.h"
#endif
-#undef _UNICODE
-#include <getopt.h>
-#ifndef _MSC_VER
-#include <unistd.h>
-#endif
-
#ifdef _WIN32
extern "C" {
// tells Nvidia and AMD drivers to use the dedicated GPU by default on laptops with switchable
@@ -67,25 +62,6 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#endif
-static void PrintHelp(const char* argv0) {
- std::cout << "Usage: " << argv0
- << " [options] <filename>\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";
-}
-
-static void PrintVersion() {
- std::cout << "Eden " << Common::g_scm_branch << " " << Common::g_scm_desc << std::endl;
-}
-
static void OnStateChanged(const Network::RoomMember::State& state) {
switch (state) {
case Network::RoomMember::State::Idle:
@@ -208,168 +184,18 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
LOG_CRITICAL(Frontend, "Failed to get command line arguments");
return SDL_APP_FAILURE;
}
-#endif
- std::string filepath;
- std::optional<std::string> config_path{};
- std::string program_args;
- std::optional<int> selected_user{};
- std::optional<u16> 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<std::string> log_filter{};
- u16 port = Network::DefaultRoomPort;
-
- static struct option long_options[] = {
- // clang-format off
- {"debug", no_argument, 0, 'd'},
- {"config", required_argument, 0, 'c'},
- {"fullscreen", no_argument, 0, 'f'},
- {"help", no_argument, 0, 'h'},
- {"game", required_argument, 0, 'g'},
- {"multiplayer", required_argument, 0, 'm'},
- {"program", optional_argument, 0, 'p'},
- {"user", required_argument, 0, 'u'},
- {"version", no_argument, 0, 'v'},
- {"input-profile", no_argument, 0, 'i'},
- {"null-render", no_argument, 0, 'n'},
- {"singlecore", no_argument, 0, 's'},
- {"filter", no_argument, 0, 'x'},
- {0, 0, 0, 0},
- // clang-format on
- };
-
- 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));
- break;
- case 'c':
- config_path = optarg;
- break;
- case 'f':
- 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);
- break;
- case 'i': {
- input_profile = std::string(optarg);
- break;
- }
- case 'm': {
- use_multiplayer = true;
- const std::string str_arg(optarg);
- // regex to check if the format is nickname:password@ip:port
- // with optional :password
- const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$");
- if (!std::regex_match(str_arg, re)) {
- std::cout << "Wrong format for option --multiplayer\n";
- PrintHelp(argv[0]);
- return SDL_APP_FAILURE;
- }
-
- std::smatch match;
- std::regex_search(str_arg, match, re);
- ASSERT(match.size() == 5);
- nickname = match[1];
- password = match[2];
- address = match[3];
- if (!match[4].str().empty()) {
- 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)) {
- LOG_ERROR(Frontend, "Nickname is not valid. Must be 4 to 20 alphanumeric characters");
- return SDL_APP_FAILURE;
- }
- if (address.empty()) {
- LOG_ERROR(Frontend, "Address to room must not be empty");
- return SDL_APP_FAILURE;
- }
- break;
- }
- case 'p':
- program_args = argv[optind];
- ++optind;
- break;
- case 'u':
- selected_user = atoi(optarg);
- break;
- case 'v':
- PrintVersion();
- return SDL_APP_FAILURE;
- case 'n':
- force_null_render = true;
- break;
- case 's':
- force_single_core = true;
- break;
- case 'x':
- log_filter = argv[optind];
- ++optind;
- break;
- }
- } else {
-#ifdef _WIN32
- filepath = Common::UTF16ToUTF8(argv_w[optind]);
#else
- filepath = argv[optind];
+ wchar_t **argv_w = nullptr;
#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 (!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;
- }
-
+ Core::LaunchParams lp = Core::ParseLaunchParams(state->system, argc, argv, argv_w);
#ifdef _WIN32
LocalFree(argv_w);
#endif
+ SdlConfig config{lp.config_path};
+
+ Core::ApplyLaunchParams(lp);
- if (filepath.empty()) {
+ if (lp.filepath.empty()) {
LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
return SDL_APP_FAILURE;
}
@@ -386,14 +212,14 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
case Settings::RendererBackend::OpenGL_GLSL:
case Settings::RendererBackend::OpenGL_GLASM:
case Settings::RendererBackend::OpenGL_SPIRV:
- state->emu_window = std::make_unique<EmuWindow_SDL3_GL>(&input_subsystem, state->system, fullscreen);
+ state->emu_window = std::make_unique<EmuWindow_SDL3_GL>(&input_subsystem, state->system, lp.fullscreen);
break;
#endif
case Settings::RendererBackend::Vulkan:
- state->emu_window = std::make_unique<EmuWindow_SDL3_VK>(&input_subsystem, state->system, fullscreen);
+ state->emu_window = std::make_unique<EmuWindow_SDL3_VK>(&input_subsystem, state->system, lp.fullscreen);
break;
case Settings::RendererBackend::Null:
- state->emu_window = std::make_unique<EmuWindow_SDL3_Null>(&input_subsystem, state->system, fullscreen);
+ state->emu_window = std::make_unique<EmuWindow_SDL3_Null>(&input_subsystem, state->system, lp.fullscreen);
break;
default:
LOG_CRITICAL(Frontend, "Invalid renderer backend");
@@ -413,12 +239,12 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
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);
+ const Core::SystemResultStatus load_result = state->system.Load(*state->emu_window, lp.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);
+ LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", lp.filepath);
return SDL_APP_FAILURE;
case Core::SystemResultStatus::ErrorLoader:
LOG_CRITICAL(Frontend, "Failed to load ROM!");
@@ -440,14 +266,14 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
return SDL_APP_FAILURE;
}
- if (use_multiplayer) {
+ if (lp.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);
+ LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", lp.address, lp.port, lp.nickname);
+ member->Join(lp.nickname, lp.address.c_str(), lp.port, 0, Network::NoPreferredIP, lp.password);
} else {
LOG_ERROR(Network, "Could not access RoomMember");
return SDL_APP_FAILURE;