From 6d8ae54a1ada79ec7929ec48b10e5efaafddaa5b Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Mon, 15 Jun 2026 09:34:03 -0700 Subject: [PATCH] runtime: skip-launcher toggle (mirrors snesrecomp) Add a persistent skip-launcher mechanism so the runtime can boot straight into the game without the GUI launcher window (removes the dismiss round- trip for scripted/debug runs; the debug server then comes up immediately). Mirrors snesrecomp's SkipLauncher: - UserSettings.skip_launcher (config_loader.{h,cpp}): persisted under [launcher] skip_launcher in settings.toml; round-trips through a Launch (seeded before the window runs) so it is never silently dropped. - main.cpp: --launcher forces the GUI back on even when skip_launcher is set; --no-launcher and the existing PSX_NO_LAUNCHER=1 env force it off. Precedence: --launcher wins; else skip if env / --no-launcher / setting. Co-Authored-By: Claude Opus 4.8 (1M context) --- recompiler/src/config_loader.cpp | 8 ++++++++ recompiler/src/config_loader.h | 4 ++++ runtime/src/main.cpp | 28 +++++++++++++++++++++++++--- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/recompiler/src/config_loader.cpp b/recompiler/src/config_loader.cpp index 82493d3e..ff13e931 100644 --- a/recompiler/src/config_loader.cpp +++ b/recompiler/src/config_loader.cpp @@ -517,6 +517,12 @@ UserSettings load_user_settings(const fs::path& path) { s.spu_hq = toml::find(a, "spu_hq"); s.has_spu_hq = true; }); } + if (doc.contains("launcher")) { + const toml::value& l = toml::find(doc, "launcher"); + if (l.contains("skip_launcher")) try_get([&]{ + s.skip_launcher = toml::find(l, "skip_launcher"); s.has_skip_launcher = true; + }); + } if (doc.contains("bios")) { const toml::value& b = toml::find(doc, "bios"); if (b.contains("path")) try_get([&]{ @@ -611,6 +617,8 @@ bool save_user_settings(const fs::path& path, const UserSettings& s) { f << "\n[audio]\n"; if (s.has_spu_hq) f << "spu_hq = " << (s.spu_hq ? "true" : "false") << "\n"; + if (s.has_skip_launcher) + f << "\n[launcher]\nskip_launcher = " << (s.skip_launcher ? "true" : "false") << "\n"; if (s.has_bios_path) f << "\n[bios]\npath = \"" << fwd(s.bios_path) << "\"\n"; if (s.has_disc_path) diff --git a/recompiler/src/config_loader.h b/recompiler/src/config_loader.h index 231601a4..f23d77f2 100644 --- a/recompiler/src/config_loader.h +++ b/recompiler/src/config_loader.h @@ -226,6 +226,10 @@ struct UserSettings { bool has_texture_filter = false; int texture_filter = 0; // 0=nearest,1=bilinear bool has_screen_kind = false; int screen_kind = 0; // 0..3 (ScreenKind) bool has_auto_skip_fmv = false; bool auto_skip_fmv = false; // skip FMVs + // [launcher] — when true, boot straight into the game and skip the GUI + // launcher window (mirrors snesrecomp's SkipLauncher). Overridable per-run: + // `--launcher` forces the GUI back on; `PSX_NO_LAUNCHER=1` forces it off. + bool has_skip_launcher = false; bool skip_launcher = false; // [audio] bool has_spu_hq = false; bool spu_hq = false; // [bios] / [disc] / [memcard] diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index f5038339..5d78e9c6 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -1341,11 +1341,18 @@ int main(int argc, char** argv) { const char* game_config_path = nullptr; const char* disc_override_path = nullptr; bool bios_from_cli = false; /* CLI --bios/positional wins over settings.toml */ + /* Launcher overrides (mirrors snesrecomp): --launcher forces the GUI back on + * even when [launcher] skip_launcher = true is set; --no-launcher (and the + * PSX_NO_LAUNCHER env) forces it off. --launcher wins if both are given. */ + bool force_launcher = false; + bool force_no_launcher = false; /* Parse args. * --bios override the compile-time BIOS path * --game load a game config (single source of truth for * disc / memcard / window title / debug port) * --disc override the game config disc path + * --launcher force the GUI launcher (overrides skip_launcher) + * --no-launcher skip the GUI launcher (boot straight in) * deprecated alias for --bios * No --memcard-dir / --game-root flags: those are config-driven. */ for (int i = 1; i < argc; i++) { @@ -1356,6 +1363,10 @@ int main(int argc, char** argv) { game_config_path = argv[++i]; } else if (std::strcmp(argv[i], "--disc") == 0 && i + 1 < argc) { disc_override_path = argv[++i]; + } else if (std::strcmp(argv[i], "--launcher") == 0) { + force_launcher = true; + } else if (std::strcmp(argv[i], "--no-launcher") == 0) { + force_no_launcher = true; } else if (argv[i][0] != '-') { bios_path = argv[i]; bios_from_cli = true; @@ -1462,12 +1473,14 @@ int main(int argc, char** argv) { * bundled game.toml. Any field present there overrides the config value; * the command line (--bios/--disc) still wins over the file. Absent => * fall through to game.toml. The file is the launcher's persistence. */ + bool skip_launcher_setting = false; /* [launcher] skip_launcher from settings.toml */ std::string settings_bios_storage; /* must outlive resolve_bios_for_runtime */ { std::filesystem::path settings_path = exe_dir_from_argv(argv[0]) / "settings.toml"; const PSXRecompV4::UserSettings us = PSXRecompV4::load_user_settings(settings_path); + if (us.has_skip_launcher) skip_launcher_setting = us.skip_launcher; if (us.has_renderer) g_video_renderer = us.renderer; if (us.has_supersampling) g_video_scale = us.supersampling; if (us.has_window_width) g_video_win_w = us.window_width; @@ -1502,9 +1515,16 @@ int main(int argc, char** argv) { * boots. Seeded with the effective settings (game.toml ∪ settings.toml); * on LAUNCH the user's choices are persisted to settings.toml and applied. * The launcher window/context is fully torn down before the emulator's own - * window is created, so the emulator boot path below is untouched. Skipped - * via PSX_NO_LAUNCHER=1 (e.g. CI / scripted runs). */ - if (!std::getenv("PSX_NO_LAUNCHER")) { + * window is created, so the emulator boot path below is untouched. + * + * Skip the GUI (boot straight in) when ANY of: PSX_NO_LAUNCHER=1 env, + * --no-launcher, or the persisted [launcher] skip_launcher setting — unless + * --launcher forces it back on (mirrors snesrecomp's SkipLauncher / --launcher). + * This removes the dismiss-the-launcher round-trip for scripted/debug runs. */ + const bool want_launcher = + force_launcher || + (!std::getenv("PSX_NO_LAUNCHER") && !force_no_launcher && !skip_launcher_setting); + if (want_launcher) { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) == 0) { PSXRecompV4::UserSettings seed; seed.renderer = g_video_renderer; seed.has_renderer = true; @@ -1514,6 +1534,7 @@ int main(int argc, char** argv) { seed.screen_kind = g_video_screen; seed.has_screen_kind = true; seed.auto_skip_fmv = (g_auto_skip_fmv != 0); seed.has_auto_skip_fmv = true; seed.spu_hq = g_audio_spu_hq; seed.has_spu_hq = true; + seed.skip_launcher = skip_launcher_setting; seed.has_skip_launcher = true; if (bios_path && bios_path[0]) { seed.bios_path = bios_path; seed.has_bios_path = true; } if (!resolved_disc.empty()) { seed.disc_path = resolved_disc; seed.has_disc_path = true; } seed.memcard_dir = memcard_dir; seed.has_memcard_dir = true; @@ -1576,6 +1597,7 @@ int main(int argc, char** argv) { g_video_screen = seed.screen_kind; g_auto_skip_fmv = seed.auto_skip_fmv ? 1 : 0; g_audio_spu_hq = seed.spu_hq; + skip_launcher_setting = seed.skip_launcher; if (seed.has_bios_path) { settings_bios_storage = seed.bios_path.string(); bios_path = settings_bios_storage.c_str();