Files
dusk/src/duskdolphin/save/savedevicedolphincard.h
T

161 lines
5.6 KiB
C

/**
* 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 <gccore.h>
// 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
);