d7223d7387
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>
123 lines
3.8 KiB
C
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, §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();
|
|
}
|