/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "save/save.h" #include "util/memory.h" #include "util/string.h" errorret_t saveInitDolphin(void) { SAVE.platform.mounted = false; // 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: %s (%d)", saveCardErrorStringDolphin(result), result ); } SAVE.platform.mounted = true; errorOk(); } errorret_t saveDisposeDolphin(void) { if(SAVE.platform.mounted) { CARD_Unmount(SAVE_DOLPHIN_CHANNEL); SAVE.platform.mounted = false; } errorOk(); } errorret_t saveLoadDolphin(const uint8_t slot, savefile_t *file) { char_t fileName[SAVE_DOLPHIN_FILE_NAME_MAX]; saveGetFileNameDolphin(slot, fileName, SAVE_DOLPHIN_FILE_NAME_MAX); 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: %s (%d)", (uint32_t)slot, saveCardErrorStringDolphin(result), result ); } void *buffer = memoryAlign(32, SAVE_DOLPHIN_SECTOR_SIZE); if(!buffer) { CARD_Close(&SAVE.platform.cardFile); errorThrow("Failed to allocate memory card read buffer"); } 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: %s (%d)", (uint32_t)slot, saveCardErrorStringDolphin(result), result ); } memoryCopy(file, buffer, sizeof(savefile_t)); memoryFree(buffer); file->exists = true; errorOk(); } errorret_t saveWriteDolphin(const uint8_t slot, const savefile_t *file) { char_t fileName[SAVE_DOLPHIN_FILE_NAME_MAX]; saveGetFileNameDolphin(slot, fileName, SAVE_DOLPHIN_FILE_NAME_MAX); void *buffer = memoryAlign(32, SAVE_DOLPHIN_SECTOR_SIZE); if(!buffer) { errorThrow("Failed to allocate memory card write buffer"); } memoryZero(buffer, SAVE_DOLPHIN_SECTOR_SIZE); memoryCopy(buffer, file, sizeof(savefile_t)); // Try open existing file first; create if absent. 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: %s (%d)", (uint32_t)slot, saveCardErrorStringDolphin(result), result ); } 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: %s (%d)", (uint32_t)slot, saveCardErrorStringDolphin(result), result ); } errorOk(); } errorret_t saveDeleteDolphin(const uint8_t slot) { char_t fileName[SAVE_DOLPHIN_FILE_NAME_MAX]; saveGetFileNameDolphin(slot, fileName, SAVE_DOLPHIN_FILE_NAME_MAX); 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: %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"; } }