From 374bbc928ddd415989441fe8a9940f19511a3b88 Mon Sep 17 00:00:00 2001 From: jdflyer Date: Sun, 28 Jun 2026 23:35:28 -0700 Subject: [PATCH 1/3] Add command line arguments for --develop, --load-save, and --stage --- include/dusk/main.h | 2 ++ src/d/d_s_logo.cpp | 79 +++++++++++++++++++++++++++++++++++++++++- src/m_Do/m_Do_main.cpp | 27 ++++++++++++--- 3 files changed, 103 insertions(+), 5 deletions(-) diff --git a/include/dusk/main.h b/include/dusk/main.h index 0a2be8734d..3913ae53dc 100644 --- a/include/dusk/main.h +++ b/include/dusk/main.h @@ -9,6 +9,8 @@ extern bool IsRunning; extern bool IsShuttingDown; extern bool IsGameLaunched; extern bool RestartRequested; +extern uint8_t SaveRequested; +extern std::string StageRequested; extern std::filesystem::path ConfigPath; extern std::filesystem::path CachePath; diff --git a/src/d/d_s_logo.cpp b/src/d/d_s_logo.cpp index 570dfac577..9115fc5b9b 100644 --- a/src/d/d_s_logo.cpp +++ b/src/d/d_s_logo.cpp @@ -23,8 +23,12 @@ #include "m_Do/m_Do_main.h" #include "JSystem/JUtility/JUTConsole.h" +#ifdef TARGET_PC #include "dusk/logging.h" #include "dusk/version.hpp" +#include "dusk/main.h" +#include "m_Do/m_Do_MemCard.h" +#endif #if !PLATFORM_GCN #include @@ -757,7 +761,80 @@ void dScnLogo_c::nextSceneChange() { if (!mDoRst::isReset()) { if (!isOpeningCut()) { - dComIfG_changeOpeningScene(this, fpcNm_OPENING_SCENE_e); +#ifdef TARGET_PC + // If we are requesting a save from the command line, load it here and set the scene to play instead of loading the LOGO SCENE + if (dusk::SaveRequested >= 1 && dusk::SaveRequested <= 3) { + u8 buf[SAVEDATA_SIZE * 3]; + mDoMemCd_Load(); + uint8_t status; + do { + status = mDoMemCd_LoadSync(buf, sizeof(buf), 0); + // Wait until the card is loaded + } while (status == 0); + + if (status == 1) { + dComIfGs_setCardToMemory(buf, dusk::SaveRequested - 1); + } else { + dComIfGs_init(); + } + + dComIfGs_setNoFile(dusk::SaveRequested); + dComIfGs_setDataNum(dusk::SaveRequested-1); + + dComIfGs_gameStart(); + + fopScnM_ChangeReq(this, fpcNm_PLAY_SCENE_e, 0, 30); + + dKy_clear_game_init(); + dComIfGs_resetDan(); + dComIfGs_setRestartRoomParam(0); + + DuskLog.info("Loaded Save From Slot {}",dusk::SaveRequested); + dusk::SaveRequested = 0xff; + } else if (dusk::SaveRequested == 0xff) { + // This indicates that the save has loaded, but we are waiting for the scene + // manager to change to play + } else if (dusk::StageRequested.size() > 0) { + + } else{ +#endif + dComIfG_changeOpeningScene(this, fpcNm_OPENING_SCENE_e); +#ifdef TARGET_PC + } + + if (dusk::StageRequested.size() > 0) { + std::stringstream ss(dusk::StageRequested); + std::string token; + + std::getline(ss,token,','); + std::string stageName = token; + s8 room = 0; + s16 point = 0; + s8 layer = -1; + if (std::getline(ss,token,',')) { + room = std::stoi(token); + if (std::getline(ss,token,',')) { + point = std::stoi(token); + if (std::getline(ss,token,',')) { + layer = std::stoi(token); + } + } + } + + if (dusk::SaveRequested == 0) { + dComIfGs_init(); + + fopScnM_ChangeReq(this, fpcNm_PLAY_SCENE_e, 0, 30); + dusk::SaveRequested = 0xff; //Skip requesting the scene from above + } + + // Use both to force-set start stage + dComIfGp_setNextStage(stageName.c_str(), point, room, layer); + g_dComIfG_gameInfo.play.mNextStage.getStartStage()->set(stageName.c_str(),point,room,layer); + + dusk::StageRequested = ""; //Reset so we don't re-do this + } +#endif } else { #if DEBUG fopScnM_ChangeReq(this, fpcNm_MENU_SCENE_e, 0, 30); diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 0d1e695524..0cc8f17ea4 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -124,6 +124,8 @@ bool dusk::IsRunning = true; bool dusk::IsShuttingDown = false; bool dusk::IsGameLaunched = false; bool dusk::RestartRequested = false; +uint8_t dusk::SaveRequested = 0; +std::string dusk::StageRequested; std::filesystem::path dusk::ConfigPath; std::filesystem::path dusk::CachePath; #endif @@ -523,7 +525,10 @@ int game_main(int argc, char* argv[]) { ("console", "Show the Windows console window for logs", cxxopts::value()->default_value("false")->implicit_value("true")) ("dvd", "Path to DVD image file", cxxopts::value()) ("backend", "Graphics API backend to use (auto, d3d12, d3d11, metal, vulkan, null)", cxxopts::value()) - ("cvar", "Override configuration variables without modifying config", cxxopts::value>()); + ("cvar", "Override configuration variables without modifying config", cxxopts::value>()) + ("develop", "Enable the game's developer mode and OSReport for debugging", cxxopts::value()->default_value("false")->implicit_value("true")) + ("load-save", "Skip the opening and load a save from slot 1-3", cxxopts::value()->default_value("0")) + ("stage", "Upon launching, load a stage, room, spawn point, and layer. Format (STAGE,ROOM,POINT,LAYER). Example: (STAGE) or (STAGE,0,0,-1)", cxxopts::value()); arg_options.parse_positional({"dvd"}); arg_options.positional_help(""); @@ -549,6 +554,12 @@ int game_main(int argc, char* argv[]) { dusk::CachePath = dataPaths.cachePath; dusk::InitializeFileLogging(dusk::CachePath, startupLogLevel); + // Development Mode + if (parsed_arg_options.count("develop")) { + mDoMain::developmentMode = parsed_arg_options["develop"].as(); // Enable Dev Mode for Debugging + dusk::OSReportReallyForceEnable = parsed_arg_options["develop"].as(); // Print OSReport to console + } + log_build_info(); dusk::config::LoadFromUserPreferences(); @@ -788,13 +799,21 @@ int game_main(int argc, char* argv[]) { // Global Context Init dComIfG_ct(); - // Development Mode - // mDoMain::developmentMode = 1; // Force Dev Mode for Debugging + // Load save from here + if (parsed_arg_options.contains("load-save")){ + uint8_t slot = parsed_arg_options["load-save"].as(); + if (slot >= 1 && slot <= 3) { + dusk::SaveRequested = slot; + } + } + if (parsed_arg_options.contains("stage")) { + dusk::StageRequested = parsed_arg_options["stage"].as(); + } + mDoDvdThd::SyncWidthSound = false; OSReport("Starting main01 (Game Loop)...\n"); - main01(); dusk::MoviePlayerShutdown(); From fb44f154b72242cb94c8ecb949f8df845c5c2c9c Mon Sep 17 00:00:00 2001 From: jdflyer Date: Mon, 29 Jun 2026 00:02:04 -0700 Subject: [PATCH 2/3] Include sstream so clang stops crying :( --- src/d/d_s_logo.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/d/d_s_logo.cpp b/src/d/d_s_logo.cpp index 9115fc5b9b..947b8960c8 100644 --- a/src/d/d_s_logo.cpp +++ b/src/d/d_s_logo.cpp @@ -24,6 +24,7 @@ #include "JSystem/JUtility/JUTConsole.h" #ifdef TARGET_PC +#include #include "dusk/logging.h" #include "dusk/version.hpp" #include "dusk/main.h" From ec50a30229933b757d79154f6dffb99f9866da31 Mon Sep 17 00:00:00 2001 From: jdflyer Date: Sat, 11 Jul 2026 01:51:19 -0700 Subject: [PATCH 3/3] Put stage load parsing in main, better load logic --- include/dusk/main.h | 14 ++++++-- src/d/d_s_logo.cpp | 32 ++++------------- src/m_Do/m_Do_main.cpp | 82 ++++++++++++++++++++++++++++++++---------- 3 files changed, 82 insertions(+), 46 deletions(-) diff --git a/include/dusk/main.h b/include/dusk/main.h index 3913ae53dc..a16291a8a7 100644 --- a/include/dusk/main.h +++ b/include/dusk/main.h @@ -9,11 +9,21 @@ extern bool IsRunning; extern bool IsShuttingDown; extern bool IsGameLaunched; extern bool RestartRequested; -extern uint8_t SaveRequested; -extern std::string StageRequested; extern std::filesystem::path ConfigPath; extern std::filesystem::path CachePath; +extern uint8_t SaveRequested; +struct StageRequest { + std::string stage; + bool set; + s8 room; + s16 point; + s8 layer; +}; +extern StageRequest StageRequested; + + + #if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS) || \ (defined(TARGET_OS_TV) && TARGET_OS_TV) inline constexpr bool SupportsProcessRestart = false; diff --git a/src/d/d_s_logo.cpp b/src/d/d_s_logo.cpp index 947b8960c8..d338775182 100644 --- a/src/d/d_s_logo.cpp +++ b/src/d/d_s_logo.cpp @@ -24,7 +24,6 @@ #include "JSystem/JUtility/JUTConsole.h" #ifdef TARGET_PC -#include #include "dusk/logging.h" #include "dusk/version.hpp" #include "dusk/main.h" @@ -795,33 +794,16 @@ void dScnLogo_c::nextSceneChange() { } else if (dusk::SaveRequested == 0xff) { // This indicates that the save has loaded, but we are waiting for the scene // manager to change to play - } else if (dusk::StageRequested.size() > 0) { - + } else if (dusk::StageRequested.set) { + // Do nothing if we need to request a stage to load later in the function } else{ #endif dComIfG_changeOpeningScene(this, fpcNm_OPENING_SCENE_e); #ifdef TARGET_PC } - if (dusk::StageRequested.size() > 0) { - std::stringstream ss(dusk::StageRequested); - std::string token; - - std::getline(ss,token,','); - std::string stageName = token; - s8 room = 0; - s16 point = 0; - s8 layer = -1; - if (std::getline(ss,token,',')) { - room = std::stoi(token); - if (std::getline(ss,token,',')) { - point = std::stoi(token); - if (std::getline(ss,token,',')) { - layer = std::stoi(token); - } - } - } - + if (dusk::StageRequested.set) { + // If we aren't loading a save, initialize a blank save file and request the correct scene to load if (dusk::SaveRequested == 0) { dComIfGs_init(); @@ -830,10 +812,10 @@ void dScnLogo_c::nextSceneChange() { } // Use both to force-set start stage - dComIfGp_setNextStage(stageName.c_str(), point, room, layer); - g_dComIfG_gameInfo.play.mNextStage.getStartStage()->set(stageName.c_str(),point,room,layer); + dComIfGp_setNextStage(dusk::StageRequested.stage.c_str(), dusk::StageRequested.point, dusk::StageRequested.room, dusk::StageRequested.layer); + g_dComIfG_gameInfo.play.mNextStage.getStartStage()->set(dusk::StageRequested.stage.c_str(), dusk::StageRequested.room, dusk::StageRequested.point, dusk::StageRequested.layer); - dusk::StageRequested = ""; //Reset so we don't re-do this + dusk::StageRequested.set = false; // Setting the stage should only happen once } #endif } else { diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 596af28a4c..881135fc51 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -42,6 +42,7 @@ #include "m_Do/m_Do_ext2.h" #include "SSystem/SComponent/c_counter.h" #include +#include #include #include @@ -126,7 +127,7 @@ bool dusk::IsShuttingDown = false; bool dusk::IsGameLaunched = false; bool dusk::RestartRequested = false; uint8_t dusk::SaveRequested = 0; -std::string dusk::StageRequested; +dusk::StageRequest dusk::StageRequested = {"",false}; std::filesystem::path dusk::ConfigPath; std::filesystem::path dusk::CachePath; #endif @@ -504,8 +505,6 @@ int game_main(int argc, char* argv[]) { } mainCalled = true; - dusk::registerSettings(); - cxxopts::ParseResult parsed_arg_options; try { @@ -521,7 +520,7 @@ int game_main(int argc, char* argv[]) { ("cvar", "Override configuration variables without modifying config", cxxopts::value>()) ("develop", "Enable the game's developer mode and OSReport for debugging", cxxopts::value()->default_value("false")->implicit_value("true")) ("load-save", "Skip the opening and load a save from slot 1-3", cxxopts::value()->default_value("0")) - ("stage", "Upon launching, load a stage, room, spawn point, and layer. Format (STAGE,ROOM,POINT,LAYER). Example: (STAGE) or (STAGE,0,0,-1)", cxxopts::value()); + ("stage", "Upon launching, load a stage, room, spawn point, and layer. When using --load-save, it uses the specified save on the loaded stage. Format (STAGE,ROOM,POINT,LAYER). Example: (STAGE) or (STAGE,0,0,-1)", cxxopts::value()); arg_options.parse_positional({"dvd"}); arg_options.positional_help(""); @@ -534,11 +533,52 @@ int game_main(int argc, char* argv[]) { printf("%s", (arg_options.help() + "\n").c_str()); exit(0); } + + if (parsed_arg_options.count("stage")) { + std::stringstream ss(parsed_arg_options["stage"].as()); + std::string token; + + std::getline(ss,token,','); + std::string stageName = token; + s8 room = 0; + s16 point = 0; + s8 layer = -1; + if (std::getline(ss,token,',')) { + room = std::stoi(token); + if (std::getline(ss,token,',')) { + point = std::stoi(token); + if (std::getline(ss,token,',')) { + layer = std::stoi(token); + } + } + } + + dusk::StageRequested = {stageName,true, room,point,layer}; + } } catch (const cxxopts::exceptions::exception& e) { fprintf(stderr, "Argument Error: %s\n", e.what()); exit(1); } + catch (const std::invalid_argument& e) { + // Handle parsing std::stoi when loading a stage + fprintf(stderr, "Fatal: Invalid Argument When Parsing Stage\n"); + exit(1); + } + catch (const std::out_of_range& e) { + // Handle parsing std::stoi when loading a stage + fprintf(stderr, "Fatal: Argument Out of Range In Parsing Stage\n"); + exit(1); + } + + if (parsed_arg_options.contains("load-save")){ + uint8_t slot = parsed_arg_options["load-save"].as(); + if (slot >= 1 && slot <= 3) { + dusk::SaveRequested = slot; + } + } + + dusk::registerSettings(); const auto startupLogLevel = static_cast(parsed_arg_options["log-level"].as()); @@ -693,7 +733,7 @@ int game_main(int argc, char* argv[]) { saveConfigBeforePrelaunch = true; } - std::string dvd_path; + std::string dvd_path = dusk::getSettings().backend.isoPath; bool dvd_opened = false; if (parsed_arg_options.count("dvd")) { dvd_path = parsed_arg_options["dvd"].as(); @@ -716,6 +756,22 @@ int game_main(int argc, char* argv[]) { } } + bool skipPreLaunchUI = dusk::getSettings().backend.skipPreLaunchUI.getValue(); + + // If we can't load right into the game, stop requesting to load a stage or save + if (forcePreLaunchUI || dvd_path.empty()) { + if (dusk::StageRequested.set) { + DuskLog.warn("Cannot load stage {} because no iso path is set, opening prelaunch UI",dusk::StageRequested.stage); + dusk::StageRequested = {}; + } + if (dusk::SaveRequested) { + DuskLog.warn("Cannot load save {} because no iso path is set, opening prelaunch UI",dusk::SaveRequested); + dusk::SaveRequested = 0; + } + }else if (dusk::StageRequested.set || dusk::SaveRequested) { + skipPreLaunchUI = true; + } + dusk::iso::log_verification_state( dusk::getSettings().backend.isoPath.getValue(), dusk::getSettings().backend.isoVerification.getValue()); @@ -724,7 +780,7 @@ int game_main(int argc, char* argv[]) { if (dusk::getSettings().backend.isoPath.getValue().empty()) { forcePreLaunchUI = true; } - if (forcePreLaunchUI && dusk::getSettings().backend.skipPreLaunchUI.getValue()) { + if (forcePreLaunchUI && skipPreLaunchUI) { DuskLog.warn("Prelaunch UI was disabled with no usable DVD image, enabling prelaunch UI"); dusk::getSettings().backend.skipPreLaunchUI.setValue(false); saveConfigBeforePrelaunch = true; @@ -733,7 +789,7 @@ int game_main(int argc, char* argv[]) { dusk::config::save(); } - if (!dusk::getSettings().backend.skipPreLaunchUI) { + if (!skipPreLaunchUI) { dusk::ui::push_document(std::make_unique(), true); // pre game launch ui main loop @@ -752,7 +808,6 @@ int game_main(int argc, char* argv[]) { } dvd_path = dusk::getSettings().backend.isoPath; - if (dvd_path.empty()) { DuskLog.fatal("No DVD image specified, unable to boot!"); } @@ -795,17 +850,6 @@ int game_main(int argc, char* argv[]) { // Global Context Init dComIfG_ct(); - // Load save from here - if (parsed_arg_options.contains("load-save")){ - uint8_t slot = parsed_arg_options["load-save"].as(); - if (slot >= 1 && slot <= 3) { - dusk::SaveRequested = slot; - } - } - if (parsed_arg_options.contains("stage")) { - dusk::StageRequested = parsed_arg_options["stage"].as(); - } - mDoDvdThd::SyncWidthSound = false; // Mod search directories, highest priority first: user dir (--mods replaces it), then