/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "save/save.h" #include "save/savestream.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 saveCombinedLoadDolphin(void) { savestream_t stream; memoryZero(&stream, sizeof(savestream_t)); errorret_t openRet = saveStreamOpenReadPlatform(&stream); SAVE.available = errorIsOk(openRet); errorChain(openRet); if(!stream.found) errorOk(); errorret_t ret = saveMetaSerializeRead(&stream, &SAVE.meta); for(uint8_t i = 0; errorIsOk(ret) && i < SAVE_SLOT_COUNT_MAX; i++) { ret = saveSlotSerializeRead(&stream, &SAVE.slots[i]); } #ifdef saveStreamClosePlatform saveStreamClosePlatform(&stream); #endif errorChain(ret); errorOk(); } errorret_t saveCombinedWriteDolphin(void) { savestream_t stream; memoryZero(&stream, sizeof(savestream_t)); errorret_t openRet = saveStreamOpenWritePlatform(&stream); SAVE.available = errorIsOk(openRet); errorChain(openRet); errorret_t ret = saveMetaSerializeWrite(&stream, &SAVE.meta); for(uint8_t i = 0; errorIsOk(ret) && i < SAVE_SLOT_COUNT_MAX; i++) { ret = saveSlotSerializeWrite(&stream, &SAVE.slots[i]); } #ifdef saveStreamClosePlatform saveStreamClosePlatform(&stream); #endif errorChain(ret); errorOk(); } errorret_t saveSlotLoadDolphin(const uint8_t slot, saveslot_t *out) { (void)slot; (void)out; return saveCombinedLoadDolphin(); } errorret_t saveSlotWriteDolphin(const uint8_t slot, saveslot_t *slotData) { (void)slot; (void)slotData; return saveCombinedWriteDolphin(); } errorret_t saveSlotDeleteDolphin(const uint8_t slot) { memoryZero(&SAVE.slots[slot], sizeof(saveslot_t)); SAVE.slots[slot].exists = false; return saveCombinedWriteDolphin(); } errorret_t saveMetaLoadDolphin(savemeta_t *out) { (void)out; return saveCombinedLoadDolphin(); } errorret_t saveMetaWriteDolphin(savemeta_t *meta) { (void)meta; return saveCombinedWriteDolphin(); } 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"; } }