/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #pragma once #include "error/error.h" #include // GameCube always uses this device (slot A + slot B). On Wii it's also used // when DUSK_SAVE_WII_METHOD_CARD is selected - libogc's CARD API // transparently redirects to Wii's NAND-based GameCube memory card // emulation, so the exact same code path works on both. #ifndef SAVE_DEVICE_DOLPHIN_GAME_CODE #define SAVE_DEVICE_DOLPHIN_GAME_CODE "DUSK" #endif // Memory cards have no rename primitive and files can't be resized once // created, so a single "current" file can't be replaced atomically the way // the other backends do. Instead saveDeviceDolphinCardDataWrite() ping-pongs // between these two fixed files - every store targets whichever one is NOT // the currently valid+newest copy (by saveDeviceRawIsValid()'s generation // counter), so a crash mid-write can only ever corrupt the copy nobody // would load from; the other one stays intact as the fallback. #ifndef SAVE_DEVICE_DOLPHIN_CARD_FILENAME_0 #define SAVE_DEVICE_DOLPHIN_CARD_FILENAME_0 "DUSKSAVE0" #endif #ifndef SAVE_DEVICE_DOLPHIN_CARD_FILENAME_1 #define SAVE_DEVICE_DOLPHIN_CARD_FILENAME_1 "DUSKSAVE1" #endif typedef struct { // Set from this device's index into SAVE.devices - device 0 is // CARD_SLOTA, device 1 is CARD_SLOTB. int32_t channel; uint8_t cardBuffer[CARD_WORKAREA] __attribute__((aligned(32))); bool_t mounted; } savedeviceplatform_t; typedef struct savedevice_s savedevice_t; /** * Initializes the save device platform. * * @param device The save device platform to initialize. * @return Error state if any. */ errorret_t saveDeviceDolphinCardInit(savedevice_t *device); /** * Updates the save device platform. * * @param device The save device platform to update. * @return Error state if any. */ errorret_t saveDeviceDolphinCardUpdate(savedevice_t *device); /** * Requests the device to check its availability, this will call the callback * whence completed. Mounts the memory card in this device's slot and * confirms there's at least one free block and one free file entry left to * write a save into. * * @param device The save device platform to check availability. */ void saveDeviceDolphinCardCheckAvailability(savedevice_t *device); /** * Maps a libogc CARD_ERROR_* result (from CARD_Probe or CARD_Mount) to the * matching reasonKey. * * @param result The card_errors result code to map. * @return The matching reasonKey, or a generic fallback if unrecognized. */ const char_t *saveDeviceDolphinCardErrorReasonKey(const int32_t result); /** * Returns whether the mounted memory card in the given slot has at least * one block of free space and one free file entry remaining, by summing * the block usage of every file already on the card (CARD_GetDirectory) * against the card's total block count (CARD_GetBlockCount) - libogc * doesn't expose a single "free blocks remaining" call. * * @param channel The CARD slot (CARD_SLOTA or CARD_SLOTB) to check. Must * already be mounted. * @return True if there's room to create at least one more save file. */ bool_t saveDeviceDolphinCardHasFreeSpace(const int32_t channel); /** * Disposes of the save device platform. * * @param device The save device platform to dispose. * @return Error state if any. */ errorret_t saveDeviceDolphinCardDispose(savedevice_t *device); // One probe result for one of the two ping-pong slots. typedef struct { bool_t valid; uint32_t generation; uint8_t *buffer;// only set if keepBuffer was requested, else NULL uint32_t len; } savedevicedolphincardslot_t; /** * Opens the given card file (if it exists) and validates it as a raw save * blob via saveDeviceRawIsValid(). CARD_Read requires a 32-byte aligned * buffer, so the read (and any buffer kept for the caller) uses memoryAlign * rather than memoryAllocate. If keepBuffer is false, or the file doesn't * exist/validate, no buffer is left allocated. * * @param channel The CARD slot (CARD_SLOTA or CARD_SLOTB) to probe. * @param filename The card file name to open. * @param keepBuffer Whether to keep the read buffer allocated on success. * @return The probe result - valid is false if the file doesn't exist or * doesn't validate as a raw save blob. */ savedevicedolphincardslot_t saveDeviceDolphinCardProbeSlot( const int32_t channel, const char_t *filename, const bool_t keepBuffer ); /** * Writes the combined save data blob out to whichever of the two fixed * card files (see SAVE_DEVICE_DOLPHIN_CARD_FILENAME_0/1 above) is not * currently the valid+newest copy - ping-pong in place of the atomic * rename the other backends use, since the CARD API has neither a rename * nor a way to resize an existing file. * * @param device The save device to write to. * @param buffer The raw bytes to write. * @param size The number of bytes to write. * @return Error state if any. */ errorret_t saveDeviceDolphinCardDataWrite( savedevice_t *device, const uint8_t *buffer, size_t size ); /** * Reads whichever of the two fixed card files is valid and has the higher * generation counter. If neither file exists or validates, this is not an * error - *outBuffer is set to NULL and *outSize to 0. * * @param device The save device to read from. * @param outBuffer Receives a memoryAlign'd (32-byte) buffer the caller * must free with memoryFree, or NULL if nothing has been saved yet. * @param outSize Receives the number of bytes in *outBuffer. * @return Error state if any. */ errorret_t saveDeviceDolphinCardDataRead( savedevice_t *device, uint8_t **outBuffer, size_t *outSize );