Strip legacy save/settings systems for a unified savemanager rewrite; split uielement list out of uielement.c

Removes the per-platform save/savestream/autosave implementations, the settings UI frames, and the autosave overlay, replacing them with a savemanager.c/h + savefile.h/savedevice.h scaffold to build the new save system on top of. Also separates the concrete UI_ELEMENTS registration into uielementlist.h/.c so uielement.c only holds the generic per-element lifecycle logic.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 12:56:02 -05:00
parent 6c8e4d5cbd
commit 33f50a2c69
91 changed files with 402 additions and 5074 deletions
-1
View File
@@ -22,6 +22,5 @@ add_subdirectory(input)
if(DUSK_NETWORK)
add_subdirectory(network)
endif()
add_subdirectory(save)
add_subdirectory(system)
add_subdirectory(time)
+1 -2
View File
@@ -9,7 +9,6 @@
#include "assert/assert.h"
#include "log/log.h"
#include "util/string.h"
#include "save/save.h"
inputbuttondata_t INPUT_BUTTON_DATA[] = {
#ifdef DUSK_INPUT_GAMEPAD
@@ -188,5 +187,5 @@ float_t inputButtonGetValueDolphin(const inputbutton_t button) {
}
float_t inputGetDeadzoneDolphin(const inputbutton_t button) {
return saveGetMeta()->deadzone;
return 0.2f;
}
-11
View File
@@ -1,11 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
savedolphin.c
savestreamdolphin.c
)
-168
View File
@@ -1,168 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "save/save.h"
#include "save/savestream.h"
#include "util/memory.h"
#include "util/string.h"
errorret_t saveInitDolphin(void) {
SAVE.platform.mounted = false;
// Must run once before any other CARD_* call: 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).
// Skipping this leaves those structures unset, so CARD_Mount ends up
// touching hardware state that was never brought up -- e.g. Dolphin's
// "Trying to read 32 bits from an invalid MMIO" error -- rather than
// failing cleanly with a CARD_ERROR_* code.
int32_t result = CARD_Init(SAVE_DOLPHIN_GAME_CODE, NULL);
if(result < 0) {
errorThrow("Failed to initialize memory card subsystem: %s (%d)",
saveCardErrorStringDolphin(result), result
);
}
do {
result = CARD_Mount(
SAVE_DOLPHIN_CHANNEL,
SAVE.platform.cardBuffer,
NULL
);
} while(result == CARD_ERROR_BUSY);
// Special-case the failures a player can actually act on; everything
// else falls through to the generic, fully-enumerated message below.
switch(result) {
case CARD_ERROR_NOCARD:
errorThrow("No memory card inserted in the slot");
case CARD_ERROR_WRONGDEVICE:
errorThrow("Unsupported device inserted in the memory card slot");
case CARD_ERROR_BROKEN:
errorThrow("Memory card is damaged or unformatted");
default:
break;
}
if(result < 0) {
errorThrow("Failed to mount memory card: %s (%d)",
saveCardErrorStringDolphin(result), result
);
}
SAVE.platform.mounted = true;
errorOk();
}
errorret_t saveDisposeDolphin(void) {
if(SAVE.platform.mounted) {
CARD_Unmount(SAVE_DOLPHIN_CHANNEL);
SAVE.platform.mounted = false;
}
errorOk();
}
errorret_t saveCombinedLoadDolphin(void) {
savestream_t stream;
memoryZero(&stream, sizeof(savestream_t));
errorret_t openRet = saveStreamOpenReadPlatform(&stream);
SAVE.available = errorIsOk(openRet);
errorChain(openRet);
if(!stream.found) errorOk();
errorret_t ret = saveMetaSerializeRead(&stream, &SAVE.meta);
for(uint8_t i = 0; errorIsOk(ret) && i < SAVE_SLOT_COUNT_MAX; i++) {
ret = saveSlotSerializeRead(&stream, &SAVE.slots[i]);
}
#ifdef saveStreamClosePlatform
saveStreamClosePlatform(&stream);
#endif
errorChain(ret);
errorOk();
}
errorret_t saveCombinedWriteDolphin(void) {
savestream_t stream;
memoryZero(&stream, sizeof(savestream_t));
errorret_t openRet = saveStreamOpenWritePlatform(&stream);
SAVE.available = errorIsOk(openRet);
errorChain(openRet);
errorret_t ret = saveMetaSerializeWrite(&stream, &SAVE.meta);
for(uint8_t i = 0; errorIsOk(ret) && i < SAVE_SLOT_COUNT_MAX; i++) {
ret = saveSlotSerializeWrite(&stream, &SAVE.slots[i]);
}
#ifdef saveStreamClosePlatform
saveStreamClosePlatform(&stream);
#endif
errorChain(ret);
errorOk();
}
errorret_t saveSlotLoadDolphin(const uint8_t slot, saveslot_t *out) {
(void)slot;
(void)out;
return saveCombinedLoadDolphin();
}
errorret_t saveSlotWriteDolphin(const uint8_t slot, saveslot_t *slotData) {
(void)slot;
(void)slotData;
return saveCombinedWriteDolphin();
}
errorret_t saveSlotDeleteDolphin(const uint8_t slot) {
memoryZero(&SAVE.slots[slot], sizeof(saveslot_t));
SAVE.slots[slot].exists = false;
return saveCombinedWriteDolphin();
}
errorret_t saveMetaLoadDolphin(savemeta_t *out) {
(void)out;
return saveCombinedLoadDolphin();
}
errorret_t saveMetaWriteDolphin(savemeta_t *meta) {
(void)meta;
return saveCombinedWriteDolphin();
}
const char_t *saveCardErrorStringDolphin(const int32_t result) {
switch(result) {
case CARD_ERROR_READY: return "card is ready";
case CARD_ERROR_UNLOCKED:
return "card is being unlocked or already unlocked";
case CARD_ERROR_BUSY: return "card is busy";
case CARD_ERROR_WRONGDEVICE: return "wrong device connected in slot";
case CARD_ERROR_NOCARD: return "no memory card in slot";
case CARD_ERROR_NOFILE: return "specified file not found";
case CARD_ERROR_IOERROR: return "internal EXI I/O error";
case CARD_ERROR_BROKEN:
return "directory structure or file entry broken";
case CARD_ERROR_EXIST:
return "file already exists with the specified parameters";
case CARD_ERROR_NOENT:
return "no empty block available to create the file";
case CARD_ERROR_INSSPACE:
return "not enough space to write file to memory card";
case CARD_ERROR_NOPERM:
return "not enough permissions to operate on the file";
case CARD_ERROR_LIMIT: return "card size limit reached";
case CARD_ERROR_NAMETOOLONG: return "filename too long";
case CARD_ERROR_ENCODING: return "font encoding PAL/SJIS mismatch";
case CARD_ERROR_CANCELED: return "card operation canceled";
case CARD_ERROR_FATAL_ERROR: return "fatal error, non-recoverable";
default: return "unknown card error";
}
}
-120
View File
@@ -1,120 +0,0 @@
/**
* 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 "save/saveslot.h"
#include "save/savemeta.h"
#include <gccore.h>
#define SAVE_DOLPHIN_SECTOR_SIZE 8192
#ifndef SAVE_DOLPHIN_GAME_CODE
#define SAVE_DOLPHIN_GAME_CODE "DUSK"
#endif
#ifndef SAVE_DOLPHIN_CHANNEL
#define SAVE_DOLPHIN_CHANNEL CARD_SLOTA
#endif
/**
* Fixed memory card file name holding meta + every save slot, back to
* back, in one file - GameCube memory cards are small enough that one
* consolidated file (rather than one per slot, plus a separate one for
* meta) meaningfully saves card space, and nothing needs true random
* access into just one section (see saveCombinedLoadDolphin()).
*/
#ifndef SAVE_DOLPHIN_FILE_NAME
#define SAVE_DOLPHIN_FILE_NAME "DUSK_SAVE"
#endif
typedef struct {
card_file cardFile;
uint8_t cardBuffer[CARD_WORKAREA] __attribute__((aligned(32)));
bool_t mounted;
} savedolphin_t;
/**
* Initializes the save system on GameCube (memory card slot A by default).
*
* @return An error code if initialization fails.
*/
errorret_t saveInitDolphin(void);
/**
* Disposes of the save system on GameCube.
*
* @return An error code if disposal fails.
*/
errorret_t saveDisposeDolphin(void);
/**
* Reads the one consolidated card file (SAVE_DOLPHIN_FILE_NAME) into
* SAVE.meta and every SAVE.slots[i], in order. Not finding the file is
* not an error - SAVE.meta/SAVE.slots simply keep their compiled
* defaults.
*
* @return An error code if the card is mounted but the read/parse fails.
*/
errorret_t saveCombinedLoadDolphin(void);
/**
* Writes SAVE.meta and every SAVE.slots[i], in order, into the one
* consolidated card file (SAVE_DOLPHIN_FILE_NAME), creating it if needed.
*
* @return An error code if the write fails.
*/
errorret_t saveCombinedWriteDolphin(void);
/**
* Save-slot platform entry point - always (re)reads the whole
* consolidated file (see saveCombinedLoadDolphin()); slot/out are unused
* since every slot is populated in the same pass.
*/
errorret_t saveSlotLoadDolphin(const uint8_t slot, saveslot_t *out);
/**
* Save-slot platform entry point - always (re)writes the whole
* consolidated file (see saveCombinedWriteDolphin()); slot/slotData are
* unused since every slot is written in the same pass.
*/
errorret_t saveSlotWriteDolphin(const uint8_t slot, saveslot_t *slotData);
/**
* Deletes a single save slot's data (zeroes it in memory) and re-writes
* the consolidated file - the file itself always exists as long as any
* slot or meta does, so "delete" can't remove the file wholesale.
*
* @param slot The save slot index.
* @return An error code if the delete fails.
*/
errorret_t saveSlotDeleteDolphin(const uint8_t slot);
/**
* Meta platform entry point - always (re)reads the whole consolidated
* file (see saveCombinedLoadDolphin()); out is unused since meta is
* populated in the same pass.
*/
errorret_t saveMetaLoadDolphin(savemeta_t *out);
/**
* Meta platform entry point - always (re)writes the whole consolidated
* file (see saveCombinedWriteDolphin()); meta is unused since it's
* written in the same pass.
*/
errorret_t saveMetaWriteDolphin(savemeta_t *meta);
/**
* Describes a libogc CARD_ERROR_* result code (see
* https://libogc.devkitpro.org/group__card__errors.html), for logging
* alongside the raw numeric code.
*
* @param result The result code returned by a CARD_* libogc call.
* @return A human-readable description of the result code, or
* "unknown card error" if result doesn't match a known CARD_ERROR_* code.
*/
const char_t *saveCardErrorStringDolphin(const int32_t result);
-37
View File
@@ -1,37 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "save/savedolphin.h"
#include "save/savestreamdolphin.h"
typedef savedolphin_t saveplatform_t;
typedef savestreamdolphin_t saveplatformstream_t;
#define saveInitPlatform saveInitDolphin
#define saveDisposePlatform saveDisposeDolphin
#define saveSlotDeletePlatform saveSlotDeleteDolphin
#define saveSlotLoadPlatform saveSlotLoadDolphin
#define saveSlotWritePlatform saveSlotWriteDolphin
#define saveMetaLoadPlatform saveMetaLoadDolphin
#define saveMetaWritePlatform saveMetaWriteDolphin
#define saveStreamOpenReadPlatform(stream) \
saveStreamOpenReadDolphin(&(stream)->platform, &(stream)->found)
#define saveStreamOpenWritePlatform(stream) \
saveStreamOpenWriteDolphin(&(stream)->platform)
#define saveStreamClosePlatform(stream) \
saveStreamCloseDolphin(&(stream)->platform)
#define saveStreamReadBytesPlatform(stream, buf, len) \
saveStreamReadBytesDolphin(&(stream)->platform, buf, len)
#define saveStreamWriteBytesPlatform(stream, buf, len) \
saveStreamWriteBytesDolphin(&(stream)->platform, buf, len)
#define saveStreamSeekPlatform(stream, pos) \
saveStreamSeekDolphin(&(stream)->platform, pos)
#define saveStreamTellPlatform(stream, out) \
saveStreamTellDolphin(&(stream)->platform, out)
-120
View File
@@ -1,120 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "save/save.h"
#include "save/savestreamdolphin.h"
#include "util/memory.h"
errorret_t saveStreamOpenReadDolphin(savestreamdolphin_t *p, bool_t *found) {
if(!SAVE.platform.mounted) {
*found = false;
errorThrow("No memory card mounted");
}
int32_t result;
do {
result = CARD_Open(
SAVE_DOLPHIN_CHANNEL, SAVE_DOLPHIN_FILE_NAME, &p->cardFile
);
} while(result == CARD_ERROR_BUSY);
if(result == CARD_ERROR_NOFILE) {
*found = false;
p->position = 0;
p->writing = false;
errorOk();
}
if(result < 0) {
*found = false;
errorThrow("Failed to open memory card file: %s (%d)",
saveCardErrorStringDolphin(result), result
);
}
do {
result = CARD_Read(&p->cardFile, p->buffer, SAVE_DOLPHIN_SECTOR_SIZE, 0);
} while(result == CARD_ERROR_BUSY);
CARD_Close(&p->cardFile);
if(result < 0) {
*found = false;
errorThrow("Failed to read memory card data: %s (%d)",
saveCardErrorStringDolphin(result), result
);
}
*found = true;
p->position = 0;
p->writing = false;
errorOk();
}
errorret_t saveStreamOpenWriteDolphin(savestreamdolphin_t *p) {
if(!SAVE.platform.mounted) errorThrow("No memory card mounted");
memoryZero(p->buffer, SAVE_DOLPHIN_SECTOR_SIZE);
p->position = 0;
p->writing = true;
errorOk();
}
void saveStreamCloseDolphin(savestreamdolphin_t *p) {
if(!p->writing) return;
int32_t result;
do {
result = CARD_Open(
SAVE_DOLPHIN_CHANNEL, SAVE_DOLPHIN_FILE_NAME, &p->cardFile
);
} while(result == CARD_ERROR_BUSY);
if(result == CARD_ERROR_NOFILE) {
do {
result = CARD_Create(
SAVE_DOLPHIN_CHANNEL, SAVE_DOLPHIN_FILE_NAME,
SAVE_DOLPHIN_SECTOR_SIZE, &p->cardFile
);
} while(result == CARD_ERROR_BUSY);
}
do {
result = CARD_Write(&p->cardFile, p->buffer, SAVE_DOLPHIN_SECTOR_SIZE, 0);
} while(result == CARD_ERROR_BUSY);
CARD_Close(&p->cardFile);
}
errorret_t saveStreamReadBytesDolphin(
savestreamdolphin_t *p, void *buf, const size_t len
) {
if(p->position + len > SAVE_DOLPHIN_SECTOR_SIZE) {
errorThrow("Save stream read exceeds sector size");
}
memoryCopy(buf, p->buffer + p->position, len);
p->position += len;
errorOk();
}
errorret_t saveStreamWriteBytesDolphin(
savestreamdolphin_t *p, const void *buf, const size_t len
) {
if(p->position + len > SAVE_DOLPHIN_SECTOR_SIZE) {
errorThrow("Save stream write exceeds sector size");
}
memoryCopy(p->buffer + p->position, buf, len);
p->position += len;
errorOk();
}
errorret_t saveStreamSeekDolphin(savestreamdolphin_t *p, const size_t pos) {
if(pos >= SAVE_DOLPHIN_SECTOR_SIZE) {
errorThrow("Save stream seek out of range");
}
p->position = pos;
errorOk();
}
errorret_t saveStreamTellDolphin(savestreamdolphin_t *p, size_t *out) {
*out = p->position;
errorOk();
}
-94
View File
@@ -1,94 +0,0 @@
/**
* 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 "save/savedolphin.h"
#include <gccore.h>
#include <stddef.h>
typedef struct {
/** libogc memory card file handle. */
card_file cardFile;
/** In-memory sector buffer; all reads and writes operate on this. */
uint8_t buffer[SAVE_DOLPHIN_SECTOR_SIZE] __attribute__((aligned(32)));
/** Current read/write position within buffer. */
size_t position;
/** True when opened for writing; flushes buffer to card on close. */
bool_t writing;
} savestreamdolphin_t;
/**
* Opens the consolidated memory card file for reading by loading its
* sector into buffer.
*
* @param p Stream to initialize.
* @param found Set to true if the file exists, false if it does not.
* @return An error if reading the card fails for a reason other than
* missing file.
*/
errorret_t saveStreamOpenReadDolphin(savestreamdolphin_t *p, bool_t *found);
/**
* Opens the consolidated memory card file for writing by zeroing the
* sector buffer. The buffer is flushed to the card when
* saveStreamCloseDolphin is called.
*
* @param p Stream to initialize.
* @return An error if initialization fails.
*/
errorret_t saveStreamOpenWriteDolphin(savestreamdolphin_t *p);
/**
* Flushes the sector buffer to the memory card (write mode only) and
* releases the card file handle.
*
* @param p Stream to close.
*/
void saveStreamCloseDolphin(savestreamdolphin_t *p);
/**
* Copies len bytes from the sector buffer at the current position into buf.
*
* @param p Active stream.
* @param buf Destination buffer.
* @param len Number of bytes to read.
* @return An error if the read would exceed the sector size.
*/
errorret_t saveStreamReadBytesDolphin(
savestreamdolphin_t *p, void *buf, const size_t len
);
/**
* Copies len bytes from buf into the sector buffer at the current position.
*
* @param p Active stream.
* @param buf Source buffer.
* @param len Number of bytes to write.
* @return An error if the write would exceed the sector size.
*/
errorret_t saveStreamWriteBytesDolphin(
savestreamdolphin_t *p, const void *buf, const size_t len
);
/**
* Sets the current read/write position within the sector buffer.
*
* @param p Active stream.
* @param pos Target byte offset from the start of the sector.
* @return An error if pos is out of range.
*/
errorret_t saveStreamSeekDolphin(savestreamdolphin_t *p, const size_t pos);
/**
* Gets the current read/write position within the sector buffer.
*
* @param p Active stream.
* @param out Receives the current position.
* @return An error - always succeeds, matches saveStreamTellImpl's shape.
*/
errorret_t saveStreamTellDolphin(savestreamdolphin_t *p, size_t *out);
+8
View File
@@ -39,4 +39,12 @@ int32_t systemGetAspectRatioDolphin(void) {
int32_t systemGetLanguageDolphin(void) {
return CONF_GetLanguage();
}
const localeinfo_t * systemGetLocaleDolphin(void) {
switch(systemGetLanguageDolphin()) {
case CONF_LANG_JAPANESE: return &LOCALE_INFO_JP_JP;
case CONF_LANG_SPANISH: return &LOCALE_INFO_ES_MX;
default: return &LOCALE_DEFAULT;
}
}
+8
View File
@@ -47,5 +47,13 @@ int32_t systemGetAspectRatioDolphin(void);
*/
int32_t systemGetLanguageDolphin(void);
/**
* Returns the locale matching the system's configured language, falling
* back to LOCALE_DEFAULT if the system language has no matching locale.
*
* @return The current locale.
*/
const localeinfo_t * systemGetLocaleDolphin(void);
// There's actually a tonne more things Wii can return, this is it for now
// though.
+2 -1
View File
@@ -9,4 +9,5 @@
#include "system/systemdolphin.h"
#define systemInitPlatform systemInitDolphin
#define systemGetActiveDialogTypePlatform systemGetActiveDialogTypeDolphin
#define systemGetActiveDialogTypePlatform systemGetActiveDialogTypeDolphin
#define systemGetLocalePlatform systemGetLocaleDolphin