Files
dusk/src/duskdolphin/save/savedevicedolphincard.c
T
YourWishes d7223d7387 Add PSP/GameCube/Wii save-device availability checks
PSP checks memory-stick reachability via sceIoGetstat. GameCube gets two
devices (CARD_SLOTA/CARD_SLOTB), each mounted via CARD_Init/CARD_Mount and
checked for free space via CARD_GetDirectory/CARD_GetBlockCount. Wii picks
between three storage methods at compile time (DUSK_SAVE_WII_METHOD =
NAND/CARD/SD in wii.cmake, default NAND via ISFS) since real hardware
behavior for the default is unverified.

Also fixes two pre-existing bugs found while build/runtime-testing this
across PPSSPP and Dolphin: wrong libogc language macros in
systemGetLocaleDolphin, and a missing SYS_STDIO_Report(true) call that
silently swallowed all guest console output in Dolphin.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 16:20:28 -05:00

123 lines
3.8 KiB
C

/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "save/savedevice.h"
#include "save/savedevicedolphincard.h"
#include "save/savemanager.h"
#include "assert/assert.h"
errorret_t saveDeviceDolphinCardInit(savedevice_t *device) {
assertNotNull(device, "device cannot be null");
device->state = SAVE_DEVICE_STATE_UNKNOWN;
// Each savedevice_t in SAVE_MANAGER.devices maps to one physical memory
// card slot - device 0 is slot A, device 1 is slot B, determined by this
// device's position in that array (the same index math savemanager.c
// itself uses to identify a device).
uint8_t index = (uint8_t)(device - &SAVE_MANAGER.devices[0]);
device->platform.channel = index == 0 ? CARD_SLOTA : CARD_SLOTB;
errorOk();
}
errorret_t saveDeviceDolphinCardUpdate(savedevice_t *device) {
assertNotNull(device, "device cannot be null");
assertIsMainThread("Invalid thread");
errorOk();
}
void saveDeviceDolphinCardCheckAvailability(savedevice_t *device) {
assertNotNull(device, "device cannot be null");
assertIsMainThread("Invalid thread");
assertTrue(
device->state == SAVE_DEVICE_STATE_CHECKING_AVAILABILITY,
"Device state incorrect?"
);
savedeviceplatform_t *platform = &device->platform;
// CARD_Init 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) - safe to call more than once, and cheap
// enough to just always call before every mount attempt.
int32_t result = CARD_Init(SAVE_DEVICE_DOLPHIN_GAME_CODE, NULL);
if(result < 0) {
device->state = SAVE_DEVICE_STATE_UNAVAILABLE;
device->reasonKey = "save.dolphin.card_init_failed";
return saveDeviceFireCallback(device);
}
do {
result = CARD_Mount(platform->channel, platform->cardBuffer, NULL);
} while(result == CARD_ERROR_BUSY);
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;
}
return saveDeviceFireCallback(device);
}
platform->mounted = true;
if(!saveDeviceDolphinCardHasFreeSpace(platform->channel)) {
device->state = SAVE_DEVICE_STATE_UNAVAILABLE;
device->reasonKey = "save.dolphin.card_full";
return saveDeviceFireCallback(device);
}
device->state = SAVE_DEVICE_STATE_AVAILABLE;
device->reasonKey = NULL;
saveDeviceFireCallback(device);
}
bool_t saveDeviceDolphinCardHasFreeSpace(const int32_t channel) {
uint16_t blockCount = 0;
if(CARD_GetBlockCount(channel, &blockCount) < 0) return false;
uint32_t sectorSize = 0;
if(CARD_GetSectorSize(channel, &sectorSize) < 0 || sectorSize == 0) {
return false;
}
// showall=true - free space is a card-wide resource, not just what this
// game itself has already written.
card_dir entries[CARD_MAXFILES];
int32_t count = 0;
if(CARD_GetDirectory(channel, entries, &count, true) < 0) return false;
if(count >= CARD_MAXFILES) return false;// No free file entry left either.
uint32_t usedBlocks = 0;
for(int32_t i = 0; i < count; i++) {
usedBlocks += (entries[i].filelen + sectorSize - 1) / sectorSize;
}
return usedBlocks < (uint32_t)blockCount;
}
errorret_t saveDeviceDolphinCardDispose(savedevice_t *device) {
assertNotNull(device, "device cannot be null");
if(device->platform.mounted) {
CARD_Unmount(device->platform.channel);
device->platform.mounted = false;
}
errorOk();
}