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>
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 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, §orSize) < 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();
|
||||
}
|
||||
Reference in New Issue
Block a user