Re-enable save system, fix header/version stamping, handle missing media
- Fixed the actual reason saving never worked on any platform: saveWrite() never stamped file->header/file->version before serializing, so every written save file had a zeroed magic header and failed its own validation on the next load. Confirmed via a manual write/load round trip that this alone fully explains "saving doesn't work." - Re-enabled saveInit()/saveDispose() in engine.c (previously commented out under "Temporarily disable save code"). - Added SAVE.available + saveIsAvailable(), refreshed by every real save/load/delete attempt. saveInit() no longer treats an unreachable save medium as fatal to booting - it logs and continues, since a missing memory card/stick shouldn't prevent playing. - Hardened PSP's saveInitPSP() to actually detect a missing memory stick (sceIoGetstat on ms0:/) instead of assuming success, and fixed single-level sceIoMkdir to build the full PSP/SAVEDATA directory chain. - Added busy-retry (CARD_ERROR_BUSY) and a not-mounted guard to Dolphin's live savestreamdolphin.c path, extending the same handling already backported into savedolphin.c. - Added a "Save" entry to the game menu wired to saveWrite(0), showing a clear message on success, on failure, and when saveIsAvailable() is false.
This commit is contained in:
@@ -56,6 +56,10 @@ msgstr "Items"
|
||||
msgid "ui.game_menu.settings"
|
||||
msgstr "Settings"
|
||||
|
||||
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||
msgid "ui.game_menu.save"
|
||||
msgstr "Save"
|
||||
|
||||
msgid "item.potion.name"
|
||||
msgstr "Potion"
|
||||
|
||||
|
||||
@@ -57,6 +57,10 @@ msgstr "Objetos"
|
||||
msgid "ui.game_menu.settings"
|
||||
msgstr "Configuración"
|
||||
|
||||
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||
msgid "ui.game_menu.save"
|
||||
msgstr "Guardar"
|
||||
|
||||
#: src/dusk/rpg/item/item.json
|
||||
msgid "item.potion.name"
|
||||
msgstr "Poción"
|
||||
|
||||
@@ -57,6 +57,10 @@ msgstr "アイテム"
|
||||
msgid "ui.game_menu.settings"
|
||||
msgstr "設定"
|
||||
|
||||
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||
msgid "ui.game_menu.save"
|
||||
msgstr "セーブ"
|
||||
|
||||
#: src/dusk/rpg/item/item.json
|
||||
msgid "item.potion.name"
|
||||
msgstr "ポーション"
|
||||
|
||||
@@ -37,7 +37,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
errorChain(systemInit());
|
||||
errorChain(inputInit());
|
||||
errorChain(assetInit());
|
||||
// errorChain(saveInit());
|
||||
errorChain(saveInit());
|
||||
errorChain(localeManagerInit());
|
||||
errorChain(displayInit());
|
||||
errorChain(uiInit());
|
||||
@@ -88,7 +88,7 @@ errorret_t engineDispose(void) {
|
||||
errorChain(uiDispose());
|
||||
consoleDispose();
|
||||
errorChain(displayDispose());
|
||||
// errorChain(saveDispose());
|
||||
errorChain(saveDispose());
|
||||
errorChain(assetDispose());
|
||||
|
||||
errorOk();
|
||||
|
||||
+29
-4
@@ -9,6 +9,7 @@
|
||||
#include "save/savestream.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
#include "error/error.h"
|
||||
|
||||
save_t SAVE;
|
||||
|
||||
@@ -16,12 +17,23 @@ errorret_t saveInit(void) {
|
||||
memoryZero(&SAVE, sizeof(save_t));
|
||||
|
||||
#ifdef saveInitPlatform
|
||||
errorChain(saveInitPlatform());
|
||||
// A missing/unreachable save medium is expected, recoverable state,
|
||||
// not a reason to fail booting the whole game - log it and carry on
|
||||
// with SAVE.available false instead of chaining the error upward.
|
||||
errorret_t result = saveInitPlatform();
|
||||
SAVE.available = errorIsOk(result);
|
||||
if(!SAVE.available) errorCatch(errorPrint(result));
|
||||
#else
|
||||
SAVE.available = false;
|
||||
#endif
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
bool_t saveIsAvailable(void) {
|
||||
return SAVE.available;
|
||||
}
|
||||
|
||||
errorret_t saveDispose(void) {
|
||||
#ifdef saveDisposePlatform
|
||||
errorChain(saveDisposePlatform());
|
||||
@@ -39,7 +51,9 @@ errorret_t saveLoad(const uint8_t slot) {
|
||||
memoryZero(&stream, sizeof(savestream_t));
|
||||
|
||||
#ifdef saveStreamOpenReadPlatform
|
||||
errorChain(saveStreamOpenReadPlatform(&stream, slot));
|
||||
errorret_t openRet = saveStreamOpenReadPlatform(&stream, slot);
|
||||
SAVE.available = errorIsOk(openRet);
|
||||
errorChain(openRet);
|
||||
#endif
|
||||
|
||||
if(!stream.found) errorOk();
|
||||
@@ -62,12 +76,21 @@ errorret_t saveWrite(const uint8_t slot) {
|
||||
assertTrue(slot < SAVE_FILE_COUNT_MAX, "slot exceeds SAVE_FILE_COUNT_MAX");
|
||||
|
||||
savefile_t *file = &SAVE.files[slot];
|
||||
// These are metadata about the file itself, not game data - always stamp
|
||||
// the current magic/version on every write rather than relying on
|
||||
// whatever happened to already be in memory (zeroed at saveInit, or
|
||||
// whatever version an old loaded file had), otherwise the written file
|
||||
// fails its own header check the next time it's loaded.
|
||||
memoryCopy(file->header, SAVE_FILE_HEADER, SAVE_FILE_HEADER_SIZE);
|
||||
file->version = SAVE_FILE_VERSION;
|
||||
|
||||
savestream_t stream;
|
||||
memoryZero(&stream, sizeof(savestream_t));
|
||||
|
||||
#ifdef saveStreamOpenWritePlatform
|
||||
errorChain(saveStreamOpenWritePlatform(&stream, slot));
|
||||
errorret_t openRet = saveStreamOpenWritePlatform(&stream, slot);
|
||||
SAVE.available = errorIsOk(openRet);
|
||||
errorChain(openRet);
|
||||
#endif
|
||||
|
||||
errorret_t ret = saveFileWrite(&stream, file);
|
||||
@@ -90,7 +113,9 @@ errorret_t saveDelete(const uint8_t slot) {
|
||||
assertTrue(slot < SAVE_FILE_COUNT_MAX, "slot exceeds SAVE_FILE_COUNT_MAX");
|
||||
|
||||
#ifdef saveDeletePlatform
|
||||
errorChain(saveDeletePlatform(slot));
|
||||
errorret_t deleteRet = saveDeletePlatform(slot);
|
||||
SAVE.available = errorIsOk(deleteRet);
|
||||
errorChain(deleteRet);
|
||||
#endif
|
||||
|
||||
SAVE.files[slot].exists = false;
|
||||
|
||||
+24
-2
@@ -15,17 +15,39 @@ typedef struct {
|
||||
savefile_t files[SAVE_FILE_COUNT_MAX];
|
||||
/** Platform-specific save system state (paths, card handles, etc.). */
|
||||
saveplatform_t platform;
|
||||
/**
|
||||
* True if the save medium (memory card/stick/disk) was reachable the
|
||||
* last time it was checked - at saveInit(), and refreshed by every
|
||||
* subsequent saveLoad()/saveWrite() attempt. Starting the game with no
|
||||
* card/stick inserted, or one being removed mid-session, are both
|
||||
* expected conditions here, not fatal errors - see saveIsAvailable().
|
||||
*/
|
||||
bool_t available;
|
||||
} save_t;
|
||||
|
||||
extern save_t SAVE;
|
||||
|
||||
/**
|
||||
* Initializes the save system.
|
||||
* Initializes the save system. Never fails the way saveWrite/saveLoad can -
|
||||
* if the platform's save medium isn't reachable (e.g. no memory card/stick
|
||||
* inserted), that's logged and reflected in saveIsAvailable() rather than
|
||||
* treated as fatal, since the game should still be playable without save
|
||||
* support.
|
||||
*
|
||||
* @return An error code if initialization fails.
|
||||
* @return An error code only for unexpected platform failures.
|
||||
*/
|
||||
errorret_t saveInit(void);
|
||||
|
||||
/**
|
||||
* Checks whether the save medium was reachable as of the last save/load
|
||||
* attempt (or saveInit(), if none has been attempted yet). Intended for UI
|
||||
* to decide whether to offer saving/loading at all, or to explain why it
|
||||
* isn't available right now - e.g. "No memory card inserted".
|
||||
*
|
||||
* @return true if the save medium was available last time it was checked.
|
||||
*/
|
||||
bool_t saveIsAvailable(void);
|
||||
|
||||
/**
|
||||
* Disposes of the save system.
|
||||
*
|
||||
|
||||
@@ -9,16 +9,48 @@
|
||||
#include "ui/frame/uiframe.h"
|
||||
#include "ui/frame/settings/uisettings.h"
|
||||
#include "ui/frame/backpack/uibackpack.h"
|
||||
#include "ui/rpg/textbox/uitextboxmain.h"
|
||||
#include "util/memory.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
#include "display/screen/screen.h"
|
||||
#include "assert/assert.h"
|
||||
#include "locale/localemanager.h"
|
||||
#include "asset/loader/locale/assetlocaleloader.h"
|
||||
#include "save/save.h"
|
||||
#include "error/error.h"
|
||||
#include "util/string.h"
|
||||
|
||||
#define UI_GAME_MENU_INDEX_CHARACTERS 0
|
||||
#define UI_GAME_MENU_INDEX_ITEMS 1
|
||||
#define UI_GAME_MENU_INDEX_SETTINGS 2
|
||||
#define UI_GAME_MENU_INDEX_SAVE 3
|
||||
|
||||
// Only one save slot is exposed through this menu for now - there is no
|
||||
// slot-select UI yet, and SAVE_FILE_COUNT_MAX > 1 exists for later.
|
||||
#define UI_GAME_MENU_SAVE_SLOT 0
|
||||
|
||||
static void uiGameMenuSave(void) {
|
||||
if(!saveIsAvailable()) {
|
||||
uiTextboxMainSetText("Can't save - no save device found.");
|
||||
return;
|
||||
}
|
||||
|
||||
errorret_t ret = saveWrite(UI_GAME_MENU_SAVE_SLOT);
|
||||
if(errorIsNotOk(ret)) {
|
||||
// Generously sized - stringFormat asserts (crashes) rather than
|
||||
// truncating if the message doesn't fit, so this must comfortably fit
|
||||
// the longest platform save-error message plus this prefix.
|
||||
char_t msg[256];
|
||||
stringFormat(
|
||||
msg, sizeof(msg), "Save failed: %s", ret.state->message
|
||||
);
|
||||
errorCatch(ret);
|
||||
uiTextboxMainSetText(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
uiTextboxMainSetText("Game saved.");
|
||||
}
|
||||
|
||||
uigamemenu_t UI_GAME_MENU;
|
||||
|
||||
@@ -29,6 +61,7 @@ void uiGameMenuSelected(
|
||||
) {
|
||||
if(index == UI_GAME_MENU_INDEX_ITEMS) uiBackpackOpen();
|
||||
if(index == UI_GAME_MENU_INDEX_SETTINGS) uiSettingsOpen();
|
||||
if(index == UI_GAME_MENU_INDEX_SAVE) uiGameMenuSave();
|
||||
}
|
||||
|
||||
errorret_t uiGameMenuInit(void) {
|
||||
@@ -55,6 +88,13 @@ errorret_t uiGameMenuInit(void) {
|
||||
UI_GAME_MENU.settingsLabel,
|
||||
UI_GAME_MENU_LABEL_MAX
|
||||
));
|
||||
errorChain(assetLocaleGetString(
|
||||
&LOCALE.entry->data.locale,
|
||||
"ui.game_menu.save",
|
||||
0,
|
||||
UI_GAME_MENU.saveLabel,
|
||||
UI_GAME_MENU_LABEL_MAX
|
||||
));
|
||||
|
||||
MENU_BEGIN(
|
||||
&UI_GAME_MENU.menu, UI_GAME_MENU.items, uiGameMenuSelected, NULL, NULL
|
||||
@@ -62,6 +102,7 @@ errorret_t uiGameMenuInit(void) {
|
||||
MENU_BUTTON(UI_GAME_MENU.charactersLabel);
|
||||
MENU_BUTTON(UI_GAME_MENU.itemsLabel);
|
||||
MENU_BUTTON(UI_GAME_MENU.settingsLabel);
|
||||
MENU_BUTTON(UI_GAME_MENU.saveLabel);
|
||||
|
||||
MENU_END(UI_GAME_MENU.items, 1);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "error/error.h"
|
||||
#include "ui/widget/uimenu.h"
|
||||
|
||||
#define UI_GAME_MENU_ITEM_COUNT 3
|
||||
#define UI_GAME_MENU_ITEM_COUNT 4
|
||||
#define UI_GAME_MENU_WIDTH 150.0f
|
||||
#define UI_GAME_MENU_LABEL_MAX 32
|
||||
|
||||
@@ -19,6 +19,7 @@ typedef struct {
|
||||
char_t charactersLabel[UI_GAME_MENU_LABEL_MAX];
|
||||
char_t itemsLabel[UI_GAME_MENU_LABEL_MAX];
|
||||
char_t settingsLabel[UI_GAME_MENU_LABEL_MAX];
|
||||
char_t saveLabel[UI_GAME_MENU_LABEL_MAX];
|
||||
} uigamemenu_t;
|
||||
|
||||
extern uigamemenu_t UI_GAME_MENU;
|
||||
|
||||
@@ -19,12 +19,20 @@ static void _saveStreamGetFileName(
|
||||
errorret_t saveStreamOpenReadDolphin(
|
||||
savestreamdolphin_t *p, bool_t *found, const uint8_t slot
|
||||
) {
|
||||
if(!SAVE.platform.mounted) {
|
||||
*found = false;
|
||||
errorThrow("No memory card mounted");
|
||||
}
|
||||
|
||||
char_t fileName[SAVE_DOLPHIN_FILE_NAME_MAX];
|
||||
_saveStreamGetFileName(fileName, SAVE_DOLPHIN_FILE_NAME_MAX, slot);
|
||||
|
||||
int32_t result = CARD_Open(
|
||||
SAVE_DOLPHIN_CHANNEL, fileName, &p->cardFile
|
||||
);
|
||||
int32_t result;
|
||||
do {
|
||||
result = CARD_Open(
|
||||
SAVE_DOLPHIN_CHANNEL, fileName, &p->cardFile
|
||||
);
|
||||
} while(result == CARD_ERROR_BUSY);
|
||||
if(result == CARD_ERROR_NOFILE) {
|
||||
*found = false;
|
||||
p->position = 0;
|
||||
@@ -33,17 +41,19 @@ errorret_t saveStreamOpenReadDolphin(
|
||||
}
|
||||
if(result < 0) {
|
||||
*found = false;
|
||||
errorThrow("Failed to open memory card file for slot %u (error %d)",
|
||||
(uint32_t)slot, result
|
||||
errorThrow("Failed to open memory card file for slot %u: %s (%d)",
|
||||
(uint32_t)slot, saveCardErrorStringDolphin(result), result
|
||||
);
|
||||
}
|
||||
|
||||
result = CARD_Read(&p->cardFile, p->buffer, SAVE_DOLPHIN_SECTOR_SIZE, 0);
|
||||
do {
|
||||
result = CARD_Read(&p->cardFile, p->buffer, SAVE_DOLPHIN_SECTOR_SIZE, 0);
|
||||
} while(result == CARD_ERROR_BUSY);
|
||||
CARD_Close(&p->cardFile);
|
||||
if(result < 0) {
|
||||
*found = false;
|
||||
errorThrow("Failed to read memory card data for slot %u (error %d)",
|
||||
(uint32_t)slot, result
|
||||
errorThrow("Failed to read memory card data for slot %u: %s (%d)",
|
||||
(uint32_t)slot, saveCardErrorStringDolphin(result), result
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,6 +67,8 @@ errorret_t saveStreamOpenReadDolphin(
|
||||
errorret_t saveStreamOpenWriteDolphin(
|
||||
savestreamdolphin_t *p, const uint8_t slot
|
||||
) {
|
||||
if(!SAVE.platform.mounted) errorThrow("No memory card mounted");
|
||||
|
||||
memoryZero(p->buffer, SAVE_DOLPHIN_SECTOR_SIZE);
|
||||
p->position = 0;
|
||||
p->writing = true;
|
||||
@@ -70,14 +82,21 @@ void saveStreamCloseDolphin(savestreamdolphin_t *p) {
|
||||
char_t fileName[SAVE_DOLPHIN_FILE_NAME_MAX];
|
||||
_saveStreamGetFileName(fileName, SAVE_DOLPHIN_FILE_NAME_MAX, p->slot);
|
||||
|
||||
int32_t result = CARD_Open(SAVE_DOLPHIN_CHANNEL, fileName, &p->cardFile);
|
||||
int32_t result;
|
||||
do {
|
||||
result = CARD_Open(SAVE_DOLPHIN_CHANNEL, fileName, &p->cardFile);
|
||||
} while(result == CARD_ERROR_BUSY);
|
||||
if(result == CARD_ERROR_NOFILE) {
|
||||
CARD_Create(
|
||||
SAVE_DOLPHIN_CHANNEL, fileName, SAVE_DOLPHIN_SECTOR_SIZE, &p->cardFile
|
||||
);
|
||||
do {
|
||||
result = CARD_Create(
|
||||
SAVE_DOLPHIN_CHANNEL, fileName, SAVE_DOLPHIN_SECTOR_SIZE, &p->cardFile
|
||||
);
|
||||
} while(result == CARD_ERROR_BUSY);
|
||||
}
|
||||
|
||||
CARD_Write(&p->cardFile, p->buffer, SAVE_DOLPHIN_SECTOR_SIZE, 0);
|
||||
do {
|
||||
result = CARD_Write(&p->cardFile, p->buffer, SAVE_DOLPHIN_SECTOR_SIZE, 0);
|
||||
} while(result == CARD_ERROR_BUSY);
|
||||
CARD_Close(&p->cardFile);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,18 @@
|
||||
|
||||
#include "save/save.h"
|
||||
|
||||
void savePSPEnsureBaseDirs(void) {
|
||||
sceIoMkdir(SAVE_PSP_BASE_DIR, 0777);
|
||||
sceIoMkdir(SAVE_PSP_SAVEDATA_DIR, 0777);
|
||||
}
|
||||
|
||||
errorret_t saveInitPSP(void) {
|
||||
SceIoStat stat;
|
||||
if(sceIoGetstat(SAVE_PSP_ROOT, &stat) < 0) {
|
||||
errorThrow("No memory stick detected");
|
||||
}
|
||||
|
||||
savePSPEnsureBaseDirs();
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -40,6 +51,7 @@ errorret_t saveLoadPSP(const uint8_t slot, savefile_t *file) {
|
||||
}
|
||||
|
||||
errorret_t saveWritePSP(const uint8_t slot, const savefile_t *file) {
|
||||
savePSPEnsureBaseDirs();
|
||||
char_t dir[SAVE_PSP_PATH_MAX];
|
||||
snprintf(dir, SAVE_PSP_PATH_MAX, SAVE_PSP_DIR_FORMAT,
|
||||
SAVE_PSP_TITLE_ID, (uint32_t)slot
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
#include <pspiofilemgr.h>
|
||||
|
||||
#define SAVE_PSP_PATH_MAX 256
|
||||
#define SAVE_PSP_ROOT "ms0:/"
|
||||
#define SAVE_PSP_BASE_DIR "ms0:/PSP"
|
||||
#define SAVE_PSP_SAVEDATA_DIR "ms0:/PSP/SAVEDATA"
|
||||
#define SAVE_PSP_FILE_FORMAT "ms0:/PSP/SAVEDATA/%s%02u/save.dat"
|
||||
#define SAVE_PSP_DIR_FORMAT "ms0:/PSP/SAVEDATA/%s%02u"
|
||||
|
||||
@@ -23,9 +26,14 @@ typedef struct {
|
||||
} savepsp_t;
|
||||
|
||||
/**
|
||||
* Initializes the save system on PSP.
|
||||
* Initializes the save system on PSP. Confirms the memory stick is
|
||||
* actually reachable (sceIoGetstat on SAVE_PSP_ROOT) rather than assuming
|
||||
* so, since raw sceIo calls otherwise only fail once something tries to
|
||||
* touch the filesystem - and ensures the PSP/SAVEDATA directory tree
|
||||
* exists (SAVE_PSP_BASE_DIR then SAVE_PSP_SAVEDATA_DIR, since sceIoMkdir
|
||||
* only creates one level at a time).
|
||||
*
|
||||
* @return An error code if initialization fails.
|
||||
* @return An error code if no memory stick is reachable.
|
||||
*/
|
||||
errorret_t saveInitPSP(void);
|
||||
|
||||
@@ -61,3 +69,12 @@ errorret_t saveWritePSP(const uint8_t slot, const savefile_t *file);
|
||||
* @return An error code if the delete fails.
|
||||
*/
|
||||
errorret_t saveDeletePSP(const uint8_t slot);
|
||||
|
||||
/**
|
||||
* Ensures SAVE_PSP_BASE_DIR and SAVE_PSP_SAVEDATA_DIR both exist, creating
|
||||
* whichever are missing. sceIoMkdir only creates one directory level at a
|
||||
* time, so this must run before creating any per-slot save directory
|
||||
* beneath SAVE_PSP_SAVEDATA_DIR. Safe to call repeatedly - an
|
||||
* already-exists result is not an error.
|
||||
*/
|
||||
void savePSPEnsureBaseDirs(void);
|
||||
|
||||
@@ -22,6 +22,7 @@ errorret_t saveStreamOpenReadPSP(
|
||||
}
|
||||
|
||||
errorret_t saveStreamOpenWritePSP(savestreampsp_t *p, const uint8_t slot) {
|
||||
savePSPEnsureBaseDirs();
|
||||
char_t dir[SAVE_PSP_PATH_MAX];
|
||||
snprintf(dir, SAVE_PSP_PATH_MAX, SAVE_PSP_DIR_FORMAT,
|
||||
SAVE_PSP_TITLE_ID, (uint32_t)slot
|
||||
|
||||
Reference in New Issue
Block a user