From 278f411dad6e8a52747994888f8778b277f6d953 Mon Sep 17 00:00:00 2001 From: lizzie Date: Wed, 23 Sep 2026 14:51:51 +0200 Subject: [frontend] Unify cmdline parsing (#4424) Addresses #4383. Makes it so both the Qt and SDL frontends now share the same options (bar `-hlaunch` and `-qlaunch`). Signed-off-by: lizzie - [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions). - [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md) - [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability. ------------------- Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4424 Reviewed-by: MaranBr Reviewed-by: Maufeat --- src/core/launch_params.cpp | 205 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 src/core/launch_params.cpp (limited to 'src/core/launch_params.cpp') 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 +#include + +#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 +#ifndef _MSC_VER +#include +#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] \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 + ); + } +} + +} -- cgit v1.2.3