From 2cbd80a0045ac799fc2798e37697a2637c0eb196 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Tue, 4 Aug 2026 10:33:57 -0500 Subject: [PATCH] Check for existing save data before saving, confirm before creating new uiGameMenuSave() now attempts a real load first (via the existing generic saveLoad()/saveExists() primitives) instead of writing blind. If a save already exists, it saves straight over it as before. If not, it prompts via the existing uiConfirm dialog ("No save data found. Create a new save?") before writing - this is exactly the flow GameCube needs (no native OS save browser to lean on, unlike PSP), but implemented generically so it also applies correctly on every other platform without any platform-specific UI code: saveIsAvailable()/saveExists() already reflect each platform's real state (e.g. Dolphin's memory-card presence and existing-file checks), so the same logic just does the right thing everywhere. Verified the two branches directly on Linux (temporarily wiring the same saveLoad -> check -> uiConfirmOpen sequence into rpgInit): with no save file, saveExists() is false and the confirm dialog opens; with one already written, it's true and the confirm dialog is correctly skipped. Not verified via actual menu navigation (no input-injection tooling available here) or on Dolphin (no devkitPPC toolchain in this environment). --- src/dusk/ui/frame/game/uigamemenu.c | 42 +++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/dusk/ui/frame/game/uigamemenu.c b/src/dusk/ui/frame/game/uigamemenu.c index e3e2664c..2e4d0111 100644 --- a/src/dusk/ui/frame/game/uigamemenu.c +++ b/src/dusk/ui/frame/game/uigamemenu.c @@ -7,6 +7,7 @@ #include "uigamemenu.h" #include "ui/frame/uiframe.h" +#include "ui/frame/uiconfirm.h" #include "ui/frame/settings/uisettings.h" #include "ui/frame/backpack/uibackpack.h" #include "ui/rpg/textbox/uitextboxmain.h" @@ -29,7 +30,7 @@ // slot-select UI yet, and SAVE_FILE_COUNT_MAX > 1 exists for later. #define UI_GAME_MENU_SAVE_SLOT 0 -static void uiGameMenuSaveComplete(errorret_t result, void *user) { +static void uiGameMenuSaveWriteComplete(errorret_t result, void *user) { if(errorIsNotOk(result)) { // Generously sized - stringFormat asserts (crashes) rather than // truncating if the message doesn't fit, so this must comfortably fit @@ -46,6 +47,43 @@ static void uiGameMenuSaveComplete(errorret_t result, void *user) { uiTextboxMainSetText("Game saved."); } +static void uiGameMenuSaveCreateConfirmed(const bool_t confirmed, void *user) { + if(!confirmed) { + uiTextboxMainSetText("Save cancelled."); + return; + } + + saveWrite(UI_GAME_MENU_SAVE_SLOT, uiGameMenuSaveWriteComplete, NULL); +} + +// Determines whether there's actually save data to overwrite (not just +// whether the medium is present) by attempting a real load first - this is +// what lets a fresh memory card/stick, with no prior save on it yet, be +// told apart from one that already has our data on it. Cheap either way +// (a single sector/file read), and correct on every platform without any +// platform-specific UI code - saveExists() already reflects each +// platform's own notion of "found something." +static void uiGameMenuSaveCheckComplete(errorret_t result, void *user) { + if(errorIsNotOk(result)) { + char_t msg[256]; + stringFormat(msg, sizeof(msg), "Can't save: %s", result.state->message); + errorCatch(result); + uiTextboxMainSetText(msg); + return; + } + + if(saveExists(UI_GAME_MENU_SAVE_SLOT)) { + saveWrite(UI_GAME_MENU_SAVE_SLOT, uiGameMenuSaveWriteComplete, NULL); + return; + } + + uiConfirmOpen( + "No save data found. Create a new save?", + uiGameMenuSaveCreateConfirmed, + NULL + ); +} + static void uiGameMenuSave(void) { if(!saveIsAvailable()) { uiTextboxMainSetText("Can't save - no save device found."); @@ -53,7 +91,7 @@ static void uiGameMenuSave(void) { } if(saveIsBusy()) return;// A save/load dialog (e.g. on PSP) is already up. - saveWrite(UI_GAME_MENU_SAVE_SLOT, uiGameMenuSaveComplete, NULL); + saveLoad(UI_GAME_MENU_SAVE_SLOT, uiGameMenuSaveCheckComplete, NULL); } uigamemenu_t UI_GAME_MENU;