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:
2026-08-14 16:20:28 -05:00
parent 45331c2a60
commit d7223d7387
16 changed files with 670 additions and 2 deletions
@@ -0,0 +1,75 @@
/**
* 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
typedef struct {
// Set from this device's index into SAVE_MANAGER.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);
/**
* 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);