Fix GameCube memory card slot B detection and free-space check

CARD_Mount() alone reliably reports CARD_ERROR_NOCARD for a card physically
present in slot B - unlike slot A, which the IPL polls automatically at
boot, slot B needs an explicit CARD_Probe() first (the standard pattern in
every official devkitPro CARD sample). Also fixes
saveDeviceDolphinCardHasFreeSpace() treating CARD_GetDirectory()'s
CARD_ERROR_NOFILE (a totally empty card, not an actual error) as a hard
failure, which made any card with zero existing save files - slot B's in
this case, since slot A already had leftover test data on it - get
reported as full.

Confirmed via Dolphin with slot A disabled/slot B set to a memory card:
previously reported "no save device found", now correctly detects it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 16:49:18 -05:00
parent d7223d7387
commit e3f10e0926
2 changed files with 40 additions and 15 deletions
+31 -15
View File
@@ -52,6 +52,21 @@ void saveDeviceDolphinCardCheckAvailability(savedevice_t *device) {
return saveDeviceFireCallback(device);
}
// Slot A gets polled automatically by the IPL at boot, but slot B does
// not - CARD_Mount() alone reliably reports CARD_ERROR_NOCARD for a
// present slot B card unless CARD_Probe() is used to explicitly detect
// it first (matches the probe-then-mount pattern in every official
// devkitPro CARD sample, not something slot A happens to need too).
do {
result = CARD_Probe(platform->channel);
} while(result == CARD_ERROR_BUSY);
if(result < 0) {
device->state = SAVE_DEVICE_STATE_UNAVAILABLE;
device->reasonKey = saveDeviceDolphinCardErrorReasonKey(result);
return saveDeviceFireCallback(device);
}
do {
result = CARD_Mount(platform->channel, platform->cardBuffer, NULL);
} while(result == CARD_ERROR_BUSY);
@@ -59,20 +74,7 @@ void saveDeviceDolphinCardCheckAvailability(savedevice_t *device) {
if(result < 0) {
platform->mounted = false;
device->state = SAVE_DEVICE_STATE_UNAVAILABLE;
switch(result) {
case CARD_ERROR_NOCARD:
device->reasonKey = "save.dolphin.no_card";
break;
case CARD_ERROR_WRONGDEVICE:
device->reasonKey = "save.dolphin.wrong_device";
break;
case CARD_ERROR_BROKEN:
device->reasonKey = "save.dolphin.broken";
break;
default:
device->reasonKey = "save.dolphin.mount_failed";
break;
}
device->reasonKey = saveDeviceDolphinCardErrorReasonKey(result);
return saveDeviceFireCallback(device);
}
platform->mounted = true;
@@ -88,6 +90,15 @@ void saveDeviceDolphinCardCheckAvailability(savedevice_t *device) {
saveDeviceFireCallback(device);
}
const char_t *saveDeviceDolphinCardErrorReasonKey(const int32_t result) {
switch(result) {
case CARD_ERROR_NOCARD: return "save.dolphin.no_card";
case CARD_ERROR_WRONGDEVICE: return "save.dolphin.wrong_device";
case CARD_ERROR_BROKEN: return "save.dolphin.broken";
default: return "save.dolphin.mount_failed";
}
}
bool_t saveDeviceDolphinCardHasFreeSpace(const int32_t channel) {
uint16_t blockCount = 0;
if(CARD_GetBlockCount(channel, &blockCount) < 0) return false;
@@ -101,7 +112,12 @@ bool_t saveDeviceDolphinCardHasFreeSpace(const int32_t channel) {
// game itself has already written.
card_dir entries[CARD_MAXFILES];
int32_t count = 0;
if(CARD_GetDirectory(channel, entries, &count, true) < 0) return false;
int32_t dirResult = CARD_GetDirectory(channel, entries, &count, true);
// A totally empty card (no files at all yet) reports CARD_ERROR_NOFILE
// here rather than success with count 0 - not a real error, and by far
// the most common state for a card nobody has saved to yet.
if(dirResult < 0 && dirResult != CARD_ERROR_NOFILE) return false;
if(dirResult == CARD_ERROR_NOFILE) count = 0;
if(count >= CARD_MAXFILES) return false;// No free file entry left either.
uint32_t usedBlocks = 0;
@@ -53,6 +53,15 @@ errorret_t saveDeviceDolphinCardUpdate(savedevice_t *device);
*/
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