From f3ea5073136cfc162ffd1397b5a046a813d2d222 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Tue, 4 Aug 2026 08:12:57 -0500 Subject: [PATCH] Fixed save crash Backported from branch ac2 (commit 85b61097) - CARD_Mount was being called without CARD_Init first, leaving per-channel control blocks and the DSP unlock sequence unset. On real Dolphin/hardware this surfaced as a hard MMIO crash instead of a clean CARD_ERROR_* failure. Co-Authored-By: Dominic Masters --- src/duskdolphin/save/savedolphin.c | 163 +++++++++++++++++++++-------- src/duskdolphin/save/savedolphin.h | 23 ++++ 2 files changed, 142 insertions(+), 44 deletions(-) diff --git a/src/duskdolphin/save/savedolphin.c b/src/duskdolphin/save/savedolphin.c index 0d1694a7..464f0ad5 100644 --- a/src/duskdolphin/save/savedolphin.c +++ b/src/duskdolphin/save/savedolphin.c @@ -9,23 +9,48 @@ #include "util/memory.h" #include "util/string.h" -static void _saveGetFileName( - const uint8_t slot, char_t *out, const size_t max -) { - snprintf(out, max, "%s_%u", SAVE_DOLPHIN_GAME_CODE, (uint32_t)slot); -} - errorret_t saveInitDolphin(void) { SAVE.platform.mounted = false; - int32_t result = CARD_Mount( - SAVE_DOLPHIN_CHANNEL, - SAVE.platform.cardBuffer, - NULL - ); + // Must run once before any other CARD_* call: sets up card_inited, + // the per-channel control blocks (wait queues, alarms) CARD_Mount reads, + // and initializes the DSP (needed for the card unlock sequence). + // Skipping this leaves those structures unset, so CARD_Mount ends up + // touching hardware state that was never brought up -- e.g. Dolphin's + // "Trying to read 32 bits from an invalid MMIO" error -- rather than + // failing cleanly with a CARD_ERROR_* code. + int32_t result = CARD_Init(SAVE_DOLPHIN_GAME_CODE, NULL); + if(result < 0) { + errorThrow("Failed to initialize memory card subsystem: %s (%d)", + saveCardErrorStringDolphin(result), result + ); + } + + do { + result = CARD_Mount( + SAVE_DOLPHIN_CHANNEL, + SAVE.platform.cardBuffer, + NULL + ); + } while(result == CARD_ERROR_BUSY); + + // Special-case the failures a player can actually act on; everything + // else falls through to the generic, fully-enumerated message below. + switch(result) { + case CARD_ERROR_NOCARD: + errorThrow("No memory card inserted in the slot"); + case CARD_ERROR_WRONGDEVICE: + errorThrow("Unsupported device inserted in the memory card slot"); + case CARD_ERROR_BROKEN: + errorThrow("Memory card is damaged or unformatted"); + default: + break; + } if(result < 0) { - errorThrow("Failed to mount memory card (error %d)", result); + errorThrow("Failed to mount memory card: %s (%d)", + saveCardErrorStringDolphin(result), result + ); } SAVE.platform.mounted = true; @@ -42,19 +67,22 @@ errorret_t saveDisposeDolphin(void) { errorret_t saveLoadDolphin(const uint8_t slot, savefile_t *file) { char_t fileName[SAVE_DOLPHIN_FILE_NAME_MAX]; - _saveGetFileName(slot, fileName, SAVE_DOLPHIN_FILE_NAME_MAX); + saveGetFileNameDolphin(slot, fileName, SAVE_DOLPHIN_FILE_NAME_MAX); - int32_t result = CARD_Open( - SAVE_DOLPHIN_CHANNEL, fileName, &SAVE.platform.cardFile - ); + int32_t result; + do { + result = CARD_Open( + SAVE_DOLPHIN_CHANNEL, fileName, &SAVE.platform.cardFile + ); + } while(result == CARD_ERROR_BUSY); if(result == CARD_ERROR_NOFILE) { file->exists = false; errorOk(); } if(result < 0) { file->exists = 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 ); } @@ -64,16 +92,18 @@ errorret_t saveLoadDolphin(const uint8_t slot, savefile_t *file) { errorThrow("Failed to allocate memory card read buffer"); } - result = CARD_Read( - &SAVE.platform.cardFile, buffer, SAVE_DOLPHIN_SECTOR_SIZE, 0 - ); + do { + result = CARD_Read( + &SAVE.platform.cardFile, buffer, SAVE_DOLPHIN_SECTOR_SIZE, 0 + ); + } while(result == CARD_ERROR_BUSY); CARD_Close(&SAVE.platform.cardFile); if(result < 0) { memoryFree(buffer); file->exists = 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 ); } @@ -86,7 +116,7 @@ errorret_t saveLoadDolphin(const uint8_t slot, savefile_t *file) { errorret_t saveWriteDolphin(const uint8_t slot, const savefile_t *file) { char_t fileName[SAVE_DOLPHIN_FILE_NAME_MAX]; - _saveGetFileName(slot, fileName, SAVE_DOLPHIN_FILE_NAME_MAX); + saveGetFileNameDolphin(slot, fileName, SAVE_DOLPHIN_FILE_NAME_MAX); void *buffer = memoryAlign(32, SAVE_DOLPHIN_SECTOR_SIZE); if(!buffer) { @@ -96,34 +126,41 @@ errorret_t saveWriteDolphin(const uint8_t slot, const savefile_t *file) { memoryCopy(buffer, file, sizeof(savefile_t)); // Try open existing file first; create if absent. - int32_t result = CARD_Open( - SAVE_DOLPHIN_CHANNEL, fileName, &SAVE.platform.cardFile - ); - if(result == CARD_ERROR_NOFILE) { - result = CARD_Create( - SAVE_DOLPHIN_CHANNEL, - fileName, - SAVE_DOLPHIN_SECTOR_SIZE, - &SAVE.platform.cardFile + int32_t result; + do { + result = CARD_Open( + SAVE_DOLPHIN_CHANNEL, fileName, &SAVE.platform.cardFile ); + } while(result == CARD_ERROR_BUSY); + if(result == CARD_ERROR_NOFILE) { + do { + result = CARD_Create( + SAVE_DOLPHIN_CHANNEL, + fileName, + SAVE_DOLPHIN_SECTOR_SIZE, + &SAVE.platform.cardFile + ); + } while(result == CARD_ERROR_BUSY); } if(result < 0) { memoryFree(buffer); - errorThrow("Failed to open/create memory card file for slot %u (error %d)", - (uint32_t)slot, result + errorThrow("Failed to open/create memory card file for slot %u: %s (%d)", + (uint32_t)slot, saveCardErrorStringDolphin(result), result ); } - result = CARD_Write( - &SAVE.platform.cardFile, buffer, SAVE_DOLPHIN_SECTOR_SIZE, 0 - ); + do { + result = CARD_Write( + &SAVE.platform.cardFile, buffer, SAVE_DOLPHIN_SECTOR_SIZE, 0 + ); + } while(result == CARD_ERROR_BUSY); CARD_Close(&SAVE.platform.cardFile); memoryFree(buffer); if(result < 0) { - errorThrow("Failed to write memory card data for slot %u (error %d)", - (uint32_t)slot, result + errorThrow("Failed to write memory card data for slot %u: %s (%d)", + (uint32_t)slot, saveCardErrorStringDolphin(result), result ); } @@ -132,14 +169,52 @@ errorret_t saveWriteDolphin(const uint8_t slot, const savefile_t *file) { errorret_t saveDeleteDolphin(const uint8_t slot) { char_t fileName[SAVE_DOLPHIN_FILE_NAME_MAX]; - _saveGetFileName(slot, fileName, SAVE_DOLPHIN_FILE_NAME_MAX); + saveGetFileNameDolphin(slot, fileName, SAVE_DOLPHIN_FILE_NAME_MAX); - int32_t result = CARD_Delete(SAVE_DOLPHIN_CHANNEL, fileName); + int32_t result; + do { + result = CARD_Delete(SAVE_DOLPHIN_CHANNEL, fileName); + } while(result == CARD_ERROR_BUSY); if(result < 0 && result != CARD_ERROR_NOFILE) { - errorThrow("Failed to delete memory card file for slot %u (error %d)", - (uint32_t)slot, result + errorThrow("Failed to delete memory card file for slot %u: %s (%d)", + (uint32_t)slot, saveCardErrorStringDolphin(result), result ); } errorOk(); } + +void saveGetFileNameDolphin( + const uint8_t slot, char_t *out, const size_t max +) { + snprintf(out, max, "%s_%u", SAVE_DOLPHIN_GAME_CODE, (uint32_t)slot); +} + +const char_t *saveCardErrorStringDolphin(const int32_t result) { + switch(result) { + case CARD_ERROR_READY: return "card is ready"; + case CARD_ERROR_UNLOCKED: + return "card is being unlocked or already unlocked"; + case CARD_ERROR_BUSY: return "card is busy"; + case CARD_ERROR_WRONGDEVICE: return "wrong device connected in slot"; + case CARD_ERROR_NOCARD: return "no memory card in slot"; + case CARD_ERROR_NOFILE: return "specified file not found"; + case CARD_ERROR_IOERROR: return "internal EXI I/O error"; + case CARD_ERROR_BROKEN: + return "directory structure or file entry broken"; + case CARD_ERROR_EXIST: + return "file already exists with the specified parameters"; + case CARD_ERROR_NOENT: + return "no empty block available to create the file"; + case CARD_ERROR_INSSPACE: + return "not enough space to write file to memory card"; + case CARD_ERROR_NOPERM: + return "not enough permissions to operate on the file"; + case CARD_ERROR_LIMIT: return "card size limit reached"; + case CARD_ERROR_NAMETOOLONG: return "filename too long"; + case CARD_ERROR_ENCODING: return "font encoding PAL/SJIS mismatch"; + case CARD_ERROR_CANCELED: return "card operation canceled"; + case CARD_ERROR_FATAL_ERROR: return "fatal error, non-recoverable"; + default: return "unknown card error"; + } +} diff --git a/src/duskdolphin/save/savedolphin.h b/src/duskdolphin/save/savedolphin.h index 1408ba89..c6a72aa0 100644 --- a/src/duskdolphin/save/savedolphin.h +++ b/src/duskdolphin/save/savedolphin.h @@ -66,3 +66,26 @@ errorret_t saveWriteDolphin(const uint8_t slot, const savefile_t *file); * @return An error code if the delete fails. */ errorret_t saveDeleteDolphin(const uint8_t slot); + +/** + * Builds the memory card file name for a given save slot, from + * SAVE_DOLPHIN_GAME_CODE and the slot index. + * + * @param slot The save slot index. + * @param out Destination buffer for the file name. + * @param max Size of out, in bytes. + */ +void saveGetFileNameDolphin( + const uint8_t slot, char_t *out, const size_t max +); + +/** + * Describes a libogc CARD_ERROR_* result code (see + * https://libogc.devkitpro.org/group__card__errors.html), for logging + * alongside the raw numeric code. + * + * @param result The result code returned by a CARD_* libogc call. + * @return A human-readable description of the result code, or + * "unknown card error" if result doesn't match a known CARD_ERROR_* code. + */ +const char_t *saveCardErrorStringDolphin(const int32_t result);