From 33f50a2c69c20e6586067bde50cfbef9a106640f Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Fri, 14 Aug 2026 12:56:02 -0500 Subject: [PATCH] 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 --- src/dusk/engine/engine.c | 10 +- src/dusk/locale/localeinfo.h | 22 +- src/dusk/locale/localemanager.c | 38 +- src/dusk/locale/localemanager.h | 32 +- src/dusk/rpg/entity/global/CMakeLists.txt | 1 - src/dusk/rpg/entity/global/globalitemstore.c | 25 - src/dusk/rpg/entity/global/globalitemstore.h | 40 -- src/dusk/rpg/rpg.c | 29 +- src/dusk/rpg/story/storyflag.c | 25 +- src/dusk/rpg/story/storyflag.h | 18 +- src/dusk/save/CMakeLists.txt | 4 +- src/dusk/save/autosave.c | 72 --- src/dusk/save/autosave.h | 57 -- src/dusk/save/save.c | 188 ------- src/dusk/save/save.h | 210 ------- src/dusk/save/savedevice.h | 22 + src/dusk/save/savefile.h | 10 + src/dusk/save/savemanager.c | 16 + src/dusk/save/savemanager.h | 26 + src/dusk/save/savemeta.h | 63 --- src/dusk/save/saveslot.h | 100 ---- src/dusk/save/savestream.c | 403 -------------- src/dusk/save/savestream.h | 511 ------------------ src/dusk/scene/initial/sceneinitial.c | 58 -- src/dusk/system/system.c | 8 + src/dusk/system/system.h | 10 +- src/dusk/ui/CMakeLists.txt | 1 + src/dusk/ui/frame/CMakeLists.txt | 1 - src/dusk/ui/frame/game/uigamemenu.c | 93 +--- src/dusk/ui/frame/mainmenu/uimainmenu.c | 4 - src/dusk/ui/frame/settings/CMakeLists.txt | 13 - src/dusk/ui/frame/settings/uisettings.c | 250 --------- src/dusk/ui/frame/settings/uisettings.h | 133 ----- src/dusk/ui/frame/settings/uisettingsaudio.c | 57 -- src/dusk/ui/frame/settings/uisettingsaudio.h | 36 -- src/dusk/ui/frame/settings/uisettingsdata.h | 73 --- .../ui/frame/settings/uisettingsdisplay.c | 57 -- .../ui/frame/settings/uisettingsdisplay.h | 36 -- .../ui/frame/settings/uisettingsgeneral.c | 108 ---- .../ui/frame/settings/uisettingsgeneral.h | 36 -- src/dusk/ui/frame/settings/uisettingsinput.c | 92 ---- src/dusk/ui/frame/settings/uisettingsinput.h | 36 -- src/dusk/ui/overlay/CMakeLists.txt | 1 - src/dusk/ui/overlay/uiautosave.c | 45 -- src/dusk/ui/overlay/uiautosave.h | 34 -- src/dusk/ui/rpg/textbox/uitextboxmini.c | 2 +- src/dusk/ui/rpg/textbox/uitextboxmini.h | 2 +- src/dusk/ui/ui.c | 10 + src/dusk/ui/ui.h | 10 +- src/dusk/ui/uielement.c | 152 +----- src/dusk/ui/uielement.h | 17 +- src/dusk/ui/uielementlist.c | 139 +++++ src/dusk/ui/uielementlist.h | 11 + src/duskdolphin/CMakeLists.txt | 1 - src/duskdolphin/input/inputdolphin.c | 3 +- src/duskdolphin/save/CMakeLists.txt | 11 - src/duskdolphin/save/savedolphin.c | 168 ------ src/duskdolphin/save/savedolphin.h | 120 ---- src/duskdolphin/save/saveplatform.h | 37 -- src/duskdolphin/save/savestreamdolphin.c | 120 ---- src/duskdolphin/save/savestreamdolphin.h | 94 ---- src/duskdolphin/system/systemdolphin.c | 8 + src/duskdolphin/system/systemdolphin.h | 8 + src/duskdolphin/system/systemplatform.h | 3 +- src/dusklinux/CMakeLists.txt | 1 - src/dusklinux/input/inputlinux.c | 3 +- src/dusklinux/save/CMakeLists.txt | 11 - src/dusklinux/save/savejsonlinux.c | 175 ------ src/dusklinux/save/savejsonlinux.h | 162 ------ src/dusklinux/save/savelinux.c | 150 ----- src/dusklinux/save/savelinux.h | 80 --- src/dusklinux/save/saveplatform.h | 27 - src/dusklinux/system/systemlinux.c | 4 + src/dusklinux/system/systemlinux.h | 10 +- src/dusklinux/system/systemplatform.h | 3 +- src/duskpsp/CMakeLists.txt | 1 - src/duskpsp/input/inputpsp.c | 3 +- src/duskpsp/save/CMakeLists.txt | 19 - src/duskpsp/save/saveplatform.h | 53 -- src/duskpsp/save/savepsp.c | 291 ---------- src/duskpsp/save/savepsp.h | 133 ----- src/duskpsp/save/savestreampsp.c | 46 -- src/duskpsp/save/savestreampsp.h | 64 --- src/duskpsp/system/systemplatform.h | 3 +- src/duskpsp/system/systempsp.c | 8 + src/duskpsp/system/systempsp.h | 10 +- src/duskvita/input/inputvita.c | 3 +- src/duskvita/save/CMakeLists.txt | 11 - src/duskvita/save/saveplatform.h | 30 - src/duskvita/save/savestreamvita.c | 77 --- src/duskvita/save/savestreamvita.h | 78 --- 91 files changed, 402 insertions(+), 5074 deletions(-) delete mode 100644 src/dusk/rpg/entity/global/globalitemstore.c delete mode 100644 src/dusk/rpg/entity/global/globalitemstore.h delete mode 100644 src/dusk/save/autosave.c delete mode 100644 src/dusk/save/autosave.h delete mode 100644 src/dusk/save/save.c delete mode 100644 src/dusk/save/save.h create mode 100644 src/dusk/save/savedevice.h create mode 100644 src/dusk/save/savefile.h create mode 100644 src/dusk/save/savemanager.c create mode 100644 src/dusk/save/savemanager.h delete mode 100644 src/dusk/save/savemeta.h delete mode 100644 src/dusk/save/saveslot.h delete mode 100644 src/dusk/save/savestream.c delete mode 100644 src/dusk/save/savestream.h delete mode 100644 src/dusk/ui/frame/settings/CMakeLists.txt delete mode 100644 src/dusk/ui/frame/settings/uisettings.c delete mode 100644 src/dusk/ui/frame/settings/uisettings.h delete mode 100644 src/dusk/ui/frame/settings/uisettingsaudio.c delete mode 100644 src/dusk/ui/frame/settings/uisettingsaudio.h delete mode 100644 src/dusk/ui/frame/settings/uisettingsdata.h delete mode 100644 src/dusk/ui/frame/settings/uisettingsdisplay.c delete mode 100644 src/dusk/ui/frame/settings/uisettingsdisplay.h delete mode 100644 src/dusk/ui/frame/settings/uisettingsgeneral.c delete mode 100644 src/dusk/ui/frame/settings/uisettingsgeneral.h delete mode 100644 src/dusk/ui/frame/settings/uisettingsinput.c delete mode 100644 src/dusk/ui/frame/settings/uisettingsinput.h delete mode 100644 src/dusk/ui/overlay/uiautosave.c delete mode 100644 src/dusk/ui/overlay/uiautosave.h create mode 100644 src/dusk/ui/uielementlist.c create mode 100644 src/dusk/ui/uielementlist.h delete mode 100644 src/duskdolphin/save/CMakeLists.txt delete mode 100644 src/duskdolphin/save/savedolphin.c delete mode 100644 src/duskdolphin/save/savedolphin.h delete mode 100644 src/duskdolphin/save/saveplatform.h delete mode 100644 src/duskdolphin/save/savestreamdolphin.c delete mode 100644 src/duskdolphin/save/savestreamdolphin.h delete mode 100644 src/dusklinux/save/CMakeLists.txt delete mode 100644 src/dusklinux/save/savejsonlinux.c delete mode 100644 src/dusklinux/save/savejsonlinux.h delete mode 100644 src/dusklinux/save/savelinux.c delete mode 100644 src/dusklinux/save/savelinux.h delete mode 100644 src/dusklinux/save/saveplatform.h delete mode 100644 src/duskpsp/save/CMakeLists.txt delete mode 100644 src/duskpsp/save/saveplatform.h delete mode 100644 src/duskpsp/save/savepsp.c delete mode 100644 src/duskpsp/save/savepsp.h delete mode 100644 src/duskpsp/save/savestreampsp.c delete mode 100644 src/duskpsp/save/savestreampsp.h delete mode 100644 src/duskvita/save/CMakeLists.txt delete mode 100644 src/duskvita/save/saveplatform.h delete mode 100644 src/duskvita/save/savestreamvita.c delete mode 100644 src/duskvita/save/savestreamvita.h diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index 18fd686c..5a986a25 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -21,8 +21,7 @@ #endif #include "system/system.h" #include "console/console.h" -#include "save/save.h" -#include "save/autosave.h" +#include "save/savemanager.h"\ engine_t ENGINE; @@ -40,7 +39,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { errorChain(systemInit()); errorChain(inputInit()); errorChain(assetInit()); - errorChain(saveInit()); + errorChain(saveManagerInit()); errorChain(localeManagerInit()); errorChain(displayInit()); errorChain(uiInit()); @@ -68,8 +67,7 @@ errorret_t engineUpdate(void) { #ifdef DUSK_NETWORK errorChain(networkUpdate()); #endif - errorChain(saveUpdate()); - autoSaveUpdate(); + // errorChain(saveManagerUpdate()); timeUpdate(); inputUpdate(); consoleUpdate(); @@ -98,7 +96,7 @@ errorret_t engineDispose(void) { errorChain(uiDispose()); consoleDispose(); errorChain(displayDispose()); - errorChain(saveDispose()); + errorChain(saveManagerDispose()); errorChain(assetDispose()); errorOk(); diff --git a/src/dusk/locale/localeinfo.h b/src/dusk/locale/localeinfo.h index 5f9d36ed..db935b68 100644 --- a/src/dusk/locale/localeinfo.h +++ b/src/dusk/locale/localeinfo.h @@ -13,17 +13,31 @@ typedef struct { const char_t *file; } localeinfo_t; -static const localeinfo_t LOCALE_EN_US = { +static const localeinfo_t LOCALE_INFO_EN_US = { .name = "en-US", .file = "locale/en_US.po", }; -static const localeinfo_t LOCALE_JP_JP = { +static const localeinfo_t LOCALE_INFO_JP_JP = { .name = "ja-JP", .file = "locale/jp_JP.po", }; -static const localeinfo_t LOCALE_ES_MX = { +static const localeinfo_t LOCALE_INFO_ES_MX = { .name = "es-MX", .file = "locale/es_MX.po", -}; \ No newline at end of file +}; + +static const localeinfo_t * const LOCALE_INFO_LIST[] = { + &LOCALE_INFO_EN_US, + &LOCALE_INFO_JP_JP, + &LOCALE_INFO_ES_MX +}; + +#define LOCALE_INFO_LIST_COUNT ( \ + sizeof(LOCALE_INFO_LIST) / sizeof(LOCALE_INFO_LIST[0]) \ +) + +#define LOCALE_DEFAULT LOCALE_INFO_EN_US + +// EOF diff --git a/src/dusk/locale/localemanager.c b/src/dusk/locale/localemanager.c index cef38a0d..878a7e53 100644 --- a/src/dusk/locale/localemanager.c +++ b/src/dusk/locale/localemanager.c @@ -9,40 +9,23 @@ #include "util/memory.h" #include "util/string.h" #include "assert/assert.h" -#include "save/save.h" +#include "ui/ui.h" +#include "system/system.h" +#include "console/console.h" localemanager_t LOCALE; -const localeinfo_t * const LOCALE_LIST[LOCALE_LIST_COUNT] = { - &LOCALE_EN_US, - &LOCALE_JP_JP, - &LOCALE_ES_MX -}; - errorret_t localeManagerInit() { memoryZero(&LOCALE, sizeof(localemanager_t)); - // saveInit() runs before this (see engine.c) and, on most platforms, - // has already loaded a persisted language choice into savemeta_t by - // this point - see localeManagerGetByIndex(). - errorChain(localeManagerSetLocale( - localeManagerGetByIndex(saveGetMeta()->language) - )); + + // TODO: Set locale based on system locale. + const localeinfo_t *locale = systemGetLocale(); + errorChain(localeManagerSetLocale(locale)); + consolePrint("Locale set to: %s", locale->name); + errorOk(); } -uint8_t localeManagerGetIndex(const localeinfo_t *locale) { - assertNotNull(locale, "Locale cannot be NULL"); - for(uint8_t i = 0; i < LOCALE_LIST_COUNT; i++) { - if(stringCompare(LOCALE_LIST[i]->file, locale->file) == 0) return i; - } - return 0; -} - -const localeinfo_t * localeManagerGetByIndex(const uint8_t index) { - if(index >= LOCALE_LIST_COUNT) return LOCALE_LIST[0]; - return LOCALE_LIST[index]; -} - errorret_t localeManagerSetLocale(const localeinfo_t *locale) { assertNotNull(locale, "Locale cannot be NULL"); @@ -56,6 +39,9 @@ errorret_t localeManagerSetLocale(const localeinfo_t *locale) { assetEntryLock(LOCALE.entry); errorChain(assetRequireLoaded(LOCALE.entry)); + // TODO : Trigger UI update. + errorChain(uiUpdateTranslations()); + errorOk(); } diff --git a/src/dusk/locale/localemanager.h b/src/dusk/locale/localemanager.h index 836d3372..4f5e9a1a 100644 --- a/src/dusk/locale/localemanager.h +++ b/src/dusk/locale/localemanager.h @@ -7,7 +7,7 @@ #pragma once #include "error/error.h" -#include "localemanager.h" +#include "locale/localemanager.h" #include "locale/localeinfo.h" #include "asset/asset.h" @@ -18,15 +18,6 @@ typedef struct { extern localemanager_t LOCALE; -/** - * Every locale the game supports, in a fixed, stable order - index into - * this array is what gets persisted as savemeta_t.language (see - * save/savemeta.h), so the order here must never change once shipped - * (only append new locales at the end). - */ -#define LOCALE_LIST_COUNT 3 -extern const localeinfo_t * const LOCALE_LIST[LOCALE_LIST_COUNT]; - /** * Initialize the locale system. * @@ -42,27 +33,6 @@ errorret_t localeManagerInit(); */ errorret_t localeManagerSetLocale(const localeinfo_t *locale); -/** - * Finds the LOCALE_LIST index matching the given locale's file, by - * content rather than pointer identity (localeinfo_t instances are - * declared `static const` in a header, so the same locale gets a - * distinct pointer in every translation unit that references it). - * - * @param locale The locale to find. - * @return The matching index in LOCALE_LIST, or 0 if not found. - */ -uint8_t localeManagerGetIndex(const localeinfo_t *locale); - -/** - * Gets the locale at the given LOCALE_LIST index, clamping to index 0 - * (LOCALE_EN_US) if out of range - e.g. a save file's persisted language - * index from a future build with more locales than this one supports. - * - * @param index The index to look up. - * @return The locale at that index, or LOCALE_LIST[0] if out of range. - */ -const localeinfo_t * localeManagerGetByIndex(const uint8_t index); - /** * Get a localized string for the given message ID. * diff --git a/src/dusk/rpg/entity/global/CMakeLists.txt b/src/dusk/rpg/entity/global/CMakeLists.txt index b5f1a268..9b35b01d 100644 --- a/src/dusk/rpg/entity/global/CMakeLists.txt +++ b/src/dusk/rpg/entity/global/CMakeLists.txt @@ -6,5 +6,4 @@ # Sources target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC - globalitemstore.c ) \ No newline at end of file diff --git a/src/dusk/rpg/entity/global/globalitemstore.c b/src/dusk/rpg/entity/global/globalitemstore.c deleted file mode 100644 index c4b468d4..00000000 --- a/src/dusk/rpg/entity/global/globalitemstore.c +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "globalitemstore.h" -#include "assert/assert.h" - -bool_t globalItemStoreIsCollected( - const saveslot_t *file, const entityglobalid_t id -) { - assertNotNull(file, "Save slot cannot be NULL"); - assertTrue(id < SAVE_GLOBAL_ITEM_COUNT_MAX, "Global item ID out of range"); - return file->globalItemCollected[id]; -} - -void globalItemStoreSetCollected( - saveslot_t *file, const entityglobalid_t id, const bool_t collected -) { - assertNotNull(file, "Save slot cannot be NULL"); - assertTrue(id < SAVE_GLOBAL_ITEM_COUNT_MAX, "Global item ID out of range"); - file->globalItemCollected[id] = collected; -} diff --git a/src/dusk/rpg/entity/global/globalitemstore.h b/src/dusk/rpg/entity/global/globalitemstore.h deleted file mode 100644 index ad6577ec..00000000 --- a/src/dusk/rpg/entity/global/globalitemstore.h +++ /dev/null @@ -1,40 +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 "rpg/entity/entity.h" - -/** - * Checks whether the global entity with the given ID has already been - * marked collected in the given save slot's data - e.g. so a global item - * entity's init callback (see rpg/entity/global/entitygloballist.h) can - * skip spawning itself if the player already picked it up in a prior - * session, without needing to keep the entity itself alive to remember - * that (which would need render/collision special-casing - this doesn't). - * - * @param file The save slot to check. - * @param id The global entity ID to check. - * @return True if already marked collected. - */ -bool_t globalItemStoreIsCollected( - const saveslot_t *file, const entityglobalid_t id -); - -/** - * Marks the global entity with the given ID as collected (or not) in the - * given save slot's data. Does not itself write the save to disk - call - * saveWriteSlot() separately once ready to persist it. - * - * @param file The save slot to write into. - * @param id The global entity ID to mark. - * @param collected The new collected state. - */ -void globalItemStoreSetCollected( - saveslot_t *file, const entityglobalid_t id, const bool_t collected -); diff --git a/src/dusk/rpg/rpg.c b/src/dusk/rpg/rpg.c index de235212..2a8fef40 100644 --- a/src/dusk/rpg/rpg.c +++ b/src/dusk/rpg/rpg.c @@ -17,11 +17,8 @@ #include "rpgcamera.h" #include "util/memory.h" #include "assert/assert.h" -#include "save/save.h" -#include "save/autosave.h" #include "error/error.h" #include "scene/scene.h" - #include "ui/rpg/uiemoji.h" #include "rpg/story/storyflag.h" @@ -29,27 +26,14 @@ errorret_t rpgInit(void) { memoryZero(ENTITIES, sizeof(ENTITIES)); memoryZero(MAP_AREAS, sizeof(MAP_AREAS)); - saveslot_t *saveSlot = saveGetSlot(SAVE_ACTIVE_SLOT); - // saveInit() eagerly loads every slot from disk, but there's no - // continue-game flow yet - every boot is a fresh game regardless of - // what was found on disk, so force this false rather than let a stale - // "exists" from a real save skip storyFlagInitDefaults() below while - // everything else here still hardcodes new-game state. - saveSlot->exists = false; - - // Must run before any code reads a story flag - stamps CSV-defined - // defaults onto the active save slot since it's now forced to look - // unloaded. - storyFlagInitDefaults(saveSlot); - backpackInit(); partyInit(); cutsceneSystemInit(); - errorChain(mapInit()); - rpgCameraInit(); - // Init world + + + // Init test world errorChain(mapPositionSet((chunkpos_t){ 0, 0, 0 })); // The player is the one entity that isn't sourced from map/chunk data - @@ -68,6 +52,7 @@ errorret_t rpgInit(void) { backpackAdd(ITEM_ID_POTATO, 3); backpackAdd(ITEM_ID_APPLE, 8); + // All Good! errorOk(); } @@ -79,12 +64,6 @@ errorret_t rpgUpdate(void) { } #endif - // A failed autosave forces a no-save-device prompt (see autosave.h) - - // freeze the world entirely until the player resolves it, rather than - // letting NPCs/cutscenes/camera keep running behind a modal that's - // supposed to be blocking. - if(autoSaveIsBlocking()) errorOk(); - // TODO: Do not update if the scene is not the map scene? errorChain(mapUpdate()); diff --git a/src/dusk/rpg/story/storyflag.c b/src/dusk/rpg/story/storyflag.c index 4a152f48..6b91a769 100644 --- a/src/dusk/rpg/story/storyflag.c +++ b/src/dusk/rpg/story/storyflag.c @@ -7,20 +7,17 @@ #include "storyflag.h" #include "assert/assert.h" +#include "console/console.h" + +storyflagvalue_t storyFlagGet(const storyflag_t flag) { + assertTrue(flag > STORY_FLAG_NULL && flag < STORY_FLAG_COUNT, "Bad Flag"); + consolePrint("Story flag get without save implementation"); + return 0; +} void storyFlagSet(const storyflag_t flag, const storyflagvalue_t value) { assertTrue(flag > STORY_FLAG_NULL && flag < STORY_FLAG_COUNT, "Bad Flag"); - saveGetSlot(SAVE_ACTIVE_SLOT)->storyFlags[flag] = value; -} - -void storyFlagInitDefaults(saveslot_t *file) { - assertNotNull(file, "Save slot cannot be NULL"); - if(file->exists) return; - assertTrue( - STORY_FLAG_COUNT <= SAVE_STORY_FLAG_COUNT_MAX, - "Too many story flags for the save format - bump SAVE_STORY_FLAG_COUNT_MAX" - ); - for(storyflag_t i = 0; i < STORY_FLAG_COUNT; i++) { - file->storyFlags[i] = STORY_FLAG_DEFAULTS[i]; - } -} + + // TODO: Dirty savefile + consolePrint("Story flag set without save implementation"); +} \ No newline at end of file diff --git a/src/dusk/rpg/story/storyflag.h b/src/dusk/rpg/story/storyflag.h index e202f6d1..f320d5ef 100644 --- a/src/dusk/rpg/story/storyflag.h +++ b/src/dusk/rpg/story/storyflag.h @@ -7,7 +7,6 @@ #pragma once #include "rpg/story/storyflagvalue.h" -#include "save/save.h" /** * Gets the value of a story flag. Reads directly from the active save @@ -16,25 +15,12 @@ * @param flag The story flag to get. * @return The value of the story flag. */ -#define storyFlagGet(flag) (saveGetSlot(SAVE_ACTIVE_SLOT)->storyFlags[(flag)]) +storyflagvalue_t storyFlagGet(const storyflag_t flag); /** - * Sets the value of a story flag, directly in the active save slot (see - * SAVE_ACTIVE_SLOT). Does not itself write the save to disk - call - * saveWriteSlot() separately once ready to persist it. + * Sets the value of a story flag. Will dirty the savefile. * * @param flag The story flag to set. * @param value The value to set the story flag to. */ void storyFlagSet(const storyflag_t flag, const storyflagvalue_t value); - -/** - * Stamps each story flag's CSV-defined default (STORY_FLAG_DEFAULTS) onto - * the given save slot, but only if it hasn't actually been loaded from - * disk yet (file->exists is false) - otherwise leaves already-played - * progress alone. Call once, e.g. during rpgInit(), before any gameplay - * code reads a story flag. - * - * @param file The save slot to stamp defaults onto. - */ -void storyFlagInitDefaults(saveslot_t *file); diff --git a/src/dusk/save/CMakeLists.txt b/src/dusk/save/CMakeLists.txt index d2b6fc74..c31cadaf 100644 --- a/src/dusk/save/CMakeLists.txt +++ b/src/dusk/save/CMakeLists.txt @@ -6,7 +6,5 @@ # Sources target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC - save.c - savestream.c - autosave.c + savemanager.c ) diff --git a/src/dusk/save/autosave.c b/src/dusk/save/autosave.c deleted file mode 100644 index 52bf54a2..00000000 --- a/src/dusk/save/autosave.c +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "autosave.h" -#include "save.h" -#include "error/error.h" -#include "ui/frame/initial/uiinitialnocard.h" - -autosave_t AUTO_SAVE; - -static void autoSaveNoCardResult(const bool_t retry, void *user) { - AUTO_SAVE.blocking = false; - - // "Retry" just re-queues the write for the next tick - there's no way - // to force a re-probe of the hardware mid-session (see saveInit()'s doc - // comment), so this only actually helps for a transient failure. - if(retry) { - AUTO_SAVE.pending = true; - return; - } - - // The player explicitly acknowledged there's no save device and chose - // to proceed anyway - sticks for the rest of the session, so future - // autoSaveUpdate() calls silently skip instead of prompting again. - saveMarkTemporary(); -} - -static void autoSaveWriteComplete(errorret_t result, void *user) { - AUTO_SAVE.saving = false; - if(errorIsOk(result)) return; - - errorCatch(errorPrint(result)); - - // The write failed - most likely no save medium is present right now - // (a GameCube with no memory card inserted, for example). Pause world - // simulation and force the same prompt the initial boot scene uses so - // the player can insert a card and retry, or explicitly accept a - // temporary, unsaved session. - AUTO_SAVE.blocking = true; - uiInitialNoCardOpen(autoSaveNoCardResult, NULL); -} - -void autoSaveQueue(void) { - AUTO_SAVE.pending = true; -} - -void autoSaveUpdate(void) { - if(AUTO_SAVE.blocking) return; - if(!AUTO_SAVE.pending) return; - if(saveIsBusy()) return; - - AUTO_SAVE.pending = false; - - // Already opted out of saving for this session - nothing to do, and - // definitely don't reprompt every time an autosave is queued. - if(saveIsTemporary()) return; - - AUTO_SAVE.saving = true; - saveWriteSlot(SAVE_ACTIVE_SLOT, autoSaveWriteComplete, NULL); -} - -bool_t autoSaveIsSaving(void) { - return AUTO_SAVE.saving; -} - -bool_t autoSaveIsBlocking(void) { - return AUTO_SAVE.blocking; -} diff --git a/src/dusk/save/autosave.h b/src/dusk/save/autosave.h deleted file mode 100644 index 1bfcf826..00000000 --- a/src/dusk/save/autosave.h +++ /dev/null @@ -1,57 +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" - -typedef struct { - /** True from autoSaveQueue() until the queued write starts. */ - bool_t pending; - /** True while the queued write is actually in flight. */ - bool_t saving; - /** - * True while the forced no-save-device prompt is up after a queued - * write failed - see autoSaveUpdate(). World simulation must pause - * while this is true (see rpgUpdate()). - */ - bool_t blocking; -} autosave_t; - -extern autosave_t AUTO_SAVE; - -/** - * Queues an autosave to run on a future engine tick. Safe to call as - * often as needed (e.g. after every map transition or story event) - - * repeated calls before the queued save starts just collapse into one. - */ -void autoSaveQueue(void); - -/** - * Pumps the queued autosave: starts the write once nothing else is - * using the save system, and if that write fails (e.g. a GameCube with - * no memory card inserted), forces the same no-save-device prompt the - * initial boot scene uses and pauses world simulation until the player - * resolves it. Must be called every engine frame, after saveUpdate(). - */ -void autoSaveUpdate(void); - -/** - * True while a queued autosave's write is actually in progress. Intended - * for UI to show a "Saving" indicator. - * - * @return true if an autosave write is currently in flight. - */ -bool_t autoSaveIsSaving(void); - -/** - * True while a failed autosave is forcing the no-save-device prompt and - * waiting on the player's decision. Intended for the world simulation to - * pause while this is true. - * - * @return true if an autosave is currently blocking on player input. - */ -bool_t autoSaveIsBlocking(void); diff --git a/src/dusk/save/save.c b/src/dusk/save/save.c deleted file mode 100644 index 07bfa51d..00000000 --- a/src/dusk/save/save.c +++ /dev/null @@ -1,188 +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 "assert/assert.h" -#include "error/error.h" - -save_t SAVE; - -const saveslot_t SAVE_DEFAULT = { - .header = { - SAVE_SLOT_HEADER[0], SAVE_SLOT_HEADER[1], SAVE_SLOT_HEADER[2] - }, - .version = SAVE_SLOT_VERSION, - .exists = true, - .playerName = "Player" - // globalItemCollected/storyFlags are left at their zero default. -}; - -static void _saveEagerLoadComplete(errorret_t result, void *user) { - if(errorIsNotOk(result)) errorCatch(errorPrint(result)); -} - -errorret_t saveInit(void) { - memoryZero(&SAVE, sizeof(save_t)); - SAVE.meta.deadzone = SAVE_META_DEADZONE_DEFAULT; - SAVE.meta.language = SAVE_META_LANGUAGE_DEFAULT; - - #ifdef saveInitPlatform - // A missing/unreachable save medium is expected, recoverable state, - // not a reason to fail booting the whole game - log it and carry on - // with SAVE.available false instead of chaining the error upward. - errorret_t result = saveInitPlatform(); - SAVE.available = errorIsOk(result); - if(!SAVE.available) errorCatch(errorPrint(result)); - #else - SAVE.available = false; - #endif - - // Eagerly pull meta + every slot into memory up front - cheap and - // harmless (nothing consumes loaded slot data automatically; there's no - // continue-game flow yet). PSP opts out entirely (see - // saveSkipEagerLoadPlatform) since its only read path is now the native - // savedata dialog, and running that on every boot would defeat the whole - // point of folding meta into it instead of a separate instant-write file. - #ifndef saveSkipEagerLoadPlatform - if(SAVE.available) { - saveLoadMeta(_saveEagerLoadComplete, NULL); - for(uint8_t i = 0; i < SAVE_SLOT_COUNT_MAX; i++) { - saveLoadSlot(i, _saveEagerLoadComplete, NULL); - } - } - #endif - - errorOk(); -} - -bool_t saveIsAvailable(void) { - return SAVE.available && !SAVE.temporary; -} - -void saveMarkTemporary(void) { - SAVE.temporary = true; -} - -bool_t saveIsTemporary(void) { - return SAVE.temporary; -} - -errorret_t saveDispose(void) { - #ifdef saveDisposePlatform - errorChain(saveDisposePlatform()); - #endif - errorOk(); -} - -errorret_t saveUpdate(void) { - #ifdef savePlatformUpdate - errorChain(savePlatformUpdate()); - #endif - errorOk(); -} - -bool_t saveIsBusy(void) { - #ifdef saveIsBusyPlatform - return saveIsBusyPlatform(); - #else - return false; - #endif -} - -void saveLoadSlot(const uint8_t slot, savecallback_t onComplete, void *user) { - assertTrue(slot < SAVE_SLOT_COUNT_MAX, "slot exceeds SAVE_SLOT_COUNT_MAX"); - assertNotNull(onComplete, "onComplete cannot be NULL"); - - SAVE.slots[slot].exists = false; - - // Some platforms (PSP's native save dialog) can't complete within this - // call - they take over entirely and invoke onComplete later, from - // saveUpdate(), once their own multi-frame flow finishes. Those - // platforms never define the sync saveSlotLoadPlatform() at all, so the - // fallback below must live in the #else, not just after an early return. - #ifdef saveSlotAsyncLoadPlatform - saveSlotAsyncLoadPlatform(slot, onComplete, user); - #else - errorret_t ret = saveSlotLoadPlatform(slot, &SAVE.slots[slot]); - SAVE.available = errorIsOk(ret); - onComplete(ret, user); - #endif -} - -void saveLoadDefault(const uint8_t slot) { - assertTrue(slot < SAVE_SLOT_COUNT_MAX, "slot exceeds SAVE_SLOT_COUNT_MAX"); - SAVE.slots[slot] = SAVE_DEFAULT; -} - -void saveWriteSlot(const uint8_t slot, savecallback_t onComplete, void *user) { - assertTrue(slot < SAVE_SLOT_COUNT_MAX, "slot exceeds SAVE_SLOT_COUNT_MAX"); - assertNotNull(onComplete, "onComplete cannot be NULL"); - - // See saveLoadSlot() - some platforms take over and complete later. - #ifdef saveSlotAsyncWritePlatform - saveSlotAsyncWritePlatform(slot, onComplete, user); - #else - errorret_t ret = saveSlotWritePlatform(slot, &SAVE.slots[slot]); - SAVE.available = errorIsOk(ret); - onComplete(ret, user); - #endif -} - -errorret_t saveDeleteSlot(const uint8_t slot) { - assertTrue(slot < SAVE_SLOT_COUNT_MAX, "slot exceeds SAVE_SLOT_COUNT_MAX"); - - #ifdef saveSlotDeletePlatform - errorret_t deleteRet = saveSlotDeletePlatform(slot); - SAVE.available = errorIsOk(deleteRet); - errorChain(deleteRet); - #endif - - SAVE.slots[slot].exists = false; - errorOk(); -} - -bool_t saveSlotExists(const uint8_t slot) { - assertTrue(slot < SAVE_SLOT_COUNT_MAX, "slot exceeds SAVE_SLOT_COUNT_MAX"); - return SAVE.slots[slot].exists; -} - -saveslot_t * saveGetSlot(const uint8_t slot) { - assertTrue(slot < SAVE_SLOT_COUNT_MAX, "slot exceeds SAVE_SLOT_COUNT_MAX"); - return &SAVE.slots[slot]; -} - -void saveLoadMeta(savecallback_t onComplete, void *user) { - assertNotNull(onComplete, "onComplete cannot be NULL"); - - SAVE.meta.exists = false; - - #ifdef saveMetaAsyncLoadPlatform - saveMetaAsyncLoadPlatform(onComplete, user); - #else - errorret_t ret = saveMetaLoadPlatform(&SAVE.meta); - SAVE.available = errorIsOk(ret); - onComplete(ret, user); - #endif -} - -void saveWriteMeta(savecallback_t onComplete, void *user) { - assertNotNull(onComplete, "onComplete cannot be NULL"); - - #ifdef saveMetaAsyncWritePlatform - saveMetaAsyncWritePlatform(onComplete, user); - #else - errorret_t ret = saveMetaWritePlatform(&SAVE.meta); - SAVE.available = errorIsOk(ret); - onComplete(ret, user); - #endif -} - -savemeta_t * saveGetMeta(void) { - return &SAVE.meta; -} diff --git a/src/dusk/save/save.h b/src/dusk/save/save.h deleted file mode 100644 index 483dcf43..00000000 --- a/src/dusk/save/save.h +++ /dev/null @@ -1,210 +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 "saveslot.h" -#include "savemeta.h" -#include "save/saveplatform.h" - -typedef struct { - /** Per-slot save data; indexed 0 to SAVE_SLOT_COUNT_MAX - 1. */ - saveslot_t slots[SAVE_SLOT_COUNT_MAX]; - /** Device-wide preferences - see savemeta.h. */ - savemeta_t meta; - /** Platform-specific save system state (paths, card handles, etc.). */ - saveplatform_t platform; - /** - * True if the save medium (memory card/stick/disk) was reachable the - * last time it was checked - at saveInit(), and refreshed by every - * subsequent load/write attempt. Starting the game with no card/stick - * inserted, or one being removed mid-session, are both expected - * conditions here, not fatal errors - see saveIsAvailable(). - */ - bool_t available; - /** - * True once the player has explicitly acknowledged there's no save - * device and chosen to continue anyway (see the initial scene's "no - * card" prompt) - sticky for the rest of the session, folded into - * saveIsAvailable() so every existing/future call site that already - * gates on that automatically refuses to save/load from here on. See - * saveMarkTemporary()/saveIsTemporary(). - */ - bool_t temporary; - /** - * Scratch error state used by platforms whose save/load completes - * asynchronously (see saveIsBusy()) to construct a result to hand to a - * savecallback_t from inside saveUpdate(), rather than from a direct - * errorThrow() return - mirrors network_t.errorState for the same reason. - */ - errorstate_t errorState; -} save_t; - -extern save_t SAVE; - -/** - * Initializes the save system. Never fails the way saveWriteSlot()/ - * saveWriteMeta() can - if the platform's save medium isn't reachable - * (e.g. no memory card/stick inserted), that's logged and reflected in - * saveIsAvailable() rather than treated as fatal, since the game should - * still be playable without save support. - * - * On most platforms, this also eagerly loads meta and every slot from - * disk immediately - cheap and harmless, since nothing consumes the - * loaded slot data automatically (there's no "continue game" flow yet; - * rpgInit() always hardcodes a fresh game regardless of what's loaded). - * PSP skips this (see saveSkipEagerLoadPlatform) - its only read path is - * the native sceUtilitySavedata dialog now that meta lives inside the - * same payload as the save slot, and running that multi-frame dialog on - * every single boot would reintroduce the exact UX problem a lightweight - * settings-only file used to avoid. - * - * @return An error code only for unexpected platform failures. - */ -errorret_t saveInit(void); - -/** - * Checks whether the save medium was reachable as of the last load/write - * attempt (or saveInit(), if none has been attempted yet), AND the - * session hasn't been marked temporary (see saveMarkTemporary()). - * Intended for UI to decide whether to offer saving/loading at all, or to - * explain why it isn't available right now - e.g. "No memory card - * inserted". - * - * @return true if saving/loading can be attempted right now. - */ -bool_t saveIsAvailable(void); - -/** - * Marks this session as temporary - the player explicitly acknowledged - * there's no save device and chose to continue anyway. One-way: nothing - * currently re-probes the save medium mid-session (see saveInit()'s doc - * comment), so there's no legitimate way to un-stick this once set short - * of restarting the game. - */ -void saveMarkTemporary(void); - -/** - * True once saveMarkTemporary() has been called this session. Distinct - * from saveIsAvailable() so UI can give a more specific message (e.g. - * "this session is temporary" rather than the generic "no save device - * found") once the player has already made that choice. - * - * @return true if this session has been marked temporary. - */ -bool_t saveIsTemporary(void); - -/** - * Disposes of the save system. - * - * @return An error code if disposal fails. - */ -errorret_t saveDispose(void); - -/** - * Updates the save manager, pumping any in-progress async save/load and - * dispatching its callback once complete. No-op on platforms where every - * operation always completes synchronously (see saveIsBusy()). Must be - * called every engine frame for platforms that need it (PSP's native save - * dialog spans multiple frames). - * - * @return An error code indicating success or failure. - */ -errorret_t saveUpdate(void); - -/** - * True while an async save/load is in progress (e.g. PSP's native save - * dialog is open). Calling any save/load function again while this is true - * is undefined behavior - wait for the previous call's callback first. - * - * @return True if a save/load request is currently in progress. - */ -bool_t saveIsBusy(void); - -/** - * Loads the save slot for a given index from persistent storage. Slow/ - * async on some platforms (PSP's native save dialog spans multiple - * frames) - on others (Linux, Dolphin) onComplete is invoked before this - * call returns. See saveIsBusy(). - * - * @param slot The save slot index (0 to SAVE_SLOT_COUNT_MAX - 1). - * @param onComplete Callback invoked with the result once loading finishes. - * @param user User data passed through to onComplete. - */ -void saveLoadSlot(const uint8_t slot, savecallback_t onComplete, void *user); - -/** - * Resets a save slot in memory to SAVE_DEFAULT, for starting a new game - * without reading anything from persistent storage. Synchronous - no - * disk I/O is involved, so there's no callback to wait on. - * - * @param slot The save slot index (0 to SAVE_SLOT_COUNT_MAX - 1) to reset. - */ -void saveLoadDefault(const uint8_t slot); - -/** - * Writes the save slot for a given index to persistent storage. Slow/ - * async on some platforms (PSP's native save dialog spans multiple - * frames) - on others (Linux, Dolphin) onComplete is invoked before this - * call returns. See saveIsBusy(). - * - * @param slot The save slot index (0 to SAVE_SLOT_COUNT_MAX - 1). - * @param onComplete Callback invoked with the result once writing finishes. - * @param user User data passed through to onComplete. - */ -void saveWriteSlot(const uint8_t slot, savecallback_t onComplete, void *user); - -/** - * Deletes the save slot for a given index from persistent storage. - * - * @param slot The save slot index (0 to SAVE_SLOT_COUNT_MAX - 1). - * @return An error code if the delete fails. - */ -errorret_t saveDeleteSlot(const uint8_t slot); - -/** - * Checks whether a save slot has data for a given index. - * - * @param slot The save slot index (0 to SAVE_SLOT_COUNT_MAX - 1). - * @return true if the slot has data, false otherwise. - */ -bool_t saveSlotExists(const uint8_t slot); - -/** - * Gets a pointer to the save slot data for a given index. - * - * @param slot The save slot index (0 to SAVE_SLOT_COUNT_MAX - 1). - * @return A pointer to the saveslot_t for the given slot. - */ -saveslot_t * saveGetSlot(const uint8_t slot); - -/** - * Loads device-wide meta (preferences) from persistent storage. Callback- - * based on every platform, same as saveLoadSlot() - on PSP specifically, - * loading meta means loading the same combined savedata payload as the - * active slot, which is unavoidably async there. - * - * @param onComplete Callback invoked with the result once loading finishes. - * @param user User data passed through to onComplete. - */ -void saveLoadMeta(savecallback_t onComplete, void *user); - -/** - * Writes device-wide meta (preferences) to persistent storage. - * - * @param onComplete Callback invoked with the result once writing finishes. - * @param user User data passed through to onComplete. - */ -void saveWriteMeta(savecallback_t onComplete, void *user); - -/** - * Gets a pointer to the live meta data. Modify fields directly, then call - * saveWriteMeta() to persist them. - * - * @return A pointer to the meta. - */ -savemeta_t * saveGetMeta(void); diff --git a/src/dusk/save/savedevice.h b/src/dusk/save/savedevice.h new file mode 100644 index 00000000..724cbda2 --- /dev/null +++ b/src/dusk/save/savedevice.h @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" + +typedef enum { + SAVE_DEVICE_STATE_UNKNOWN, + SAVE_DEVICE_STATE_UNAVAILABLE, + SAVE_DEVICE_STATE_AVAILABLE, + SAVE_DEVICE_STATE_MOUNTING, + SAVE_DEVICE_STATE_MOUNTED, + SAVE_DEVICE_STATE_ERRORED +} savedevicestate_t; + +typedef struct { + savedevicestate_t state; +} savedevice_t; \ No newline at end of file diff --git a/src/dusk/save/savefile.h b/src/dusk/save/savefile.h new file mode 100644 index 00000000..b592deeb --- /dev/null +++ b/src/dusk/save/savefile.h @@ -0,0 +1,10 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" + diff --git a/src/dusk/save/savemanager.c b/src/dusk/save/savemanager.c new file mode 100644 index 00000000..22e7c69a --- /dev/null +++ b/src/dusk/save/savemanager.c @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "savemanager.h" + +errorret_t saveManagerInit() { + errorOk(); +} + +errorret_t saveManagerDispose() { + errorOk(); +} \ No newline at end of file diff --git a/src/dusk/save/savemanager.h b/src/dusk/save/savemanager.h new file mode 100644 index 00000000..9ef7199c --- /dev/null +++ b/src/dusk/save/savemanager.h @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" + +typedef struct { + void *nothing; +} savemanager_t; + +extern savemanager_t SAVE_MANAGER; + +/** + * Initializes the save manager, this does not do anything related to mounting + * files, memory cards, etc, this is entirely prepping for that capability. + */ +errorret_t saveManagerInit(); + +/** + * Disposes of the save manager. + */ +errorret_t saveManagerDispose(); \ No newline at end of file diff --git a/src/dusk/save/savemeta.h b/src/dusk/save/savemeta.h deleted file mode 100644 index 895e4fdb..00000000 --- a/src/dusk/save/savemeta.h +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -/** Save meta format version. Increment on breaking change. */ -#define SAVE_META_VERSION 1 - -/** Magic bytes that identify a Dusk save meta blob. */ -#define SAVE_META_HEADER "DSM" - -/** Byte length of the magic header (excludes the null terminator). */ -#define SAVE_META_HEADER_SIZE (sizeof(SAVE_META_HEADER) - 1) - -/** - * Default gamepad deadzone for meta that's never actually been loaded from - * disk yet (see saveInit(), which stamps this on first boot) - the save - * meta is the single source of truth for this value (see - * savemeta_t.deadzone); nothing else stores or defaults it. - */ -#define SAVE_META_DEADZONE_DEFAULT 0.1f - -/** - * Default language index for meta that's never actually been loaded from - * disk yet - index 0 into LOCALE_LIST (see locale/localemanager.h), i.e. - * LOCALE_EN_US. - */ -#define SAVE_META_LANGUAGE_DEFAULT 0 - -/** - * Device/user-wide preferences, independent of any individual game save - * slot (see saveslot.h) - there's exactly one of these, not one per slot, - * since a setting like gamepad deadzone shouldn't reset or diverge just - * because the player started a new game in a different slot. - */ -typedef struct { - /** Magic header bytes read from the blob; must equal SAVE_META_HEADER. */ - char_t header[SAVE_META_HEADER_SIZE]; - /** Format version read from the blob; used to branch on older layouts. */ - uint32_t version; - /** Runtime flag - true if meta was successfully loaded or written. */ - bool_t exists; - /** - * User-configured gamepad deadzone (0.0f-1.0f) - the save meta is the - * only place this lives; read it directly via saveGetMeta()->deadzone - * rather than caching it anywhere else. - */ - float_t deadzone; - /** - * Index into LOCALE_LIST (see locale/localemanager.h) for the player's - * preferred UI language - the save meta is the only place this lives; - * read it directly via saveGetMeta()->language rather than caching it - * anywhere else. Never reorder/remove entries from LOCALE_LIST, only - * append, since this index must keep meaning the same locale across - * versions. - */ - uint8_t language; -} savemeta_t; diff --git a/src/dusk/save/saveslot.h b/src/dusk/save/saveslot.h deleted file mode 100644 index abc306cb..00000000 --- a/src/dusk/save/saveslot.h +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -/** Save slot format version. Increment on breaking change. */ -#define SAVE_SLOT_VERSION 1 - -/** Magic bytes that identify a Dusk save slot. */ -#define SAVE_SLOT_HEADER "DSK" - -/** Byte length of the magic header (excludes the null terminator). */ -#define SAVE_SLOT_HEADER_SIZE (sizeof(SAVE_SLOT_HEADER) - 1) - -/** - * Maximum number of independent save slots supported. Platform-overridable - * via a compiler define (not a header #ifndef alone, since this header is - * included before any platform header gets a chance to react) - see PSP's - * CMakeLists.txt, which overrides this to 1. - */ -#ifndef SAVE_SLOT_COUNT_MAX - #define SAVE_SLOT_COUNT_MAX 3 -#endif - -/** - * The save slot actually used for gameplay right now - there's no slot - * select/multi-save UX yet (SAVE_SLOT_COUNT_MAX > 1 exists for later), so - * every part of the game that needs "the" save slot (the game menu's Save - * button, etc.) reads/writes this one slot. - */ -#define SAVE_ACTIVE_SLOT 0 - -/** Maximum length of a saved player name, including the null terminator. */ -#define SAVE_PLAYER_NAME_MAX 32 - -/** - * Maximum number of global entities whose "collected" state can be - * tracked - see rpg/entity/global/globalitemstore.h. Bounded/fixed here - * rather than tied to ENTITY_GLOBAL_LIST_COUNT, since saveslot.h is a - * leaf header with no dependency on the entity system (and no reason to - * take one just for a size constant). - */ -#define SAVE_GLOBAL_ITEM_COUNT_MAX 64 - -/** - * Maximum number of story flags the save format can hold - see - * rpg/story/storyflag.h. Bounded/fixed here (with real headroom over the - * current flag count) rather than tied to STORY_FLAG_COUNT, since - * saveslot.h is a leaf header with no dependency on generated story - * content, matching SAVE_GLOBAL_ITEM_COUNT_MAX's reasoning. - */ -#define SAVE_STORY_FLAG_COUNT_MAX 128 - -/** Per-slot game progress - the state a "save file" traditionally means. */ -typedef struct { - /** Magic header bytes read from the slot; must equal SAVE_SLOT_HEADER. */ - char_t header[SAVE_SLOT_HEADER_SIZE]; - /** Format version read from the slot; used to branch on older layouts. */ - uint32_t version; - /** Runtime flag - true if this slot was successfully loaded or written. */ - bool_t exists; - /** The player's saved name. */ - char_t playerName[SAVE_PLAYER_NAME_MAX]; - /** Per-global-ID "already collected" flags - see globalitemstore.h. */ - bool_t globalItemCollected[SAVE_GLOBAL_ITEM_COUNT_MAX]; - /** - * Story flag values, indexed by storyflag_t - the save slot is the only - * place these live; read/write via storyFlagGet()/storyFlagSet() (see - * rpg/story/storyflag.h), not directly. - */ - uint8_t storyFlags[SAVE_STORY_FLAG_COUNT_MAX]; -} saveslot_t; - -/** - * Callback invoked when an async saveWriteSlot()/saveLoadSlot()/ - * saveWriteMeta()/saveLoadMeta() request completes. Declared here (rather - * than save.h) so platform save headers - which save.h's platform - * indirection pulls in before save.h finishes defining anything else - can - * reference it without a circular include. - * - * @param result Whether the request succeeded. - * @param user User data passed through from the original call. - */ -typedef void (*savecallback_t)(errorret_t result, void *user); - -/** - * The blank template used to (re)initialize a save slot for a brand new - * game via saveLoadDefault(), rather than reading one from persistent - * storage. Deliberately generic at this layer (empty collected-item - * flags, no story flags set) - anything that needs CSV-defined story flag - * defaults layers that on top afterward (see storyFlagInitDefaults()), - * since saveslot.h stays a leaf header with no dependency on generated - * story content (see SAVE_STORY_FLAG_COUNT_MAX's doc comment above). - */ -extern const saveslot_t SAVE_DEFAULT; diff --git a/src/dusk/save/savestream.c b/src/dusk/save/savestream.c deleted file mode 100644 index 2da1b070..00000000 --- a/src/dusk/save/savestream.c +++ /dev/null @@ -1,403 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "save/savestream.h" -#include "util/crypt.h" -#include "util/endian.h" -#include "util/string.h" -#include "util/memory.h" - -errorret_t saveStreamReadBytesRawImpl( - savestream_t *stream, void *buf, const size_t len -) { - #ifdef saveStreamReadBytesPlatform - errorChain(saveStreamReadBytesPlatform(stream, buf, len)); - #endif - errorOk(); -} - -errorret_t saveStreamWriteBytesRawImpl( - savestream_t *stream, const void *buf, const size_t len -) { - #ifdef saveStreamWriteBytesPlatform - errorChain(saveStreamWriteBytesPlatform(stream, buf, len)); - #endif - errorOk(); -} - -errorret_t saveStreamReadBytesImpl( - savestream_t *stream, void *buf, const size_t len -) { - errorChain(saveStreamReadBytesRawImpl(stream, buf, len)); - cryptCRC32Update(&stream->checksum, buf, len); - errorOk(); -} - -errorret_t saveStreamWriteBytesImpl( - savestream_t *stream, const void *buf, const size_t len -) { - cryptCRC32Update(&stream->checksum, buf, len); - errorChain(saveStreamWriteBytesRawImpl(stream, buf, len)); - errorOk(); -} - -errorret_t saveStreamTellImpl(savestream_t *stream, size_t *out) { - #ifdef saveStreamTellPlatform - errorChain(saveStreamTellPlatform(stream, out)); - #else - *out = 0; - #endif - errorOk(); -} - -errorret_t saveStreamFinalizeWriteImpl( - savestream_t *stream, const size_t headerPosition, const size_t headerSize -) { - uint32_t finalCRC = cryptCRC32End(stream->checksum); - uint32_t leChecksum = endianLittleToHost32(finalCRC); - - #ifdef saveStreamSeekPlatform - errorChain(saveStreamSeekPlatform(stream, headerPosition + headerSize)); - #endif - - errorChain(saveStreamWriteBytesRawImpl( - stream, &leChecksum, sizeof(uint32_t) - )); - errorOk(); -} - -errorret_t saveStreamVerifyChecksumImpl( - savestream_t *stream, const char_t *sectionLabel -) { - uint32_t computed = cryptCRC32End(stream->checksum); - if(computed != stream->expectedChecksum) { - errorThrow("%s has invalid checksum", sectionLabel); - } - errorOk(); -} - -errorret_t saveStreamReadHeaderImpl( - savestream_t *stream, char_t *header, const char_t *expectedHeader, - const size_t headerSize -) { - errorChain(saveStreamReadBytesRawImpl(stream, header, headerSize)); - - for(size_t i = 0; i < headerSize; i++) { - if(header[i] != expectedHeader[i]) { - errorThrow("Save data has invalid header"); - } - } - - uint32_t leChecksum; - errorChain(saveStreamReadBytesRawImpl(stream, &leChecksum, sizeof(uint32_t))); - stream->expectedChecksum = endianLittleToHost32(leChecksum); - stream->checksum = cryptCRC32Begin(); - errorOk(); -} - -errorret_t saveStreamWriteHeaderImpl( - savestream_t *stream, const char_t *header, const size_t headerSize -) { - errorChain(saveStreamWriteBytesRawImpl(stream, header, headerSize)); - - uint32_t placeholder = 0; - errorChain(saveStreamWriteBytesRawImpl( - stream, &placeholder, sizeof(uint32_t) - )); - stream->checksum = cryptCRC32Begin(); - errorOk(); -} - -errorret_t saveStreamReadVersionImpl(savestream_t *stream, uint32_t *out) { - uint32_t raw; - errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint32_t))); - *out = endianLittleToHost32(raw); - errorOk(); -} - -errorret_t saveStreamWriteVersionImpl( - savestream_t *stream, const uint32_t *input -) { - uint32_t raw = endianLittleToHost32(*input); - errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint32_t))); - errorOk(); -} - -errorret_t saveStreamReadBoolImpl(savestream_t *stream, bool_t *out) { - uint8_t raw; - errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint8_t))); - *out = (bool_t)(raw != 0); - errorOk(); -} - -errorret_t saveStreamWriteBoolImpl(savestream_t *stream, const bool_t *input) { - uint8_t raw = *input ? 1 : 0; - errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint8_t))); - errorOk(); -} - -errorret_t saveStreamReadInt8Impl(savestream_t *stream, int8_t *out) { - errorChain(saveStreamReadBytesImpl(stream, out, sizeof(int8_t))); - errorOk(); -} - -errorret_t saveStreamWriteInt8Impl(savestream_t *stream, const int8_t *input) { - errorChain(saveStreamWriteBytesImpl(stream, input, sizeof(int8_t))); - errorOk(); -} - -errorret_t saveStreamReadUInt8Impl(savestream_t *stream, uint8_t *out) { - errorChain(saveStreamReadBytesImpl(stream, out, sizeof(uint8_t))); - errorOk(); -} - -errorret_t saveStreamWriteUInt8Impl( - savestream_t *stream, const uint8_t *input -) { - errorChain(saveStreamWriteBytesImpl(stream, input, sizeof(uint8_t))); - errorOk(); -} - -errorret_t saveStreamReadInt16Impl(savestream_t *stream, int16_t *out) { - uint16_t raw; - errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint16_t))); - uint16_t host = endianLittleToHost16(raw); - memoryCopy(out, &host, sizeof(int16_t)); - errorOk(); -} - -errorret_t saveStreamWriteInt16Impl( - savestream_t *stream, const int16_t *input -) { - uint16_t raw; - memoryCopy(&raw, input, sizeof(int16_t)); - raw = endianLittleToHost16(raw); - errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint16_t))); - errorOk(); -} - -errorret_t saveStreamReadUInt16Impl(savestream_t *stream, uint16_t *out) { - uint16_t raw; - errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint16_t))); - *out = endianLittleToHost16(raw); - errorOk(); -} - -errorret_t saveStreamWriteUInt16Impl( - savestream_t *stream, const uint16_t *input -) { - uint16_t raw = endianLittleToHost16(*input); - errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint16_t))); - errorOk(); -} - -errorret_t saveStreamReadInt32Impl(savestream_t *stream, int32_t *out) { - uint32_t raw; - errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint32_t))); - uint32_t host = endianLittleToHost32(raw); - memoryCopy(out, &host, sizeof(int32_t)); - errorOk(); -} - -errorret_t saveStreamWriteInt32Impl( - savestream_t *stream, const int32_t *input -) { - uint32_t raw; - memoryCopy(&raw, input, sizeof(int32_t)); - raw = endianLittleToHost32(raw); - errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint32_t))); - errorOk(); -} - -errorret_t saveStreamReadUInt32Impl(savestream_t *stream, uint32_t *out) { - uint32_t raw; - errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint32_t))); - *out = endianLittleToHost32(raw); - errorOk(); -} - -errorret_t saveStreamWriteUInt32Impl( - savestream_t *stream, const uint32_t *input -) { - uint32_t raw = endianLittleToHost32(*input); - errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint32_t))); - errorOk(); -} - -errorret_t saveStreamReadInt64Impl(savestream_t *stream, int64_t *out) { - uint64_t raw; - errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint64_t))); - uint64_t host = endianLittleToHost64(raw); - memoryCopy(out, &host, sizeof(int64_t)); - errorOk(); -} - -errorret_t saveStreamWriteInt64Impl( - savestream_t *stream, const int64_t *input -) { - uint64_t raw; - memoryCopy(&raw, input, sizeof(int64_t)); - raw = endianLittleToHost64(raw); - errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint64_t))); - errorOk(); -} - -errorret_t saveStreamReadUInt64Impl(savestream_t *stream, uint64_t *out) { - uint64_t raw; - errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint64_t))); - *out = endianLittleToHost64(raw); - errorOk(); -} - -errorret_t saveStreamWriteUInt64Impl( - savestream_t *stream, const uint64_t *input -) { - uint64_t raw = endianLittleToHost64(*input); - errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint64_t))); - errorOk(); -} - -errorret_t saveStreamReadFloatImpl(savestream_t *stream, float_t *out) { - float_t raw; - errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(float_t))); - *out = endianLittleToHostFloat(raw); - errorOk(); -} - -errorret_t saveStreamWriteFloatImpl( - savestream_t *stream, const float_t *input -) { - float_t raw = endianLittleToHostFloat(*input); - errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(float_t))); - errorOk(); -} - -errorret_t saveStreamReadStringImpl( - savestream_t *stream, char_t *out, const size_t maxLen -) { - for(size_t i = 0; i < maxLen; i++) { - errorChain(saveStreamReadBytesImpl(stream, &out[i], sizeof(char_t))); - if(out[i] == '\0') errorOk(); - } - out[maxLen - 1] = '\0'; - errorOk(); -} - -errorret_t saveStreamWriteStringImpl( - savestream_t *stream, const char_t *input, const size_t maxLen -) { - size_t len = strlen(input); - if(len >= maxLen) len = maxLen - 1; - errorChain(saveStreamWriteBytesImpl(stream, input, len + 1)); - errorOk(); -} - -errorret_t saveStreamReadDateImpl(savestream_t *stream, dusktimeepoch_t *out) { - uint64_t raw; - - errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint64_t))); - raw = endianLittleToHost64(raw); - memoryCopy(&out->time, &raw, sizeof(double_t)); - - errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint64_t))); - raw = endianLittleToHost64(raw); - memoryCopy(&out->timeZone, &raw, sizeof(double_t)); - - errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint64_t))); - raw = endianLittleToHost64(raw); - memoryCopy(&out->offsetTime, &raw, sizeof(double_t)); - - errorOk(); -} - -errorret_t saveStreamWriteDateImpl( - savestream_t *stream, const dusktimeepoch_t *input -) { - uint64_t raw; - - memoryCopy(&raw, &input->time, sizeof(double_t)); - raw = endianLittleToHost64(raw); - errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint64_t))); - - memoryCopy(&raw, &input->timeZone, sizeof(double_t)); - raw = endianLittleToHost64(raw); - errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint64_t))); - - memoryCopy(&raw, &input->offsetTime, sizeof(double_t)); - raw = endianLittleToHost64(raw); - errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint64_t))); - - errorOk(); -} - -errorret_t saveMetaSerializeRead(savestream_t *stream, savemeta_t *meta) { - saveFileReadHeader( - stream, meta->header, SAVE_META_HEADER, SAVE_META_HEADER_SIZE - ); - saveFileReadVersion(stream, &meta->version); - saveFileReadFloat(stream, &meta->deadzone); - saveFileReadUInt8(stream, &meta->language); - errorChain(saveStreamVerifyChecksumImpl(stream, "Save meta")); - meta->exists = true; - errorOk(); -} - -errorret_t saveMetaSerializeWrite(savestream_t *stream, savemeta_t *meta) { - memoryCopy(meta->header, SAVE_META_HEADER, SAVE_META_HEADER_SIZE); - meta->version = SAVE_META_VERSION; - - size_t headerPosition; - errorChain(saveStreamTellImpl(stream, &headerPosition)); - saveFileWriteHeader(stream, meta->header, SAVE_META_HEADER_SIZE); - saveFileWriteVersion(stream, &meta->version); - saveFileWriteFloat(stream, &meta->deadzone); - saveFileWriteUInt8(stream, &meta->language); - errorChain(saveStreamFinalizeWriteImpl( - stream, headerPosition, SAVE_META_HEADER_SIZE - )); - meta->exists = true; - errorOk(); -} - -errorret_t saveSlotSerializeRead(savestream_t *stream, saveslot_t *slot) { - saveFileReadHeader( - stream, slot->header, SAVE_SLOT_HEADER, SAVE_SLOT_HEADER_SIZE - ); - saveFileReadVersion(stream, &slot->version); - saveFileReadString(stream, slot->playerName, SAVE_PLAYER_NAME_MAX); - for(size_t i = 0; i < SAVE_GLOBAL_ITEM_COUNT_MAX; i++) { - saveFileReadBool(stream, &slot->globalItemCollected[i]); - } - for(size_t i = 0; i < SAVE_STORY_FLAG_COUNT_MAX; i++) { - saveFileReadUInt8(stream, &slot->storyFlags[i]); - } - errorChain(saveStreamVerifyChecksumImpl(stream, "Save slot")); - slot->exists = true; - errorOk(); -} - -errorret_t saveSlotSerializeWrite(savestream_t *stream, saveslot_t *slot) { - memoryCopy(slot->header, SAVE_SLOT_HEADER, SAVE_SLOT_HEADER_SIZE); - slot->version = SAVE_SLOT_VERSION; - - size_t headerPosition; - errorChain(saveStreamTellImpl(stream, &headerPosition)); - saveFileWriteHeader(stream, slot->header, SAVE_SLOT_HEADER_SIZE); - saveFileWriteVersion(stream, &slot->version); - saveFileWriteString(stream, slot->playerName, SAVE_PLAYER_NAME_MAX); - for(size_t i = 0; i < SAVE_GLOBAL_ITEM_COUNT_MAX; i++) { - saveFileWriteBool(stream, &slot->globalItemCollected[i]); - } - for(size_t i = 0; i < SAVE_STORY_FLAG_COUNT_MAX; i++) { - saveFileWriteUInt8(stream, &slot->storyFlags[i]); - } - errorChain(saveStreamFinalizeWriteImpl( - stream, headerPosition, SAVE_SLOT_HEADER_SIZE - )); - slot->exists = true; - errorOk(); -} diff --git a/src/dusk/save/savestream.h b/src/dusk/save/savestream.h deleted file mode 100644 index 2c26e18b..00000000 --- a/src/dusk/save/savestream.h +++ /dev/null @@ -1,511 +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 "saveslot.h" -#include "savemeta.h" -#include "save/saveplatform.h" -#include "time/timeepoch.h" - -typedef struct { - bool_t found; - uint32_t checksum; - uint32_t expectedChecksum; - saveplatformstream_t platform; -} savestream_t; - -/** - * Reads bytes from the platform stream without updating the CRC. - * - * @param stream Active stream. - * @param buf Destination buffer. - * @param len Number of bytes to read. - * @return An error if the read fails. - */ -errorret_t saveStreamReadBytesRawImpl( - savestream_t *stream, void *buf, const size_t len -); - -/** - * Writes bytes to the platform stream without updating the CRC. - * - * @param stream Active stream. - * @param buf Source buffer. - * @param len Number of bytes to write. - * @return An error if the write fails. - */ -errorret_t saveStreamWriteBytesRawImpl( - savestream_t *stream, const void *buf, const size_t len -); - -/** - * Reads bytes from the platform stream and accumulates them into the CRC. - * - * @param stream Active stream. - * @param buf Destination buffer. - * @param len Number of bytes to read. - * @return An error if the read fails. - */ -errorret_t saveStreamReadBytesImpl( - savestream_t *stream, void *buf, const size_t len -); - -/** - * Updates the CRC then writes bytes to the platform stream. - * - * @param stream Active stream. - * @param buf Source buffer. - * @param len Number of bytes to write. - * @return An error if the write fails. - */ -errorret_t saveStreamWriteBytesImpl( - savestream_t *stream, const void *buf, const size_t len -); - -/** - * Gets the current read/write position within the stream. Used to capture - * a section's start position before writing its header, so its checksum - * can be backfilled at the right offset once the section's body is known - - * required now that a single stream can hold multiple self-contained - * sections back-to-back (meta + N save slots), not just one. - * - * @param stream Active stream. - * @param out Receives the current position. - * @return An error if the platform can't report a position. - */ -errorret_t saveStreamTellImpl(savestream_t *stream, size_t *out); - -/** - * Finalizes a write stream: computes the final CRC32, seeks back to the - * checksum field just after this section's header, and writes it in - * little-endian order. - * - * @param stream Active write stream. - * @param headerPosition Byte offset where this section's header started - * (see saveStreamTellImpl), captured before writing the header. - * @param headerSize Byte length of this section's magic header. - * @return An error if the seek or write fails. - */ -errorret_t saveStreamFinalizeWriteImpl( - savestream_t *stream, const size_t headerPosition, const size_t headerSize -); - -/** - * Verifies that the CRC32 accumulated during loading matches the value - * stored in this section's header. - * - * @param stream Active read stream (loading must be complete). - * @param sectionLabel Human-readable label used in the error message on - * mismatch (e.g. "save meta", "save slot"). - * @return An error if the checksum does not match. - */ -errorret_t saveStreamVerifyChecksumImpl( - savestream_t *stream, const char_t *sectionLabel -); - -/** - * Reads and validates a section's magic header, then reads its stored - * CRC32 and resets the running accumulator. - * - * @param stream Active read stream. - * @param header Buffer of headerSize bytes to receive the header. - * @param expectedHeader The magic bytes this section must match. - * @param headerSize Byte length of the magic header. - * @return An error if the header is missing or invalid. - */ -errorret_t saveStreamReadHeaderImpl( - savestream_t *stream, char_t *header, const char_t *expectedHeader, - const size_t headerSize -); - -/** - * Writes a section's magic header and a zero CRC32 placeholder, then - * resets the running accumulator. - * - * @param stream Active write stream. - * @param header Buffer of headerSize bytes to write. - * @param headerSize Byte length of the magic header. - * @return An error if the write fails. - */ -errorret_t saveStreamWriteHeaderImpl( - savestream_t *stream, const char_t *header, const size_t headerSize -); - -/** - * Reads a little-endian uint32 version field from the stream. - * - * @param stream Active read stream. - * @param out Receives the host-order value. - * @return An error if the read fails. - */ -errorret_t saveStreamReadVersionImpl(savestream_t *stream, uint32_t *out); - -/** - * Writes a uint32 version field to the stream in little-endian order. - * - * @param stream Active write stream. - * @param input Value to write. - * @return An error if the write fails. - */ -errorret_t saveStreamWriteVersionImpl( - savestream_t *stream, const uint32_t *input -); - -/** - * Reads a single byte as a boolean (0 = false, non-zero = true). - * - * @param stream Active read stream. - * @param out Receives the boolean value. - * @return An error if the read fails. - */ -errorret_t saveStreamReadBoolImpl(savestream_t *stream, bool_t *out); - -/** - * Writes a boolean as a single byte (true = 1, false = 0). - * - * @param stream Active write stream. - * @param input Value to write. - * @return An error if the write fails. - */ -errorret_t saveStreamWriteBoolImpl(savestream_t *stream, const bool_t *input); - -/** - * Reads a signed 8-bit integer from the stream. - * - * @param stream Active read stream. - * @param out Receives the value. - * @return An error if the read fails. - */ -errorret_t saveStreamReadInt8Impl(savestream_t *stream, int8_t *out); - -/** - * Writes a signed 8-bit integer to the stream. - * - * @param stream Active write stream. - * @param input Value to write. - * @return An error if the write fails. - */ -errorret_t saveStreamWriteInt8Impl(savestream_t *stream, const int8_t *input); - -/** - * Reads an unsigned 8-bit integer from the stream. - * - * @param stream Active read stream. - * @param out Receives the value. - * @return An error if the read fails. - */ -errorret_t saveStreamReadUInt8Impl(savestream_t *stream, uint8_t *out); - -/** - * Writes an unsigned 8-bit integer to the stream. - * - * @param stream Active write stream. - * @param input Value to write. - * @return An error if the write fails. - */ -errorret_t saveStreamWriteUInt8Impl(savestream_t *stream, const uint8_t *input); - -/** - * Reads a little-endian signed 16-bit integer from the stream. - * - * @param stream Active read stream. - * @param out Receives the host-order value. - * @return An error if the read fails. - */ -errorret_t saveStreamReadInt16Impl(savestream_t *stream, int16_t *out); - -/** - * Writes a signed 16-bit integer to the stream in little-endian order. - * - * @param stream Active write stream. - * @param input Value to write. - * @return An error if the write fails. - */ -errorret_t saveStreamWriteInt16Impl( - savestream_t *stream, const int16_t *input -); - -/** - * Reads a little-endian unsigned 16-bit integer from the stream. - * - * @param stream Active read stream. - * @param out Receives the host-order value. - * @return An error if the read fails. - */ -errorret_t saveStreamReadUInt16Impl(savestream_t *stream, uint16_t *out); - -/** - * Writes an unsigned 16-bit integer to the stream in little-endian order. - * - * @param stream Active write stream. - * @param input Value to write. - * @return An error if the write fails. - */ -errorret_t saveStreamWriteUInt16Impl( - savestream_t *stream, const uint16_t *input -); - -/** - * Reads a little-endian signed 32-bit integer from the stream. - * - * @param stream Active read stream. - * @param out Receives the host-order value. - * @return An error if the read fails. - */ -errorret_t saveStreamReadInt32Impl(savestream_t *stream, int32_t *out); - -/** - * Writes a signed 32-bit integer to the stream in little-endian order. - * - * @param stream Active write stream. - * @param input Value to write. - * @return An error if the write fails. - */ -errorret_t saveStreamWriteInt32Impl( - savestream_t *stream, const int32_t *input -); - -/** - * Reads a little-endian unsigned 32-bit integer from the stream. - * - * @param stream Active read stream. - * @param out Receives the host-order value. - * @return An error if the read fails. - */ -errorret_t saveStreamReadUInt32Impl(savestream_t *stream, uint32_t *out); - -/** - * Writes an unsigned 32-bit integer to the stream in little-endian order. - * - * @param stream Active write stream. - * @param input Value to write. - * @return An error if the write fails. - */ -errorret_t saveStreamWriteUInt32Impl( - savestream_t *stream, const uint32_t *input -); - -/** - * Reads a little-endian signed 64-bit integer from the stream. - * - * @param stream Active read stream. - * @param out Receives the host-order value. - * @return An error if the read fails. - */ -errorret_t saveStreamReadInt64Impl(savestream_t *stream, int64_t *out); - -/** - * Writes a signed 64-bit integer to the stream in little-endian order. - * - * @param stream Active write stream. - * @param input Value to write. - * @return An error if the write fails. - */ -errorret_t saveStreamWriteInt64Impl( - savestream_t *stream, const int64_t *input -); - -/** - * Reads a little-endian unsigned 64-bit integer from the stream. - * - * @param stream Active read stream. - * @param out Receives the host-order value. - * @return An error if the read fails. - */ -errorret_t saveStreamReadUInt64Impl(savestream_t *stream, uint64_t *out); - -/** - * Writes an unsigned 64-bit integer to the stream in little-endian order. - * - * @param stream Active write stream. - * @param input Value to write. - * @return An error if the write fails. - */ -errorret_t saveStreamWriteUInt64Impl( - savestream_t *stream, const uint64_t *input -); - -/** - * Reads a little-endian float from the stream. - * - * @param stream Active read stream. - * @param out Receives the host-order value. - * @return An error if the read fails. - */ -errorret_t saveStreamReadFloatImpl(savestream_t *stream, float_t *out); - -/** - * Writes a float to the stream in little-endian order. - * - * @param stream Active write stream. - * @param input Value to write. - * @return An error if the write fails. - */ -errorret_t saveStreamWriteFloatImpl( - savestream_t *stream, const float_t *input -); - -/** - * Reads a null-terminated string from the stream up to maxLen bytes - * (including the terminator). Always null-terminates the output buffer. - * - * @param stream Active read stream. - * @param out Destination buffer of at least maxLen bytes. - * @param maxLen Maximum bytes to read, including the null terminator. - * @return An error if the read fails. - */ -errorret_t saveStreamReadStringImpl( - savestream_t *stream, char_t *out, const size_t maxLen -); - -/** - * Writes a null-terminated string to the stream, truncating to maxLen-1 - * characters and always appending a null terminator. - * - * @param stream Active write stream. - * @param input Source string. - * @param maxLen Maximum bytes to write, including the null terminator. - * @return An error if the write fails. - */ -errorret_t saveStreamWriteStringImpl( - savestream_t *stream, const char_t *input, const size_t maxLen -); - -/** - * Reads a dusktimeepoch_t as three little-endian 64-bit IEEE 754 doubles - * (time, timeZone, offsetTime). - * - * @param stream Active read stream. - * @param out Receives the epoch value. - * @return An error if the read fails. - */ -errorret_t saveStreamReadDateImpl( - savestream_t *stream, dusktimeepoch_t *out -); - -/** - * Writes a dusktimeepoch_t as three little-endian 64-bit IEEE 754 doubles - * (time, timeZone, offsetTime). - * - * @param stream Active write stream. - * @param input Epoch value to write. - * @return An error if the write fails. - */ -errorret_t saveStreamWriteDateImpl( - savestream_t *stream, const dusktimeepoch_t *input -); - -/** - * Reads a self-contained save meta section (header, version, fields, - * checksum verification) from the stream. - * - * @param stream Active read stream, positioned at the section's start. - * @param meta Meta struct to populate. - * @return An error code if loading fails. - */ -errorret_t saveMetaSerializeRead(savestream_t *stream, savemeta_t *meta); - -/** - * Writes a self-contained save meta section (header, version, fields, - * checksum) to the stream. - * - * @param stream Active write stream, positioned at the section's start. - * @param meta Meta struct to serialize. - * @return An error code if writing fails. - */ -errorret_t saveMetaSerializeWrite(savestream_t *stream, savemeta_t *meta); - -/** - * Reads a self-contained save slot section (header, version, fields, - * checksum verification) from the stream. - * - * @param stream Active read stream, positioned at the section's start. - * @param slot Slot struct to populate. - * @return An error code if loading fails. - */ -errorret_t saveSlotSerializeRead(savestream_t *stream, saveslot_t *slot); - -/** - * Writes a self-contained save slot section (header, version, fields, - * checksum) to the stream. - * - * @param stream Active write stream, positioned at the section's start. - * @param slot Slot struct to serialize. - * @return An error code if writing fails. - */ -errorret_t saveSlotSerializeWrite(savestream_t *stream, saveslot_t *slot); - -#define saveFileReadHeader(stream, header, expected, size) \ - errorChain(saveStreamReadHeaderImpl(stream, header, expected, size)) -#define saveFileWriteHeader(stream, header, size) \ - errorChain(saveStreamWriteHeaderImpl(stream, header, size)) - -#define saveFileReadVersion(stream, out) \ - errorChain(saveStreamReadVersionImpl(stream, out)) -#define saveFileWriteVersion(stream, input) \ - errorChain(saveStreamWriteVersionImpl(stream, input)) - -#define saveFileReadBool(stream, out) \ - errorChain(saveStreamReadBoolImpl(stream, out)) -#define saveFileWriteBool(stream, input) \ - errorChain(saveStreamWriteBoolImpl(stream, input)) - -#define saveFileReadInt8(stream, out) \ - errorChain(saveStreamReadInt8Impl(stream, out)) -#define saveFileWriteInt8(stream, input) \ - errorChain(saveStreamWriteInt8Impl(stream, input)) - -#define saveFileReadUInt8(stream, out) \ - errorChain(saveStreamReadUInt8Impl(stream, out)) -#define saveFileWriteUInt8(stream, input) \ - errorChain(saveStreamWriteUInt8Impl(stream, input)) - -#define saveFileReadInt16(stream, out) \ - errorChain(saveStreamReadInt16Impl(stream, out)) -#define saveFileWriteInt16(stream, input) \ - errorChain(saveStreamWriteInt16Impl(stream, input)) - -#define saveFileReadUInt16(stream, out) \ - errorChain(saveStreamReadUInt16Impl(stream, out)) -#define saveFileWriteUInt16(stream, input) \ - errorChain(saveStreamWriteUInt16Impl(stream, input)) - -#define saveFileReadInt32(stream, out) \ - errorChain(saveStreamReadInt32Impl(stream, out)) -#define saveFileWriteInt32(stream, input) \ - errorChain(saveStreamWriteInt32Impl(stream, input)) - -#define saveFileReadUInt32(stream, out) \ - errorChain(saveStreamReadUInt32Impl(stream, out)) -#define saveFileWriteUInt32(stream, input) \ - errorChain(saveStreamWriteUInt32Impl(stream, input)) - -#define saveFileReadInt64(stream, out) \ - errorChain(saveStreamReadInt64Impl(stream, out)) -#define saveFileWriteInt64(stream, input) \ - errorChain(saveStreamWriteInt64Impl(stream, input)) - -#define saveFileReadUInt64(stream, out) \ - errorChain(saveStreamReadUInt64Impl(stream, out)) -#define saveFileWriteUInt64(stream, input) \ - errorChain(saveStreamWriteUInt64Impl(stream, input)) - -#define saveFileReadFloat(stream, out) \ - errorChain(saveStreamReadFloatImpl(stream, out)) -#define saveFileWriteFloat(stream, input) \ - errorChain(saveStreamWriteFloatImpl(stream, input)) - -#define saveFileReadString(stream, out, maxLen) \ - errorChain(saveStreamReadStringImpl(stream, out, maxLen)) -#define saveFileWriteString(stream, input, maxLen) \ - errorChain(saveStreamWriteStringImpl(stream, input, maxLen)) - -#define saveFileReadDate(stream, out) \ - errorChain(saveStreamReadDateImpl(stream, out)) -#define saveFileWriteDate(stream, input) \ - errorChain(saveStreamWriteDateImpl(stream, input)) diff --git a/src/dusk/scene/initial/sceneinitial.c b/src/dusk/scene/initial/sceneinitial.c index 395b070c..341e11c2 100644 --- a/src/dusk/scene/initial/sceneinitial.c +++ b/src/dusk/scene/initial/sceneinitial.c @@ -9,71 +9,13 @@ #include "assert/assert.h" #include "util/memory.h" #include "error/error.h" -#include "save/save.h" #include "ui/frame/initial/uiinitialnocard.h" #include "ui/frame/initial/uiinitialcreatesave.h" -static void sceneInitialCheckSave(void); - -static void sceneInitialCreateSaveWriteComplete(errorret_t result, void *user) { - if(errorIsNotOk(result)) errorCatch(errorPrint(result)); - sceneSet(SCENE_TYPE_MAIN_MENU); -} - -static void sceneInitialCreateSaveResult(const bool_t create, void *user) { - if(!create) { - sceneSet(SCENE_TYPE_MAIN_MENU); - return; - } - - saveWriteSlot(SAVE_ACTIVE_SLOT, sceneInitialCreateSaveWriteComplete, NULL); -} - -static void sceneInitialNoCardResult(const bool_t retry, void *user) { - if(retry) { - sceneInitialCheckSave(); - return; - } - - // The player explicitly acknowledged there's no save device and chose - // to proceed anyway - stick with that for the rest of the session (see - // saveMarkTemporary()'s doc comment for why this can't be un-set later). - saveMarkTemporary(); - sceneSet(SCENE_TYPE_MAIN_MENU); -} - -static void sceneInitialLoadComplete(errorret_t result, void *user) { - if(errorIsNotOk(result) || !saveIsAvailable()) { - errorCatch(result); - uiInitialNoCardOpen(sceneInitialNoCardResult, NULL); - return; - } - - if(saveSlotExists(SAVE_ACTIVE_SLOT)) { - sceneSet(SCENE_TYPE_MAIN_MENU); - return; - } - - uiInitialCreateSaveOpen(sceneInitialCreateSaveResult, NULL); -} - -static void sceneInitialCheckSave(void) { - if(!saveIsAvailable()) { - uiInitialNoCardOpen(sceneInitialNoCardResult, NULL); - return; - } - - if(saveIsBusy()) return; - - saveLoadSlot(SAVE_ACTIVE_SLOT, sceneInitialLoadComplete, NULL); -} - errorret_t sceneInitialInit(scenedata_t *sceneData) { assertNotNull(sceneData, "Scene data cannot be null"); memoryZero(&sceneData->initial, sizeof(sceneinitial_t)); - sceneInitialCheckSave(); - errorOk(); } diff --git a/src/dusk/system/system.c b/src/dusk/system/system.c index e8c07d68..fe9c6584 100644 --- a/src/dusk/system/system.c +++ b/src/dusk/system/system.c @@ -16,6 +16,10 @@ #error "systemInitPlatform is not defined" #endif +#ifndef systemGetLocalePlatform + #error "systemGetLocalePlatform is not defined" +#endif + errorret_t systemInit() { return systemInitPlatform(); } @@ -24,6 +28,10 @@ systemdialogtype_t systemGetActiveDialogType() { return systemGetActiveDialogTypePlatform(); } +const localeinfo_t * systemGetLocale(void) { + return systemGetLocalePlatform(); +} + systemplatform_t systemGetPlatform(void) { #if defined(DUSK_KNULLI) return SYSTEM_PLATFORM_KNULLI; diff --git a/src/dusk/system/system.h b/src/dusk/system/system.h index d819f943..2985a194 100644 --- a/src/dusk/system/system.h +++ b/src/dusk/system/system.h @@ -8,6 +8,7 @@ #pragma once #include "error/error.h" #include "system/systemplatformlist.h" +#include "locale/localeinfo.h" #define SYSTEM_PLATFORM(name, value) SYSTEM_PLATFORM_##name = value, typedef enum { SYSTEM_PLATFORM_LIST } systemplatform_t; @@ -47,4 +48,11 @@ systemdialogtype_t systemGetActiveDialogType(); * * @return The current platform. */ -systemplatform_t systemGetPlatform(void); \ No newline at end of file +systemplatform_t systemGetPlatform(void); + +/** + * Returns the current locale of the system. + * + * @return The current locale. + */ +const localeinfo_t * systemGetLocale(void); \ No newline at end of file diff --git a/src/dusk/ui/CMakeLists.txt b/src/dusk/ui/CMakeLists.txt index 26754f0d..1dd15832 100644 --- a/src/dusk/ui/CMakeLists.txt +++ b/src/dusk/ui/CMakeLists.txt @@ -16,5 +16,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC ui.c uielement.c + uielementlist.c # uitextbox.c ) \ No newline at end of file diff --git a/src/dusk/ui/frame/CMakeLists.txt b/src/dusk/ui/frame/CMakeLists.txt index 106541ae..a94d68bd 100644 --- a/src/dusk/ui/frame/CMakeLists.txt +++ b/src/dusk/ui/frame/CMakeLists.txt @@ -11,7 +11,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} add_subdirectory(game) add_subdirectory(mainmenu) -add_subdirectory(settings) add_subdirectory(battle) add_subdirectory(backpack) add_subdirectory(initial) diff --git a/src/dusk/ui/frame/game/uigamemenu.c b/src/dusk/ui/frame/game/uigamemenu.c index e587461c..b9b5f873 100644 --- a/src/dusk/ui/frame/game/uigamemenu.c +++ b/src/dusk/ui/frame/game/uigamemenu.c @@ -8,7 +8,6 @@ #include "uigamemenu.h" #include "ui/frame/uiframe.h" #include "ui/frame/uiconfirm.h" -#include "ui/frame/settings/uisettings.h" #include "ui/frame/backpack/uibackpack.h" #include "ui/rpg/textbox/uitextboxmain.h" #include "util/memory.h" @@ -17,85 +16,11 @@ #include "assert/assert.h" #include "locale/localemanager.h" #include "asset/loader/locale/assetlocaleloader.h" -#include "save/save.h" #include "error/error.h" #include "util/string.h" #define UI_GAME_MENU_INDEX_CHARACTERS 0 #define UI_GAME_MENU_INDEX_ITEMS 1 -#define UI_GAME_MENU_INDEX_SETTINGS 2 -#define UI_GAME_MENU_INDEX_SAVE 3 - -static void uiGameMenuSaveWriteComplete(errorret_t result, void *user) { - if(errorIsNotOk(result)) { - // Generously sized - stringFormat asserts (crashes) rather than - // truncating if the message doesn't fit, so this must comfortably fit - // the longest platform save-error message plus this prefix. - char_t msg[256]; - stringFormat( - msg, sizeof(msg), UI_GAME_MENU.saveFailedFormat, result.state->message - ); - errorCatch(result); - uiTextboxMainSetText(msg); - return; - } - - uiTextboxMainSetText(UI_GAME_MENU.saveSuccessText); -} - -static void uiGameMenuSaveCreateConfirmed(const bool_t confirmed, void *user) { - if(!confirmed) { - uiTextboxMainSetText(UI_GAME_MENU.saveCancelledText); - return; - } - - saveWriteSlot(SAVE_ACTIVE_SLOT, uiGameMenuSaveWriteComplete, NULL); -} - -// Determines whether there's actually save data to overwrite (not just -// whether the medium is present) by attempting a real load first - this is -// what lets a fresh memory card/stick, with no prior save on it yet, be -// told apart from one that already has our data on it. Cheap either way -// (a single sector/file read), and correct on every platform without any -// platform-specific UI code - saveSlotExists() already reflects each -// platform's own notion of "found something." -static void uiGameMenuSaveCheckComplete(errorret_t result, void *user) { - if(errorIsNotOk(result)) { - char_t msg[256]; - stringFormat( - msg, sizeof(msg), UI_GAME_MENU.saveCheckFailedFormat, - result.state->message - ); - errorCatch(result); - uiTextboxMainSetText(msg); - return; - } - - if(saveSlotExists(SAVE_ACTIVE_SLOT)) { - saveWriteSlot(SAVE_ACTIVE_SLOT, uiGameMenuSaveWriteComplete, NULL); - return; - } - - uiConfirmOpen( - UI_GAME_MENU.saveCreateConfirmText, - uiGameMenuSaveCreateConfirmed, - NULL - ); -} - -static void uiGameMenuSave(void) { - if(saveIsTemporary()) { - uiTextboxMainSetText(UI_GAME_MENU.saveTemporaryText); - return; - } - if(!saveIsAvailable()) { - uiTextboxMainSetText(UI_GAME_MENU.saveUnavailableText); - return; - } - if(saveIsBusy()) return;// A save/load dialog (e.g. on PSP) is already up. - - saveLoadSlot(SAVE_ACTIVE_SLOT, uiGameMenuSaveCheckComplete, NULL); -} uigamemenu_t UI_GAME_MENU; @@ -104,9 +29,13 @@ void uiGameMenuSelected( const uint8_t index, const uimenuitem_t *item ) { - if(index == UI_GAME_MENU_INDEX_ITEMS) uiBackpackOpen(); - if(index == UI_GAME_MENU_INDEX_SETTINGS) uiSettingsOpen(); - if(index == UI_GAME_MENU_INDEX_SAVE) uiGameMenuSave(); + switch(index) { + case UI_GAME_MENU_INDEX_ITEMS: + uiBackpackOpen(); + break; + default: + assertUnreachable("Unhandled game menu index"); + } } errorret_t uiGameMenuInit(void) { @@ -133,13 +62,6 @@ errorret_t uiGameMenuInit(void) { UI_GAME_MENU.settingsLabel, UI_GAME_MENU_LABEL_MAX )); - errorChain(assetLocaleGetString( - &LOCALE.entry->data.locale, - "ui.game_menu.save", - 0, - UI_GAME_MENU.saveLabel, - UI_GAME_MENU_LABEL_MAX - )); errorChain(assetLocaleGetString( &LOCALE.entry->data.locale, "ui.game_menu.save_success", @@ -195,7 +117,6 @@ errorret_t uiGameMenuInit(void) { ); MENU_BUTTON(UI_GAME_MENU.charactersLabel); MENU_BUTTON(UI_GAME_MENU.itemsLabel); - MENU_BUTTON(UI_GAME_MENU.settingsLabel); MENU_BUTTON(UI_GAME_MENU.saveLabel); MENU_END(UI_GAME_MENU.items, 1); diff --git a/src/dusk/ui/frame/mainmenu/uimainmenu.c b/src/dusk/ui/frame/mainmenu/uimainmenu.c index e4de1033..810a0f8d 100644 --- a/src/dusk/ui/frame/mainmenu/uimainmenu.c +++ b/src/dusk/ui/frame/mainmenu/uimainmenu.c @@ -10,7 +10,6 @@ #include "scene/scene.h" #include "engine/engine.h" #include "rpg/battle/testbattle/testbattle.h" -#include "save/save.h" #include "util/memory.h" #include "display/spritebatch/spritebatch.h" #include "display/screen/screen.h" @@ -34,9 +33,6 @@ void uiMainMenuSelected( switch(index) { case UI_MAIN_MENU_INDEX_NEW_GAME: - // Resets the active slot to SAVE_DEFAULT in memory -- no disk I/O, - // unlike Load Game (which will read a real slot via saveLoadSlot()). - saveLoadDefault(SAVE_ACTIVE_SLOT); testBattleStart(); break; diff --git a/src/dusk/ui/frame/settings/CMakeLists.txt b/src/dusk/ui/frame/settings/CMakeLists.txt deleted file mode 100644 index f0e4b1c2..00000000 --- a/src/dusk/ui/frame/settings/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -target_sources(${DUSK_LIBRARY_TARGET_NAME} - PUBLIC - uisettings.c - uisettingsgeneral.c - uisettingsinput.c - uisettingsdisplay.c - uisettingsaudio.c -) diff --git a/src/dusk/ui/frame/settings/uisettings.c b/src/dusk/ui/frame/settings/uisettings.c deleted file mode 100644 index 23b496e5..00000000 --- a/src/dusk/ui/frame/settings/uisettings.c +++ /dev/null @@ -1,250 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uisettings.h" -#include "uisettingsgeneral.h" -#include "uisettingsinput.h" -#include "uisettingsdisplay.h" -#include "uisettingsaudio.h" -#include "ui/frame/uiframe.h" -#include "ui/frame/uiconfirm.h" -#include "util/memory.h" -#include "display/spritebatch/spritebatch.h" -#include "display/text/text.h" -#include "display/screen/screen.h" -#include "assert/assert.h" -#include "locale/localemanager.h" -#include "asset/loader/locale/assetlocaleloader.h" - -uisettings_t UI_SETTINGS; - -uisettingstabdef_t UI_SETTINGS_TAB_DEFS[UI_SETTINGS_TAB_COUNT] = { - { - .localeId = "ui.settings.tabs.general", - .init = uiSettingsGeneralInit, - .menu = (uimenu_t *)&UI_SETTINGS.data, - .load = uiSettingsGeneralLoad, - .apply = uiSettingsGeneralApply, - .hasChanges = uiSettingsGeneralHasChanges - }, - - { - .localeId = "ui.settings.tabs.input", - .init = uiSettingsInputInit, - .menu = (uimenu_t *)&UI_SETTINGS.data, - .load = uiSettingsInputLoad, - .apply = uiSettingsInputApply, - .hasChanges = uiSettingsInputHasChanges - }, - - { - .localeId = "ui.settings.tabs.display", - .init = uiSettingsDisplayInit, - .menu = (uimenu_t *)&UI_SETTINGS.data, - .load = uiSettingsDisplayLoad, - .apply = uiSettingsDisplayApply, - .hasChanges = uiSettingsDisplayHasChanges - }, - - { - .localeId = "ui.settings.tabs.audio", - .init = uiSettingsAudioInit, - .menu = (uimenu_t *)&UI_SETTINGS.data, - .load = uiSettingsAudioLoad, - .apply = uiSettingsAudioApply, - .hasChanges = uiSettingsAudioHasChanges - } -}; - -static uisettingstabdef_t UI_SETTINGS_TOP_DEF = { - .menu = &UI_SETTINGS.tabsMenu, - .load = uiSettingsLoad, - .hasChanges = uiSettingsHasChanges -}; - -static uisettingstabdef_t *UI_SETTINGS_PENDING_DISCARD = NULL; -static uisettingstabdef_t *UI_SETTINGS_ACTIVE_TAB = NULL; - -errorret_t uiSettingsActivateTab(uisettingstabdef_t *def) { - if(def == UI_SETTINGS_ACTIVE_TAB) errorOk(); - - errorChain(def->init(&UI_SETTINGS.data)); - def->menu->user = def; - def->load(); - UI_SETTINGS_ACTIVE_TAB = def; - - errorOk(); -} - -void uiSettingsTabChanged( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -) { - if(index >= UI_SETTINGS_TAB_COUNT) return; - errorCatch(uiSettingsActivateTab(&UI_SETTINGS_TAB_DEFS[index])); -} - -void uiSettingsTabSelected( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -) { - if(index >= UI_SETTINGS_TAB_COUNT) return; - - uisettingstabdef_t *def = &UI_SETTINGS_TAB_DEFS[index]; - errorCatch(uiSettingsActivateTab(def)); - uiMenuOpen(def->menu); -} - -errorret_t uiSettingsInit(void) { - memoryZero(&UI_SETTINGS, sizeof(uisettings_t)); - UI_SETTINGS_ACTIVE_TAB = NULL; - - for(uint8_t i = 0; i < UI_SETTINGS_TAB_COUNT; i++) { - errorChain(assetLocaleGetString( - &LOCALE.entry->data.locale, - UI_SETTINGS_TAB_DEFS[i].localeId, - 0, - UI_SETTINGS.tabLabels[i], - UI_SETTINGS_TAB_LABEL_MAX - )); - } - - MENU_BEGIN( - &UI_SETTINGS.tabsMenu, UI_SETTINGS.tabs, uiSettingsTabSelected, - uiSettingsDiscardMenuClosed, uiSettingsTabChanged - ); - for(uint8_t i = 0; i < UI_SETTINGS_TAB_COUNT; i++) { - MENU_TAB(UI_SETTINGS.tabLabels[i]); - } - MENU_END(UI_SETTINGS.tabs, menuIndex); - - UI_SETTINGS.tabsMenu.user = &UI_SETTINGS_TOP_DEF; - - errorChain(assetLocaleGetString( - &LOCALE.entry->data.locale, - "ui.settings.apply", - 0, - UI_SETTINGS.applyLabel, - UI_SETTINGS_APPLY_LABEL_MAX - )); - - errorChain(uiSettingsActivateTab(&UI_SETTINGS_TAB_DEFS[0])); - - errorOk(); -} - -errorret_t uiSettingsDraw(void) { - if(!uiMenuIsActive(&UI_SETTINGS.tabsMenu)) errorOk(); - - const float_t width = SCREEN.width; - const float_t height = SCREEN.height; - const float_t x = (float_t)SCREEN.scanX + - ((float_t)SCREEN.scanWidth - width) * 0.5f; - const float_t y = (float_t)SCREEN.scanY + - ((float_t)SCREEN.scanHeight - height) * 0.5f; - - errorChain(uiFrameDraw(x, y, width, height)); - - const float_t contentX = x + UI_FRAME_START_X; - const float_t contentY = y + UI_FRAME_START_Y; - const float_t contentWidth = width - (UI_FRAME_START_X * 2); - const float_t contentHeight = height - (UI_FRAME_START_Y * 2); - - errorChain(uiMenuDraw( - &UI_SETTINGS.tabsMenu, - contentX, - contentY, - contentWidth, - contentHeight - )); - - const float_t tabsRowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight; - const float_t pageY = contentY + tabsRowHeight + UI_FRAME_PADDING_Y; - const float_t pageHeight = contentHeight - tabsRowHeight - UI_FRAME_PADDING_Y; - - if(UI_SETTINGS_ACTIVE_TAB != NULL) { - errorChain(uiMenuDraw( - UI_SETTINGS_ACTIVE_TAB->menu, contentX, pageY, contentWidth, pageHeight - )); - } - - errorChain(spriteBatchFlush()); - errorOk(); -} - -errorret_t uiSettingsUpdate(void) { - if(!UI_SETTINGS_PENDING_DISCARD) errorOk(); - - uisettingstabdef_t *def = UI_SETTINGS_PENDING_DISCARD; - UI_SETTINGS_PENDING_DISCARD = NULL; - - return uiSettingsDiscardUpdate(def); -} - -bool_t uiSettingsIsOpen(void) { - return uiMenuIsActive(&UI_SETTINGS.tabsMenu); -} - -void uiSettingsOpen() { - uiSettingsLoad(); - uiMenuOpen(&UI_SETTINGS.tabsMenu); -} - -void uiSettingsClose() { - uiMenuClose(&UI_SETTINGS.tabsMenu); -} - -errorret_t uiSettingsDispose(void) { - errorOk(); -} - -void uiSettingsLoad(void) { - if(UI_SETTINGS_ACTIVE_TAB != NULL) UI_SETTINGS_ACTIVE_TAB->load(); -} - -void uiSettingsApply(void) { - if(UI_SETTINGS_ACTIVE_TAB != NULL) UI_SETTINGS_ACTIVE_TAB->apply(); -} - -bool_t uiSettingsHasChanges(void) { - return UI_SETTINGS_ACTIVE_TAB != NULL && UI_SETTINGS_ACTIVE_TAB->hasChanges(); -} - -void uiSettingsDiscardMenuClosed(const uimenu_t *menu) { - uisettingstabdef_t *def = (uisettingstabdef_t *)menu->user; - if(def->hasChanges()) UI_SETTINGS_PENDING_DISCARD = def; -} - -errorret_t uiSettingsDiscardUpdate(uisettingstabdef_t *def) { - #define UI_SETTINGS_DISCARD_TEXT_MAX 128 - char_t text[UI_SETTINGS_DISCARD_TEXT_MAX]; - errorChain(assetLocaleGetString( - &LOCALE.entry->data.locale, - "ui.confirm.discard_changes", - 0, - text, - UI_SETTINGS_DISCARD_TEXT_MAX - )); - #undef UI_SETTINGS_DISCARD_TEXT_MAX - - uiConfirmOpen(text, uiSettingsDiscardConfirmResult, def); - - errorOk(); -} - -void uiSettingsDiscardConfirmResult(const bool_t result, void *user) { - uisettingstabdef_t *def = (uisettingstabdef_t *)user; - - if(!result) { - uiMenuOpen(def->menu); - return; - } - - def->load(); -} diff --git a/src/dusk/ui/frame/settings/uisettings.h b/src/dusk/ui/frame/settings/uisettings.h deleted file mode 100644 index 6b41f568..00000000 --- a/src/dusk/ui/frame/settings/uisettings.h +++ /dev/null @@ -1,133 +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 "ui/widget/uimenu.h" -#include "uisettingsdata.h" - -#define UI_SETTINGS_TAB_COUNT 4 -#define UI_SETTINGS_TAB_LABEL_MAX 32 -#define UI_SETTINGS_APPLY_LABEL_MAX 32 - -typedef struct { - const char_t *localeId; - errorret_t (*init)(uisettingsdata_t *data); - uimenu_t *menu; - void (*load)(void); - void (*apply)(void); - bool_t (*hasChanges)(void); -} uisettingstabdef_t; - -typedef struct { - uimenu_t tabsMenu; - uimenuitem_t tabs[UI_SETTINGS_TAB_COUNT]; - char_t tabLabels[UI_SETTINGS_TAB_COUNT][UI_SETTINGS_TAB_LABEL_MAX]; - char_t applyLabel[UI_SETTINGS_APPLY_LABEL_MAX]; - uisettingsdata_t data; -} uisettings_t; - -extern uisettings_t UI_SETTINGS; -extern uisettingstabdef_t UI_SETTINGS_TAB_DEFS[UI_SETTINGS_TAB_COUNT]; - -/** - * Menu closed callback that flags a pending discard-changes - * confirmation if the menu's settings page/panel reports unsaved - * changes. Pass this directly as a settings page menu's closed - * callback. The pending def is picked up by uiSettingsUpdate on the - * next tick. - * - * @param menu The menu that was just closed. - */ -void uiSettingsDiscardMenuClosed(const uimenu_t *menu); - -/** - * Shows the discard-changes confirmation dialog for def. Called once - * by uiSettingsUpdate when uiSettingsDiscardMenuClosed has flagged a - * pending def. - * - * @param def The tab/panel def to show the confirmation for. - * @return Any error that occurs. - */ -errorret_t uiSettingsDiscardUpdate(uisettingstabdef_t *def); - -/** - * Callback passed to uiConfirmOpen by uiSettingsDiscardUpdate. Reopens - * the menu if the user chose to keep editing, otherwise reloads the - * widgets back to the actual engine values. - * - * @param result True if the user confirmed discarding changes, false - * to keep editing. - * @param user The uisettingstabdef_t* this confirmation belongs to. - */ -void uiSettingsDiscardConfirmResult(const bool_t result, void *user); - -/** - * Initializes the settings panel and all of its category pages. - * - * @return Any error that occurs. - */ -errorret_t uiSettingsInit(void); - -/** - * Draws the settings panel. No-op when not visible. - * - * @return Any error that occurs. - */ -errorret_t uiSettingsDraw(void); - -/** - * Updates the settings panel. Shows a discard-changes confirmation if - * the panel was just closed with unsaved changes. - * - * @return Any error that occurs. - */ -errorret_t uiSettingsUpdate(void); - -/** - * Returns true when the settings panel is currently open. - * - * @returns True if open. - */ -bool_t uiSettingsIsOpen(void); - -/** - * Opens the settings panel. No-op when already open. - */ -void uiSettingsOpen(); - -/** - * Closes the settings panel. No-op when already closed. - */ -void uiSettingsClose(); - -/** - * Disposes of the settings panel. - * - * @return Any error that occurs. - */ -errorret_t uiSettingsDispose(void); - -/** - * Loads every category page's widgets from the current engine settings. - * Called automatically by uiSettingsOpen. - */ -void uiSettingsLoad(void); - -/** - * Applies every category page's widget values to the engine settings. - * Called automatically when a page's own Apply button is pressed. - */ -void uiSettingsApply(void); - -/** - * Returns whether any category page has a widget value that differs - * from what was loaded. - * - * @returns True if there are unsaved changes. - */ -bool_t uiSettingsHasChanges(void); diff --git a/src/dusk/ui/frame/settings/uisettingsaudio.c b/src/dusk/ui/frame/settings/uisettingsaudio.c deleted file mode 100644 index 32063b82..00000000 --- a/src/dusk/ui/frame/settings/uisettingsaudio.c +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uisettingsaudio.h" -#include "uisettings.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "locale/localemanager.h" -#include "asset/loader/locale/assetlocaleloader.h" - -void uiSettingsAudioSelected( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -) { - if(index != UI_SETTINGS_AUDIO_INDEX_APPLY) return; - uiSettingsAudioApply(); -} - -errorret_t uiSettingsAudioInit(uisettingsdata_t *data) { - uisettingsaudio_t *audio = &data->audio; - memoryZero(audio, sizeof(uisettingsaudio_t)); - - errorChain(assetLocaleGetString( - &LOCALE.entry->data.locale, - "ui.settings.audio.placeholder", - 0, - audio->placeholderLabel, - UI_SETTINGS_AUDIO_PLACEHOLDER_MAX - )); - - MENU_BEGIN( - &audio->menu, audio->items, - uiSettingsAudioSelected, uiSettingsDiscardMenuClosed, NULL - ); - MENU_LABEL(audio->placeholderLabel); - MENU_BUTTON(UI_SETTINGS.applyLabel); - - MENU_END(audio->items, 1); - - errorOk(); -} - -void uiSettingsAudioLoad(void) { -} - -void uiSettingsAudioApply(void) { - uiMenuClose(&UI_SETTINGS.data.audio.menu); -} - -bool_t uiSettingsAudioHasChanges(void) { - return false; -} diff --git a/src/dusk/ui/frame/settings/uisettingsaudio.h b/src/dusk/ui/frame/settings/uisettingsaudio.h deleted file mode 100644 index 04f8a605..00000000 --- a/src/dusk/ui/frame/settings/uisettingsaudio.h +++ /dev/null @@ -1,36 +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 "uisettingsdata.h" - -/** - * Initializes the audio settings page into the shared settings data - * union. - * - * @param data The shared settings data union to initialize into. - * @return Any error that occurs. - */ -errorret_t uiSettingsAudioInit(uisettingsdata_t *data); - -/** - * Loads the page's widgets from the current engine audio settings. - */ -void uiSettingsAudioLoad(void); - -/** - * Applies the page's widget values to the engine audio settings. - */ -void uiSettingsAudioApply(void); - -/** - * Returns whether any widget's value differs from what was loaded. - * - * @returns True if there are unsaved changes. - */ -bool_t uiSettingsAudioHasChanges(void); diff --git a/src/dusk/ui/frame/settings/uisettingsdata.h b/src/dusk/ui/frame/settings/uisettingsdata.h deleted file mode 100644 index 88db23a5..00000000 --- a/src/dusk/ui/frame/settings/uisettingsdata.h +++ /dev/null @@ -1,73 +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 "ui/widget/uimenu.h" -#include "locale/localemanager.h" - -#define UI_SETTINGS_GENERAL_ITEM_COUNT 3 -#define UI_SETTINGS_GENERAL_INDEX_LANGUAGE 0 -#define UI_SETTINGS_GENERAL_INDEX_LANGUAGE_DETAIL 1 -#define UI_SETTINGS_GENERAL_INDEX_APPLY 2 -#define UI_SETTINGS_GENERAL_LABEL_MAX 32 -#define UI_SETTINGS_GENERAL_INFO_MAX 96 - -typedef struct { - uimenu_t menu; - uimenuitem_t items[UI_SETTINGS_GENERAL_ITEM_COUNT]; - const char_t *localeNames[LOCALE_LIST_COUNT]; - char_t languageLabel[UI_SETTINGS_GENERAL_LABEL_MAX]; - char_t labelInfo[UI_SETTINGS_GENERAL_INFO_MAX]; -} uisettingsgeneral_t; - -#define UI_SETTINGS_INPUT_ITEM_COUNT 2 -#define UI_SETTINGS_INPUT_LABEL_MAX 32 -#define UI_SETTINGS_INPUT_PLACEHOLDER_MAX 64 -#define UI_SETTINGS_INPUT_INDEX_DEADZONE 0 -#define UI_SETTINGS_INPUT_INDEX_APPLY 1 - -typedef struct { - uimenu_t menu; - uimenuitem_t items[UI_SETTINGS_INPUT_ITEM_COUNT]; - char_t deadzoneLabel[UI_SETTINGS_INPUT_LABEL_MAX]; - char_t placeholderLabel[UI_SETTINGS_INPUT_PLACEHOLDER_MAX]; -} uisettingsinput_t; - -#define UI_SETTINGS_DISPLAY_ITEM_COUNT 2 -#define UI_SETTINGS_DISPLAY_INDEX_APPLY 1 -#define UI_SETTINGS_DISPLAY_PLACEHOLDER_MAX 64 - -typedef struct { - uimenu_t menu; - uimenuitem_t items[UI_SETTINGS_DISPLAY_ITEM_COUNT]; - char_t placeholderLabel[UI_SETTINGS_DISPLAY_PLACEHOLDER_MAX]; -} uisettingsdisplay_t; - -#define UI_SETTINGS_AUDIO_ITEM_COUNT 2 -#define UI_SETTINGS_AUDIO_INDEX_APPLY 1 -#define UI_SETTINGS_AUDIO_PLACEHOLDER_MAX 64 - -typedef struct { - uimenu_t menu; - uimenuitem_t items[UI_SETTINGS_AUDIO_ITEM_COUNT]; - char_t placeholderLabel[UI_SETTINGS_AUDIO_PLACEHOLDER_MAX]; -} uisettingsaudio_t; - -/** - * Shared, overlapping storage for every settings tab's page data. Only - * one member is ever live at a time -- the active tab's Init writes - * into it, overwriting whatever the previously active tab left there. - * This trades simultaneous access to every tab for a memory footprint - * of only the single largest page. - */ -typedef union { - uisettingsgeneral_t general; - uisettingsinput_t input; - uisettingsdisplay_t display; - uisettingsaudio_t audio; -} uisettingsdata_t; diff --git a/src/dusk/ui/frame/settings/uisettingsdisplay.c b/src/dusk/ui/frame/settings/uisettingsdisplay.c deleted file mode 100644 index 39760fcc..00000000 --- a/src/dusk/ui/frame/settings/uisettingsdisplay.c +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uisettingsdisplay.h" -#include "uisettings.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "locale/localemanager.h" -#include "asset/loader/locale/assetlocaleloader.h" - -void uiSettingsDisplaySelected( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -) { - if(index != UI_SETTINGS_DISPLAY_INDEX_APPLY) return; - uiSettingsDisplayApply(); -} - -errorret_t uiSettingsDisplayInit(uisettingsdata_t *data) { - uisettingsdisplay_t *display = &data->display; - memoryZero(display, sizeof(uisettingsdisplay_t)); - - errorChain(assetLocaleGetString( - &LOCALE.entry->data.locale, - "ui.settings.display.placeholder", - 0, - display->placeholderLabel, - UI_SETTINGS_DISPLAY_PLACEHOLDER_MAX - )); - - MENU_BEGIN( - &display->menu, display->items, - uiSettingsDisplaySelected, uiSettingsDiscardMenuClosed, NULL - ); - MENU_LABEL(display->placeholderLabel); - MENU_BUTTON(UI_SETTINGS.applyLabel); - - MENU_END(display->items, 1); - - errorOk(); -} - -void uiSettingsDisplayLoad(void) { -} - -void uiSettingsDisplayApply(void) { - uiMenuClose(&UI_SETTINGS.data.display.menu); -} - -bool_t uiSettingsDisplayHasChanges(void) { - return false; -} diff --git a/src/dusk/ui/frame/settings/uisettingsdisplay.h b/src/dusk/ui/frame/settings/uisettingsdisplay.h deleted file mode 100644 index 5811db0f..00000000 --- a/src/dusk/ui/frame/settings/uisettingsdisplay.h +++ /dev/null @@ -1,36 +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 "uisettingsdata.h" - -/** - * Initializes the display settings page into the shared settings data - * union. - * - * @param data The shared settings data union to initialize into. - * @return Any error that occurs. - */ -errorret_t uiSettingsDisplayInit(uisettingsdata_t *data); - -/** - * Loads the page's widgets from the current engine display settings. - */ -void uiSettingsDisplayLoad(void); - -/** - * Applies the page's widget values to the engine display settings. - */ -void uiSettingsDisplayApply(void); - -/** - * Returns whether any widget's value differs from what was loaded. - * - * @returns True if there are unsaved changes. - */ -bool_t uiSettingsDisplayHasChanges(void); diff --git a/src/dusk/ui/frame/settings/uisettingsgeneral.c b/src/dusk/ui/frame/settings/uisettingsgeneral.c deleted file mode 100644 index 865b0db7..00000000 --- a/src/dusk/ui/frame/settings/uisettingsgeneral.c +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uisettingsgeneral.h" -#include "uisettings.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "locale/localemanager.h" -#include "locale/localeinfo.h" -#include "asset/loader/locale/assetlocaleloader.h" -#include "save/save.h" -#include "error/error.h" - -static void uiSettingsGeneralSaveComplete(errorret_t result, void *user) { - if(errorIsNotOk(result)) errorCatch(errorPrint(result)); -} - -void uiSettingsGeneralSelected( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -) { - switch(index) { - case UI_SETTINGS_GENERAL_INDEX_APPLY: - uiSettingsGeneralApply(); - break; - - default: - break; - } -} - -errorret_t uiSettingsGeneralInit(uisettingsdata_t *data) { - uisettingsgeneral_t *general = &data->general; - memoryZero(general, sizeof(uisettingsgeneral_t)); - - for(uint8_t i = 0; i < LOCALE_LIST_COUNT; i++) { - general->localeNames[i] = LOCALE_LIST[i]->name; - } - - errorChain(assetLocaleGetString( - &LOCALE.entry->data.locale, - "ui.settings.general.language", - 0, - general->languageLabel, - UI_SETTINGS_GENERAL_LABEL_MAX - )); - errorChain(assetLocaleGetString( - &LOCALE.entry->data.locale, - "ui.settings.general.language_detail", - 0, - general->labelInfo, - UI_SETTINGS_GENERAL_INFO_MAX - )); - - MENU_BEGIN( - &general->menu, general->items, - uiSettingsGeneralSelected, uiSettingsDiscardMenuClosed, NULL - ); - - MENU_DROPDOWN( - general->languageLabel, general->localeNames, - LOCALE_LIST_COUNT, 0 - ); - MENU_LABEL(general->labelInfo); - - MENU_BUTTON(UI_SETTINGS.applyLabel); - MENU_END(general->items, 1); - - errorOk(); -} - -void uiSettingsGeneralLoad(void) { - uisettingsgeneral_t *general = &UI_SETTINGS.data.general; - - uint8_t index = localeManagerGetIndex(LOCALE.locale); - - uiDropdownSetSelectedIndex( - &general->items[UI_SETTINGS_GENERAL_INDEX_LANGUAGE].dropdown, index - ); -} - -void uiSettingsGeneralApply(void) { - uisettingsgeneral_t *general = &UI_SETTINGS.data.general; - - uint8_t index = uiDropdownGetSelectedIndex( - &general->items[UI_SETTINGS_GENERAL_INDEX_LANGUAGE].dropdown - ); - errorCatch(localeManagerSetLocale(LOCALE_LIST[index])); - - saveGetMeta()->language = index; - saveWriteMeta(uiSettingsGeneralSaveComplete, NULL); - - uiMenuClose(&general->menu); -} - -bool_t uiSettingsGeneralHasChanges(void) { - uisettingsgeneral_t *general = &UI_SETTINGS.data.general; - - uint8_t index = uiDropdownGetSelectedIndex( - &general->items[UI_SETTINGS_GENERAL_INDEX_LANGUAGE].dropdown - ); - return localeManagerGetIndex(LOCALE.locale) != index; -} diff --git a/src/dusk/ui/frame/settings/uisettingsgeneral.h b/src/dusk/ui/frame/settings/uisettingsgeneral.h deleted file mode 100644 index 47dbf8ee..00000000 --- a/src/dusk/ui/frame/settings/uisettingsgeneral.h +++ /dev/null @@ -1,36 +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 "uisettingsdata.h" - -/** - * Initializes the general settings page into the shared settings data - * union. - * - * @param data The shared settings data union to initialize into. - * @return Any error that occurs. - */ -errorret_t uiSettingsGeneralInit(uisettingsdata_t *data); - -/** - * Loads the page's widgets from the current engine general settings. - */ -void uiSettingsGeneralLoad(void); - -/** - * Applies the page's widget values to the engine general settings. - */ -void uiSettingsGeneralApply(void); - -/** - * Returns whether any widget's value differs from what was loaded. - * - * @returns True if there are unsaved changes. - */ -bool_t uiSettingsGeneralHasChanges(void); diff --git a/src/dusk/ui/frame/settings/uisettingsinput.c b/src/dusk/ui/frame/settings/uisettingsinput.c deleted file mode 100644 index d1ebd17f..00000000 --- a/src/dusk/ui/frame/settings/uisettingsinput.c +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uisettingsinput.h" -#include "uisettings.h" -#include "assert/assert.h" -#include "util/memory.h" -#include "locale/localemanager.h" -#include "asset/loader/locale/assetlocaleloader.h" -#include "save/save.h" - -static void uiSettingsInputSaveComplete(errorret_t result, void *user) { - if(errorIsNotOk(result)) errorCatch(errorPrint(result)); -} - -void uiSettingsInputSelected( - const uimenu_t *menu, - const uint8_t index, - const uimenuitem_t *item -) { - if(index != UI_SETTINGS_INPUT_INDEX_APPLY) return; - uiSettingsInputApply(); -} - -errorret_t uiSettingsInputInit(uisettingsdata_t *data) { - uisettingsinput_t *input = &data->input; - memoryZero(input, sizeof(uisettingsinput_t)); - - MENU_BEGIN( - &input->menu, input->items, - uiSettingsInputSelected, uiSettingsDiscardMenuClosed, NULL - ); - #ifdef DUSK_INPUT_GAMEPAD - errorChain(assetLocaleGetString( - &LOCALE.entry->data.locale, - "ui.settings.input.deadzone", - 0, - input->deadzoneLabel, - UI_SETTINGS_INPUT_LABEL_MAX - )); - MENU_SLIDER_FLOAT( - input->deadzoneLabel, SAVE_META_DEADZONE_DEFAULT, 0.0f, 1.0f, 0.05f - ); - #else - errorChain(assetLocaleGetString( - &LOCALE.entry->data.locale, - "ui.settings.input.placeholder", - 0, - input->placeholderLabel, - UI_SETTINGS_INPUT_PLACEHOLDER_MAX - )); - MENU_LABEL(input->placeholderLabel); - #endif - - MENU_BUTTON(UI_SETTINGS.applyLabel); - MENU_END(input->items, 1); - - errorOk(); -} - -void uiSettingsInputLoad(void) { - #ifdef DUSK_INPUT_GAMEPAD - uiSliderSetFloat( - &UI_SETTINGS.data.input.items[UI_SETTINGS_INPUT_INDEX_DEADZONE].slider, - saveGetMeta()->deadzone - ); - #endif -} - -void uiSettingsInputApply(void) { - #ifdef DUSK_INPUT_GAMEPAD - saveGetMeta()->deadzone = uiSliderGetFloat( - &UI_SETTINGS.data.input.items[UI_SETTINGS_INPUT_INDEX_DEADZONE].slider - ); - saveWriteMeta(uiSettingsInputSaveComplete, NULL); - #endif - uiMenuClose(&UI_SETTINGS.data.input.menu); -} - -bool_t uiSettingsInputHasChanges(void) { - #ifdef DUSK_INPUT_GAMEPAD - if(uiSliderGetFloat( - &UI_SETTINGS.data.input.items[UI_SETTINGS_INPUT_INDEX_DEADZONE].slider - ) != saveGetMeta()->deadzone) return true; - #endif - - return false; -} diff --git a/src/dusk/ui/frame/settings/uisettingsinput.h b/src/dusk/ui/frame/settings/uisettingsinput.h deleted file mode 100644 index 3fad214e..00000000 --- a/src/dusk/ui/frame/settings/uisettingsinput.h +++ /dev/null @@ -1,36 +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 "uisettingsdata.h" - -/** - * Initializes the input settings page into the shared settings data - * union. - * - * @param data The shared settings data union to initialize into. - * @return Any error that occurs. - */ -errorret_t uiSettingsInputInit(uisettingsdata_t *data); - -/** - * Loads the page's widgets from the current engine input settings. - */ -void uiSettingsInputLoad(void); - -/** - * Applies the page's widget values to the engine input settings. - */ -void uiSettingsInputApply(void); - -/** - * Returns whether any widget's value differs from what was loaded. - * - * @returns True if there are unsaved changes. - */ -bool_t uiSettingsInputHasChanges(void); diff --git a/src/dusk/ui/overlay/CMakeLists.txt b/src/dusk/ui/overlay/CMakeLists.txt index 1aed33ef..d6c051b3 100644 --- a/src/dusk/ui/overlay/CMakeLists.txt +++ b/src/dusk/ui/overlay/CMakeLists.txt @@ -8,5 +8,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} uicrop.c uifullbox.c uiloading.c - uiautosave.c ) diff --git a/src/dusk/ui/overlay/uiautosave.c b/src/dusk/ui/overlay/uiautosave.c deleted file mode 100644 index bbc5db5c..00000000 --- a/src/dusk/ui/overlay/uiautosave.c +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "uiautosave.h" -#include "save/autosave.h" -#include "display/text/text.h" -#include "display/screen/screen.h" -#include "display/spritebatch/spritebatch.h" -#include "util/memory.h" -#include "locale/localemanager.h" -#include "asset/loader/locale/assetlocaleloader.h" - -uiautosave_t UI_AUTO_SAVE; - -errorret_t uiAutoSaveInit(void) { - memoryZero(&UI_AUTO_SAVE, sizeof(uiautosave_t)); - - errorChain(assetLocaleGetString( - &LOCALE.entry->data.locale, - "ui.autosave.saving", - 0, - UI_AUTO_SAVE.text, - UI_AUTOSAVE_TEXT_MAX - )); - - errorOk(); -} - -errorret_t uiAutoSaveDraw(void) { - if(!autoSaveIsSaving()) errorOk(); - - int32_t textW, textH; - textMeasure(UI_AUTO_SAVE.text, &FONT_DEFAULT, &textW, &textH); - - float_t x = (float_t)SCREEN.scanX + UI_AUTOSAVE_MARGIN; - float_t y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - - (float_t)textH - UI_AUTOSAVE_MARGIN; - - errorChain(textDraw(x, y, UI_AUTO_SAVE.text, COLOR_WHITE, &FONT_DEFAULT)); - return spriteBatchFlush(); -} diff --git a/src/dusk/ui/overlay/uiautosave.h b/src/dusk/ui/overlay/uiautosave.h deleted file mode 100644 index 94cc3d95..00000000 --- a/src/dusk/ui/overlay/uiautosave.h +++ /dev/null @@ -1,34 +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" - -#define UI_AUTOSAVE_MARGIN 8.0f -#define UI_AUTOSAVE_TEXT_MAX 32 - -typedef struct { - char_t text[UI_AUTOSAVE_TEXT_MAX]; -} uiautosave_t; - -extern uiautosave_t UI_AUTO_SAVE; - -/** - * Loads the "Saving" indicator text from the active locale. - * - * @return Any error that occurs. - */ -errorret_t uiAutoSaveInit(void); - -/** - * Draws a "Saving" indicator in the bottom-left corner of the screen - * while an autosave write is in flight (see save/autosave.h). No-op - * otherwise. - * - * @return Any error that occurs. - */ -errorret_t uiAutoSaveDraw(void); diff --git a/src/dusk/ui/rpg/textbox/uitextboxmini.c b/src/dusk/ui/rpg/textbox/uitextboxmini.c index f04515c6..251eaa3b 100644 --- a/src/dusk/ui/rpg/textbox/uitextboxmini.c +++ b/src/dusk/ui/rpg/textbox/uitextboxmini.c @@ -27,7 +27,7 @@ void uiTextboxMiniInit(uitextboxmini_t *mini) { void uiTextboxMiniShow( uitextboxmini_t *mini, const char_t *text, - vec3 position, + const vec3 position, const float_t duration, uitextboxminiclosedcallback_t closed, void *user diff --git a/src/dusk/ui/rpg/textbox/uitextboxmini.h b/src/dusk/ui/rpg/textbox/uitextboxmini.h index f173b3c5..16cf3f4b 100644 --- a/src/dusk/ui/rpg/textbox/uitextboxmini.h +++ b/src/dusk/ui/rpg/textbox/uitextboxmini.h @@ -72,7 +72,7 @@ void uiTextboxMiniInit(uitextboxmini_t *mini); void uiTextboxMiniShow( uitextboxmini_t *mini, const char_t *text, - vec3 position, + const vec3 position, const float_t duration, uitextboxminiclosedcallback_t closed, void *user diff --git a/src/dusk/ui/ui.c b/src/dusk/ui/ui.c index 566672fc..2cd586d1 100644 --- a/src/dusk/ui/ui.c +++ b/src/dusk/ui/ui.c @@ -11,6 +11,7 @@ #include "display/spritebatch/spritebatch.h" #include "display/screen/screen.h" #include "ui/uielement.h" +#include "ui/uielementlist.h" #include "ui/focus/uifocus.h" #include "log/log.h" @@ -63,4 +64,13 @@ errorret_t uiDispose(void) { element++; } errorOk(); +} + +errorret_t uiUpdateTranslations(void) { + uielement_t *element = &UI_ELEMENTS[0]; + while(!uiElementIsNull(element)) { + errorChain(uiElementUpdateTranslations(element)); + element++; + } + errorOk(); } \ No newline at end of file diff --git a/src/dusk/ui/ui.h b/src/dusk/ui/ui.h index 2887a262..8d6a88ad 100644 --- a/src/dusk/ui/ui.h +++ b/src/dusk/ui/ui.h @@ -38,4 +38,12 @@ errorret_t uiRender(void); * * @return Any error that occurs. */ -errorret_t uiDispose(void); \ No newline at end of file +errorret_t uiDispose(void); + +/** + * Notifies the UI system that the active locale has changed, so every + * registered element can re-fetch any localized strings it caches. + * + * @return Any error that occurs. + */ +errorret_t uiUpdateTranslations(void); \ No newline at end of file diff --git a/src/dusk/ui/uielement.c b/src/dusk/ui/uielement.c index 7ca67946..064aba75 100644 --- a/src/dusk/ui/uielement.c +++ b/src/dusk/ui/uielement.c @@ -8,154 +8,14 @@ #include "uielement.h" #include "assert/assert.h" #include "ui/frame/uiframe.h" -#include "ui/debug/uifps.h" #include "engine/engine.h" -#include "ui/overlay/uifullbox.h" -#include "ui/overlay/uiloading.h" -#include "ui/overlay/uiautosave.h" -#include "ui/debug/uiplayerpos.h" -#include "ui/overlay/uicrop.h" -#include "ui/transition/uitransition.h" -#include "ui/debug/uiconsole.h" -#include "ui/frame/settings/uisettings.h" -#include "ui/frame/game/uigamemenu.h" -#include "ui/frame/mainmenu/uimainmenu.h" -#include "ui/frame/battle/uibattlemenu.h" -#include "ui/frame/battle/uibattlehud.h" -#include "ui/frame/backpack/uibackpack.h" -#include "ui/frame/uiconfirm.h" -#include "ui/frame/initial/uiinitialnocard.h" -#include "ui/frame/initial/uiinitialcreatesave.h" -#include "ui/rpg/textbox/uitextboxmain.h" -#include "ui/rpg/textbox/uitextboxminilist.h" -#include "ui/rpg/uiemoji.h" - -uielement_t UI_ELEMENTS[] = { - { - .init = uiFrameInit, - .dispose = uiFrameDispose - }, - - // Fullbox under: above scene, below system UI. - { - .init = uiFullboxUnderInit, - .update = uiFullboxUnderUpdate, - .draw = uiFullboxUnderDraw - }, - - // in world stuff - { - .init = uiEmojiInit, - .update = uiEmojiUpdate, - .draw = uiEmojiDraw, - .dispose = uiEmojiDispose - }, - - // Ingame menus - { - .init = uiGameMenuInit, - .draw = uiGameMenuDraw, - .dispose = uiGameMenuDispose - }, - - { - .init = uiMainMenuInit, - .update = uiMainMenuUpdate, - .draw = uiMainMenuDraw, - .dispose = uiMainMenuDispose - }, - - { .init = uiBattleHudInit, .draw = uiBattleHudDraw }, - - { - .init = uiBattleMenuInit, - .update = uiBattleMenuUpdate, - .draw = uiBattleMenuDraw - }, - - { - .init = uiBackpackInit, - .draw = uiBackpackDraw, - .dispose = uiBackpackDispose - }, - - { - .init = uiSettingsInit, - .update = uiSettingsUpdate, - .draw = uiSettingsDraw, - .dispose = uiSettingsDispose - }, - - // Text stuffs - { - .init = uiConfirmInit, - .draw = uiConfirmDraw, - .dispose = uiConfirmDispose - }, - - { - .init = uiInitialNoCardInit, - .draw = uiInitialNoCardDraw, - .dispose = uiInitialNoCardDispose - }, - - { - .init = uiInitialCreateSaveInit, - .draw = uiInitialCreateSaveDraw, - .dispose = uiInitialCreateSaveDispose - }, - - { - .init = uiTextboxMainInit, - .update = uiTextboxMainUpdate, - .draw = uiTextboxMainDraw - }, - - { - .init = uiTextboxMiniListInit, - .update = uiTextboxMiniListUpdate, - .draw = uiTextboxMiniListDraw - }, - - { - .init = uiTransitionInit, - .update = uiTransitionUpdate, - .draw = uiTransitionDraw - }, - - // Fullbox over: above absolutely everything. - { - .init = uiFullboxOverInit, - .update = uiFullboxOverUpdate, - .draw = uiFullboxOverDraw - }, - - { - .init = uiLoadingInit, - .update = uiLoadingUpdate, - .draw = uiLoadingDraw - }, - - { - .init = uiCropInit, - .draw = uiCropDraw - }, - - { .init = uiAutoSaveInit, .draw = uiAutoSaveDraw }, - - // Debug items - { .draw = uiConsoleDraw }, - { .draw = uiFPSDraw }, - { .draw = uiPlayerPosDraw }, - - { 0 } // Null terminator -}; bool_t uiElementIsNull(const uielement_t *element) { return element->init == NULL && element->update == NULL && element->draw == NULL && - element->dispose == NULL; + element->dispose == NULL && + element->updateTranslations == NULL; } errorret_t uiElementInit(uielement_t *element) { @@ -182,4 +42,12 @@ errorret_t uiElementDispose(uielement_t *element) { if(element->dispose != NULL) errorChain(element->dispose()); errorChain(uiFrameDispose()); errorOk(); +} + +errorret_t uiElementUpdateTranslations(uielement_t *element) { + assertNotNull(element, "element must not be NULL"); + if(element->updateTranslations != NULL) { + errorChain(element->updateTranslations()); + } + errorOk(); } \ No newline at end of file diff --git a/src/dusk/ui/uielement.h b/src/dusk/ui/uielement.h index 49db9d60..d4fdc74f 100644 --- a/src/dusk/ui/uielement.h +++ b/src/dusk/ui/uielement.h @@ -13,13 +13,12 @@ typedef struct { errorret_t (*update)(); errorret_t (*draw)(); errorret_t (*dispose)(); + errorret_t (*updateTranslations)(); } uielement_t; -extern uielement_t UI_ELEMENTS[]; - /** - * Returns true when all four callbacks on the element are NULL, - * which marks the end of the UI_ELEMENTS array. + * Returns true when all callbacks on the element are NULL, which marks + * the end of the UI_ELEMENTS array. * * @param element The element to test. * @returns True if the element is the null terminator. @@ -57,3 +56,13 @@ errorret_t uiElementDraw(const uielement_t *element); * @return Any error that occurs. */ errorret_t uiElementDispose(uielement_t *element); + +/** + * Notifies a UI element that the active locale has changed, calling its + * updateTranslations callback if set, so it can re-fetch any localized + * strings it caches. + * + * @param element The element to notify. + * @return Any error that occurs. + */ +errorret_t uiElementUpdateTranslations(uielement_t *element); diff --git a/src/dusk/ui/uielementlist.c b/src/dusk/ui/uielementlist.c new file mode 100644 index 00000000..585798f7 --- /dev/null +++ b/src/dusk/ui/uielementlist.c @@ -0,0 +1,139 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "uielementlist.h" +#include "ui/frame/uiframe.h" +#include "ui/debug/uifps.h" +#include "ui/overlay/uifullbox.h" +#include "ui/overlay/uiloading.h" +#include "ui/debug/uiplayerpos.h" +#include "ui/overlay/uicrop.h" +#include "ui/transition/uitransition.h" +#include "ui/debug/uiconsole.h" +#include "ui/frame/game/uigamemenu.h" +#include "ui/frame/mainmenu/uimainmenu.h" +#include "ui/frame/battle/uibattlemenu.h" +#include "ui/frame/battle/uibattlehud.h" +#include "ui/frame/backpack/uibackpack.h" +#include "ui/frame/uiconfirm.h" +#include "ui/frame/initial/uiinitialnocard.h" +#include "ui/frame/initial/uiinitialcreatesave.h" +#include "ui/rpg/textbox/uitextboxmain.h" +#include "ui/rpg/textbox/uitextboxminilist.h" +#include "ui/rpg/uiemoji.h" + +uielement_t UI_ELEMENTS[] = { + { + .init = uiFrameInit, + .dispose = uiFrameDispose + }, + + // Fullbox under: above scene, below system UI. + { + .init = uiFullboxUnderInit, + .update = uiFullboxUnderUpdate, + .draw = uiFullboxUnderDraw + }, + + // in world stuff + { + .init = uiEmojiInit, + .update = uiEmojiUpdate, + .draw = uiEmojiDraw, + .dispose = uiEmojiDispose + }, + + // Ingame menus + { + .init = uiGameMenuInit, + .draw = uiGameMenuDraw, + .dispose = uiGameMenuDispose + }, + + { + .init = uiMainMenuInit, + .update = uiMainMenuUpdate, + .draw = uiMainMenuDraw, + .dispose = uiMainMenuDispose + }, + + { .init = uiBattleHudInit, .draw = uiBattleHudDraw }, + + { + .init = uiBattleMenuInit, + .update = uiBattleMenuUpdate, + .draw = uiBattleMenuDraw + }, + + { + .init = uiBackpackInit, + .draw = uiBackpackDraw, + .dispose = uiBackpackDispose + }, + + // Text stuffs + { + .init = uiConfirmInit, + .draw = uiConfirmDraw, + .dispose = uiConfirmDispose + }, + + { + .init = uiInitialNoCardInit, + .draw = uiInitialNoCardDraw, + .dispose = uiInitialNoCardDispose + }, + + { + .init = uiInitialCreateSaveInit, + .draw = uiInitialCreateSaveDraw, + .dispose = uiInitialCreateSaveDispose + }, + + { + .init = uiTextboxMainInit, + .update = uiTextboxMainUpdate, + .draw = uiTextboxMainDraw + }, + + { + .init = uiTextboxMiniListInit, + .update = uiTextboxMiniListUpdate, + .draw = uiTextboxMiniListDraw + }, + + { + .init = uiTransitionInit, + .update = uiTransitionUpdate, + .draw = uiTransitionDraw + }, + + // Fullbox over: above absolutely everything. + { + .init = uiFullboxOverInit, + .update = uiFullboxOverUpdate, + .draw = uiFullboxOverDraw + }, + + { + .init = uiLoadingInit, + .update = uiLoadingUpdate, + .draw = uiLoadingDraw + }, + + { + .init = uiCropInit, + .draw = uiCropDraw + }, + + // Debug items + { .draw = uiConsoleDraw }, + { .draw = uiFPSDraw }, + { .draw = uiPlayerPosDraw }, + + { 0 } // Null terminator +}; diff --git a/src/dusk/ui/uielementlist.h b/src/dusk/ui/uielementlist.h new file mode 100644 index 00000000..11bbca33 --- /dev/null +++ b/src/dusk/ui/uielementlist.h @@ -0,0 +1,11 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "ui/uielement.h" + +extern uielement_t UI_ELEMENTS[]; diff --git a/src/duskdolphin/CMakeLists.txt b/src/duskdolphin/CMakeLists.txt index a5323674..8b6630b0 100644 --- a/src/duskdolphin/CMakeLists.txt +++ b/src/duskdolphin/CMakeLists.txt @@ -22,6 +22,5 @@ add_subdirectory(input) if(DUSK_NETWORK) add_subdirectory(network) endif() -add_subdirectory(save) add_subdirectory(system) add_subdirectory(time) \ No newline at end of file diff --git a/src/duskdolphin/input/inputdolphin.c b/src/duskdolphin/input/inputdolphin.c index 366fb5e8..c9fb4869 100644 --- a/src/duskdolphin/input/inputdolphin.c +++ b/src/duskdolphin/input/inputdolphin.c @@ -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; } \ No newline at end of file diff --git a/src/duskdolphin/save/CMakeLists.txt b/src/duskdolphin/save/CMakeLists.txt deleted file mode 100644 index 95b853df..00000000 --- a/src/duskdolphin/save/CMakeLists.txt +++ /dev/null @@ -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 -) diff --git a/src/duskdolphin/save/savedolphin.c b/src/duskdolphin/save/savedolphin.c deleted file mode 100644 index f8660138..00000000 --- a/src/duskdolphin/save/savedolphin.c +++ /dev/null @@ -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"; - } -} diff --git a/src/duskdolphin/save/savedolphin.h b/src/duskdolphin/save/savedolphin.h deleted file mode 100644 index 96180e67..00000000 --- a/src/duskdolphin/save/savedolphin.h +++ /dev/null @@ -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 - -#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); diff --git a/src/duskdolphin/save/saveplatform.h b/src/duskdolphin/save/saveplatform.h deleted file mode 100644 index dc8144ae..00000000 --- a/src/duskdolphin/save/saveplatform.h +++ /dev/null @@ -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) diff --git a/src/duskdolphin/save/savestreamdolphin.c b/src/duskdolphin/save/savestreamdolphin.c deleted file mode 100644 index d79418af..00000000 --- a/src/duskdolphin/save/savestreamdolphin.c +++ /dev/null @@ -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(); -} diff --git a/src/duskdolphin/save/savestreamdolphin.h b/src/duskdolphin/save/savestreamdolphin.h deleted file mode 100644 index e84cbf61..00000000 --- a/src/duskdolphin/save/savestreamdolphin.h +++ /dev/null @@ -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 -#include - -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); diff --git a/src/duskdolphin/system/systemdolphin.c b/src/duskdolphin/system/systemdolphin.c index 19915b16..b1e7d419 100644 --- a/src/duskdolphin/system/systemdolphin.c +++ b/src/duskdolphin/system/systemdolphin.c @@ -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; + } } \ No newline at end of file diff --git a/src/duskdolphin/system/systemdolphin.h b/src/duskdolphin/system/systemdolphin.h index 41322641..c241e594 100644 --- a/src/duskdolphin/system/systemdolphin.h +++ b/src/duskdolphin/system/systemdolphin.h @@ -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. \ No newline at end of file diff --git a/src/duskdolphin/system/systemplatform.h b/src/duskdolphin/system/systemplatform.h index 483f7eef..dda9116d 100644 --- a/src/duskdolphin/system/systemplatform.h +++ b/src/duskdolphin/system/systemplatform.h @@ -9,4 +9,5 @@ #include "system/systemdolphin.h" #define systemInitPlatform systemInitDolphin -#define systemGetActiveDialogTypePlatform systemGetActiveDialogTypeDolphin \ No newline at end of file +#define systemGetActiveDialogTypePlatform systemGetActiveDialogTypeDolphin +#define systemGetLocalePlatform systemGetLocaleDolphin \ No newline at end of file diff --git a/src/dusklinux/CMakeLists.txt b/src/dusklinux/CMakeLists.txt index b025af73..9aff40e1 100644 --- a/src/dusklinux/CMakeLists.txt +++ b/src/dusklinux/CMakeLists.txt @@ -16,6 +16,5 @@ add_subdirectory(input) if(DUSK_NETWORK) add_subdirectory(network) endif() -add_subdirectory(save) add_subdirectory(system) add_subdirectory(time) \ No newline at end of file diff --git a/src/dusklinux/input/inputlinux.c b/src/dusklinux/input/inputlinux.c index 5cf39fb0..d3523a50 100644 --- a/src/dusklinux/input/inputlinux.c +++ b/src/dusklinux/input/inputlinux.c @@ -6,7 +6,6 @@ */ #include "input/input.h" -#include "save/save.h" inputbuttondata_t INPUT_BUTTON_DATA[] = { #ifdef DUSK_INPUT_GAMEPAD @@ -548,5 +547,5 @@ errorret_t inputInitLinux(void) { } float_t inputGetDeadzoneSDL2(const inputbutton_t button) { - return saveGetMeta()->deadzone; + return 0.2f; } \ No newline at end of file diff --git a/src/dusklinux/save/CMakeLists.txt b/src/dusklinux/save/CMakeLists.txt deleted file mode 100644 index 5714936f..00000000 --- a/src/dusklinux/save/CMakeLists.txt +++ /dev/null @@ -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 - savelinux.c - savejsonlinux.c -) diff --git a/src/dusklinux/save/savejsonlinux.c b/src/dusklinux/save/savejsonlinux.c deleted file mode 100644 index 55f20773..00000000 --- a/src/dusklinux/save/savejsonlinux.c +++ /dev/null @@ -1,175 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "save/savejsonlinux.h" -#include "util/string.h" -#include - -errorret_t saveJsonWriterInitLinux(savejsonwriterlinux_t *writer) { - writer->doc = yyjson_mut_doc_new(NULL); - if(!writer->doc) { - errorThrow("Failed to allocate JSON document"); - } - writer->root = yyjson_mut_obj(writer->doc); - yyjson_mut_doc_set_root(writer->doc, writer->root); - errorOk(); -} - -void saveJsonWriterAddUInt32Linux( - savejsonwriterlinux_t *writer, const char_t *key, const uint32_t value -) { - yyjson_mut_obj_add_uint(writer->doc, writer->root, key, (uint64_t)value); -} - -void saveJsonWriterAddFloatLinux( - savejsonwriterlinux_t *writer, const char_t *key, const float_t value -) { - yyjson_mut_obj_add_real(writer->doc, writer->root, key, (double)value); -} - -void saveJsonWriterAddUInt8Linux( - savejsonwriterlinux_t *writer, const char_t *key, const uint8_t value -) { - yyjson_mut_obj_add_uint(writer->doc, writer->root, key, (uint64_t)value); -} - -void saveJsonWriterAddStringLinux( - savejsonwriterlinux_t *writer, const char_t *key, const char_t *value -) { - yyjson_mut_obj_add_strcpy(writer->doc, writer->root, key, value); -} - -void saveJsonWriterAddBoolArrayLinux( - savejsonwriterlinux_t *writer, const char_t *key, const bool_t *values, - const size_t count -) { - yyjson_mut_val *arr = yyjson_mut_arr(writer->doc); - for(size_t i = 0; i < count; i++) { - yyjson_mut_arr_add_bool(writer->doc, arr, values[i]); - } - yyjson_mut_obj_add_val(writer->doc, writer->root, key, arr); -} - -void saveJsonWriterAddUInt8ArrayLinux( - savejsonwriterlinux_t *writer, const char_t *key, const uint8_t *values, - const size_t count -) { - yyjson_mut_val *arr = yyjson_mut_arr(writer->doc); - for(size_t i = 0; i < count; i++) { - yyjson_mut_arr_add_uint(writer->doc, arr, (uint64_t)values[i]); - } - yyjson_mut_obj_add_val(writer->doc, writer->root, key, arr); -} - -errorret_t saveJsonWriterSaveLinux( - savejsonwriterlinux_t *writer, const char_t *path -) { - yyjson_write_err err; - if(!yyjson_mut_write_file( - path, writer->doc, YYJSON_WRITE_PRETTY, NULL, &err - )) { - errorThrow("Failed to write %s: %s", path, err.msg); - } - errorOk(); -} - -void saveJsonWriterDisposeLinux(savejsonwriterlinux_t *writer) { - if(writer->doc) { - yyjson_mut_doc_free(writer->doc); - writer->doc = NULL; - } -} - -errorret_t saveJsonReaderOpenLinux( - const char_t *path, yyjson_doc **outDoc, yyjson_val **outRoot, - bool_t *found -) { - *outDoc = NULL; - *outRoot = NULL; - - struct stat st; - if(stat(path, &st) != 0) { - *found = false; - errorOk(); - } - - yyjson_read_err err; - *outDoc = yyjson_read_file( - path, YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS, - NULL, &err - ); - if(!*outDoc) { - *found = false; - errorThrow("Failed to parse %s: %s", path, err.msg); - } - - *outRoot = yyjson_doc_get_root(*outDoc); - *found = true; - errorOk(); -} - -uint32_t saveJsonReadUInt32Linux( - yyjson_val *root, const char_t *key, const uint32_t defaultValue -) { - yyjson_val *val = yyjson_obj_get(root, key); - if(!val || !yyjson_is_num(val)) return defaultValue; - return (uint32_t)yyjson_get_uint(val); -} - -float_t saveJsonReadFloatLinux( - yyjson_val *root, const char_t *key, const float_t defaultValue -) { - yyjson_val *val = yyjson_obj_get(root, key); - if(!val || !yyjson_is_num(val)) return defaultValue; - return (float_t)yyjson_get_num(val); -} - -uint8_t saveJsonReadUInt8Linux( - yyjson_val *root, const char_t *key, const uint8_t defaultValue -) { - yyjson_val *val = yyjson_obj_get(root, key); - if(!val || !yyjson_is_num(val)) return defaultValue; - return (uint8_t)yyjson_get_uint(val); -} - -void saveJsonReadStringLinux( - yyjson_val *root, const char_t *key, char_t *out, const size_t maxLen, - const char_t *defaultValue -) { - yyjson_val *val = yyjson_obj_get(root, key); - const char_t *src = defaultValue; - if(val && yyjson_is_str(val)) src = yyjson_get_str(val); - stringCopy(out, src, maxLen); -} - -void saveJsonReadBoolArrayLinux( - yyjson_val *root, const char_t *key, bool_t *out, const size_t count -) { - yyjson_val *arr = yyjson_obj_get(root, key); - if(!arr || !yyjson_is_arr(arr)) return; - - size_t idx, len; - yyjson_val *elem; - yyjson_arr_foreach(arr, idx, len, elem) { - if(idx >= count) break; - if(yyjson_is_bool(elem)) out[idx] = yyjson_get_bool(elem); - } -} - -void saveJsonReadUInt8ArrayLinux( - yyjson_val *root, const char_t *key, uint8_t *out, const size_t count -) { - yyjson_val *arr = yyjson_obj_get(root, key); - if(!arr || !yyjson_is_arr(arr)) return; - - size_t idx, len; - yyjson_val *elem; - yyjson_arr_foreach(arr, idx, len, elem) { - if(idx >= count) break; - if(yyjson_is_int(elem)) out[idx] = (uint8_t)yyjson_get_int(elem); - } -} diff --git a/src/dusklinux/save/savejsonlinux.h b/src/dusklinux/save/savejsonlinux.h deleted file mode 100644 index e8122d42..00000000 --- a/src/dusklinux/save/savejsonlinux.h +++ /dev/null @@ -1,162 +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 "yyjson.h" - -/** - * Small helper around a yyjson mutable document, used to build up a save - * file's fields one at a time before writing it out. Schema-agnostic - - * knows nothing about saveslot_t/savemeta_t; the caller supplies field - * names and values. - */ -typedef struct { - yyjson_mut_doc *doc; - yyjson_mut_val *root; -} savejsonwriterlinux_t; - -/** - * Creates a new mutable JSON document with an empty root object. - * - * @param writer Writer to initialize. - * @return An error if the document can't be allocated. - */ -errorret_t saveJsonWriterInitLinux(savejsonwriterlinux_t *writer); - -/** - * Adds a "version": field to the root object. - */ -void saveJsonWriterAddUInt32Linux( - savejsonwriterlinux_t *writer, const char_t *key, const uint32_t value -); - -/** - * Adds a float field to the root object (written as a JSON number). - */ -void saveJsonWriterAddFloatLinux( - savejsonwriterlinux_t *writer, const char_t *key, const float_t value -); - -/** - * Adds a "key": field to the root object. - */ -void saveJsonWriterAddUInt8Linux( - savejsonwriterlinux_t *writer, const char_t *key, const uint8_t value -); - -/** - * Adds a string field to the root object. The value is copied into the - * document, so the caller's buffer doesn't need to outlive the call. - */ -void saveJsonWriterAddStringLinux( - savejsonwriterlinux_t *writer, const char_t *key, const char_t *value -); - -/** - * Adds a JSON array of booleans as a field on the root object. - */ -void saveJsonWriterAddBoolArrayLinux( - savejsonwriterlinux_t *writer, const char_t *key, const bool_t *values, - const size_t count -); - -/** - * Adds a JSON array of unsigned 8-bit integers as a field on the root - * object. - */ -void saveJsonWriterAddUInt8ArrayLinux( - savejsonwriterlinux_t *writer, const char_t *key, const uint8_t *values, - const size_t count -); - -/** - * Pretty-prints the document to the given file path, creating or - * truncating it. - * - * @param writer Writer holding the document to write. - * @param path Destination file path. - * @return An error if the write fails. - */ -errorret_t saveJsonWriterSaveLinux( - savejsonwriterlinux_t *writer, const char_t *path -); - -/** - * Frees the document. Safe to call even if saveJsonWriterInitLinux() - * failed partway. - * - * @param writer Writer to dispose. - */ -void saveJsonWriterDisposeLinux(savejsonwriterlinux_t *writer); - -/** - * Reads and parses a JSON file, returning its root object. - * - * @param path File path to read. - * @param outDoc Receives the parsed document (must be freed via - * yyjson_doc_free() once done, regardless of found/error outcome). - * @param outRoot Receives the root object, or NULL if not found. - * @param found Set to true if the file exists, false if it does not - * (not finding the file is not an error). - * @return An error if the file exists but fails to parse. - */ -errorret_t saveJsonReaderOpenLinux( - const char_t *path, yyjson_doc **outDoc, yyjson_val **outRoot, - bool_t *found -); - -/** - * Reads a uint32 field, falling back to defaultValue if the key is - * missing or not a number - a hand-edited file shouldn't hard-fail the - * whole load over one bad/missing field. - */ -uint32_t saveJsonReadUInt32Linux( - yyjson_val *root, const char_t *key, const uint32_t defaultValue -); - -/** - * Reads a float field, falling back to defaultValue if the key is - * missing or not a number. - */ -float_t saveJsonReadFloatLinux( - yyjson_val *root, const char_t *key, const float_t defaultValue -); - -/** - * Reads a uint8 field, falling back to defaultValue if the key is - * missing or not a number. - */ -uint8_t saveJsonReadUInt8Linux( - yyjson_val *root, const char_t *key, const uint8_t defaultValue -); - -/** - * Reads a string field into out, falling back to defaultValue if the key - * is missing or not a string. Always null-terminates. - */ -void saveJsonReadStringLinux( - yyjson_val *root, const char_t *key, char_t *out, const size_t maxLen, - const char_t *defaultValue -); - -/** - * Reads a JSON array of booleans into out, up to count entries. Missing - * key, non-array value, or a shorter array all leave the remaining/all - * entries untouched (caller should zero the buffer first). - */ -void saveJsonReadBoolArrayLinux( - yyjson_val *root, const char_t *key, bool_t *out, const size_t count -); - -/** - * Reads a JSON array of unsigned 8-bit integers into out, up to count - * entries. Same forgiving semantics as saveJsonReadBoolArrayLinux(). - */ -void saveJsonReadUInt8ArrayLinux( - yyjson_val *root, const char_t *key, uint8_t *out, const size_t count -); diff --git a/src/dusklinux/save/savelinux.c b/src/dusklinux/save/savelinux.c deleted file mode 100644 index 53fccfb7..00000000 --- a/src/dusklinux/save/savelinux.c +++ /dev/null @@ -1,150 +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/savejsonlinux.h" -#include "util/string.h" -#include "util/memory.h" -#include -#include - -errorret_t saveInitLinux(void) { - stringCopy(SAVE.platform.savePath, SAVE_LINUX_PATH, SAVE_LINUX_PATH_MAX); - - if(mkdir(SAVE.platform.savePath, 0755) != 0 && errno != EEXIST) { - errorThrow("Failed to create save directory: %s", SAVE.platform.savePath); - } - - errorOk(); -} - -errorret_t saveDisposeLinux(void) { - errorOk(); -} - -static void _saveSlotPathLinux( - char_t *out, const size_t max, const uint8_t slot -) { - snprintf( - out, max, SAVE_LINUX_SLOT_FILE_FORMAT, SAVE.platform.savePath, - (uint32_t)slot - ); -} - -static void _saveMetaPathLinux(char_t *out, const size_t max) { - snprintf(out, max, SAVE_LINUX_META_FILE_FORMAT, SAVE.platform.savePath); -} - -errorret_t saveSlotLoadLinux(const uint8_t slot, saveslot_t *out) { - char_t path[SAVE_LINUX_PATH_MAX]; - _saveSlotPathLinux(path, SAVE_LINUX_PATH_MAX, slot); - - yyjson_doc *doc; - yyjson_val *root; - bool_t found; - errorret_t ret = saveJsonReaderOpenLinux(path, &doc, &root, &found); - if(errorIsNotOk(ret)) { yyjson_doc_free(doc); errorChain(ret); } - if(!found) errorOk(); - - memoryZero(out, sizeof(saveslot_t)); - out->version = saveJsonReadUInt32Linux(root, "version", SAVE_SLOT_VERSION); - saveJsonReadStringLinux( - root, "playerName", out->playerName, SAVE_PLAYER_NAME_MAX, "" - ); - saveJsonReadBoolArrayLinux( - root, "globalItemCollected", out->globalItemCollected, - SAVE_GLOBAL_ITEM_COUNT_MAX - ); - saveJsonReadUInt8ArrayLinux( - root, "storyFlags", out->storyFlags, SAVE_STORY_FLAG_COUNT_MAX - ); - out->exists = true; - - yyjson_doc_free(doc); - errorOk(); -} - -errorret_t saveSlotWriteLinux(const uint8_t slot, saveslot_t *slotData) { - char_t path[SAVE_LINUX_PATH_MAX]; - _saveSlotPathLinux(path, SAVE_LINUX_PATH_MAX, slot); - - slotData->version = SAVE_SLOT_VERSION; - - savejsonwriterlinux_t writer; - errorChain(saveJsonWriterInitLinux(&writer)); - saveJsonWriterAddUInt32Linux(&writer, "version", slotData->version); - saveJsonWriterAddStringLinux(&writer, "playerName", slotData->playerName); - saveJsonWriterAddBoolArrayLinux( - &writer, "globalItemCollected", slotData->globalItemCollected, - SAVE_GLOBAL_ITEM_COUNT_MAX - ); - saveJsonWriterAddUInt8ArrayLinux( - &writer, "storyFlags", slotData->storyFlags, SAVE_STORY_FLAG_COUNT_MAX - ); - - errorret_t ret = saveJsonWriterSaveLinux(&writer, path); - saveJsonWriterDisposeLinux(&writer); - errorChain(ret); - - slotData->exists = true; - errorOk(); -} - -errorret_t saveDeleteSlotLinux(const uint8_t slot) { - char_t path[SAVE_LINUX_PATH_MAX]; - _saveSlotPathLinux(path, SAVE_LINUX_PATH_MAX, slot); - - if(remove(path) != 0 && errno != ENOENT) { - errorThrow("Failed to delete save slot %u: %s", (uint32_t)slot, path); - } - - errorOk(); -} - -errorret_t saveMetaLoadLinux(savemeta_t *out) { - char_t path[SAVE_LINUX_PATH_MAX]; - _saveMetaPathLinux(path, SAVE_LINUX_PATH_MAX); - - yyjson_doc *doc; - yyjson_val *root; - bool_t found; - errorret_t ret = saveJsonReaderOpenLinux(path, &doc, &root, &found); - if(errorIsNotOk(ret)) { yyjson_doc_free(doc); errorChain(ret); } - if(!found) errorOk(); - - out->version = saveJsonReadUInt32Linux(root, "version", SAVE_META_VERSION); - out->deadzone = saveJsonReadFloatLinux( - root, "deadzone", SAVE_META_DEADZONE_DEFAULT - ); - out->language = saveJsonReadUInt8Linux( - root, "language", SAVE_META_LANGUAGE_DEFAULT - ); - out->exists = true; - - yyjson_doc_free(doc); - errorOk(); -} - -errorret_t saveMetaWriteLinux(savemeta_t *meta) { - char_t path[SAVE_LINUX_PATH_MAX]; - _saveMetaPathLinux(path, SAVE_LINUX_PATH_MAX); - - meta->version = SAVE_META_VERSION; - - savejsonwriterlinux_t writer; - errorChain(saveJsonWriterInitLinux(&writer)); - saveJsonWriterAddUInt32Linux(&writer, "version", meta->version); - saveJsonWriterAddFloatLinux(&writer, "deadzone", meta->deadzone); - saveJsonWriterAddUInt8Linux(&writer, "language", meta->language); - - errorret_t ret = saveJsonWriterSaveLinux(&writer, path); - saveJsonWriterDisposeLinux(&writer); - errorChain(ret); - - meta->exists = true; - errorOk(); -} diff --git a/src/dusklinux/save/savelinux.h b/src/dusklinux/save/savelinux.h deleted file mode 100644 index 86f9d801..00000000 --- a/src/dusklinux/save/savelinux.h +++ /dev/null @@ -1,80 +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" - -#define SAVE_LINUX_PATH_MAX FILENAME_MAX -#define SAVE_LINUX_SLOT_FILE_FORMAT "%s/slot%u.json" -#define SAVE_LINUX_META_FILE_FORMAT "%s/settings.json" - -#ifndef SAVE_LINUX_PATH - #define SAVE_LINUX_PATH "./saves" -#endif - -typedef struct { - char_t savePath[SAVE_LINUX_PATH_MAX]; -} savelinux_t; - -/** - * Initializes the save system on Linux - ensures the save directory - * exists (shared by both save slots and meta). - * - * @return An error code if initialization fails. - */ -errorret_t saveInitLinux(void); - -/** - * Disposes of the save system on Linux. - * - * @return An error code if disposal fails. - */ -errorret_t saveDisposeLinux(void); - -/** - * Loads a save slot as JSON (slotN.json) from disk, if it exists. - * - * @param slot The save slot index. - * @param out Output slot data. - * @return An error code if the slot exists but fails to parse. - */ -errorret_t saveSlotLoadLinux(const uint8_t slot, saveslot_t *out); - -/** - * Writes a save slot as JSON (slotN.json) to disk. - * - * @param slot The save slot index. - * @param slotData Slot data to write. - * @return An error code if the write fails. - */ -errorret_t saveSlotWriteLinux(const uint8_t slot, saveslot_t *slotData); - -/** - * Deletes the save slot JSON file for the given index. - * - * @param slot The save slot index. - * @return An error code if the delete fails. - */ -errorret_t saveDeleteSlotLinux(const uint8_t slot); - -/** - * Loads save meta as JSON (settings.json) from disk, if it exists. - * - * @param out Output meta data. - * @return An error code if the file exists but fails to parse. - */ -errorret_t saveMetaLoadLinux(savemeta_t *out); - -/** - * Writes save meta as JSON (settings.json) to disk. - * - * @param meta Meta data to write. - * @return An error code if the write fails. - */ -errorret_t saveMetaWriteLinux(savemeta_t *meta); diff --git a/src/dusklinux/save/saveplatform.h b/src/dusklinux/save/saveplatform.h deleted file mode 100644 index a122bb17..00000000 --- a/src/dusklinux/save/saveplatform.h +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "save/savelinux.h" - -typedef savelinux_t saveplatform_t; -// Linux fully overrides every save operation with JSON I/O (see -// savelinux.c/savejsonlinux.c) - nothing generic ever opens a -// savestream_t here, so this only needs to exist for that shared type to -// compile. -typedef struct { - uint8_t reserved; -} saveplatformstream_t; - -#define saveInitPlatform saveInitLinux -#define saveDisposePlatform saveDisposeLinux - -#define saveSlotDeletePlatform saveDeleteSlotLinux -#define saveSlotLoadPlatform saveSlotLoadLinux -#define saveSlotWritePlatform saveSlotWriteLinux -#define saveMetaLoadPlatform saveMetaLoadLinux -#define saveMetaWritePlatform saveMetaWriteLinux diff --git a/src/dusklinux/system/systemlinux.c b/src/dusklinux/system/systemlinux.c index cec2f252..4fd328e3 100644 --- a/src/dusklinux/system/systemlinux.c +++ b/src/dusklinux/system/systemlinux.c @@ -13,4 +13,8 @@ errorret_t systemInitLinux() { systemdialogtype_t systemGetActiveDialogTypeLinux() { return SYSTEM_DIALOG_TYPE_NONE; +} + +const localeinfo_t * systemGetLocaleLinux() { + return &LOCALE_DEFAULT; } \ No newline at end of file diff --git a/src/dusklinux/system/systemlinux.h b/src/dusklinux/system/systemlinux.h index 6bb739be..5059b4d5 100644 --- a/src/dusklinux/system/systemlinux.h +++ b/src/dusklinux/system/systemlinux.h @@ -18,4 +18,12 @@ errorret_t systemInitLinux(void); * * @return Currently open system dialog type. */ -systemdialogtype_t systemGetActiveDialogTypeLinux(); \ No newline at end of file +systemdialogtype_t systemGetActiveDialogTypeLinux(); + +/** + * Returns the current locale of the system. Linux has no system language + * query wired up yet, so this always returns LOCALE_DEFAULT. + * + * @return The current locale. + */ +const localeinfo_t * systemGetLocaleLinux(); \ No newline at end of file diff --git a/src/dusklinux/system/systemplatform.h b/src/dusklinux/system/systemplatform.h index 42e34dbe..0d9f5218 100644 --- a/src/dusklinux/system/systemplatform.h +++ b/src/dusklinux/system/systemplatform.h @@ -9,4 +9,5 @@ #include "system/systemlinux.h" #define systemInitPlatform systemInitLinux -#define systemGetActiveDialogTypePlatform systemGetActiveDialogTypeLinux \ No newline at end of file +#define systemGetActiveDialogTypePlatform systemGetActiveDialogTypeLinux +#define systemGetLocalePlatform systemGetLocaleLinux \ No newline at end of file diff --git a/src/duskpsp/CMakeLists.txt b/src/duskpsp/CMakeLists.txt index 5ca3534c..9e10f8d5 100644 --- a/src/duskpsp/CMakeLists.txt +++ b/src/duskpsp/CMakeLists.txt @@ -21,6 +21,5 @@ add_subdirectory(log) if(DUSK_NETWORK) add_subdirectory(network) endif() -add_subdirectory(save) add_subdirectory(system) add_subdirectory(time) \ No newline at end of file diff --git a/src/duskpsp/input/inputpsp.c b/src/duskpsp/input/inputpsp.c index 4132c924..c3dd4c18 100644 --- a/src/duskpsp/input/inputpsp.c +++ b/src/duskpsp/input/inputpsp.c @@ -6,7 +6,6 @@ */ #include "input/input.h" -#include "save/save.h" // #define INPUT_PSP_GAMEPAD_BUTTON_ACCEPT INPUT_SDL2_GAMEPAD_BUTTON_CUSTOM // #define INPUT_PSP_GAMEPAD_BUTTON_CANCEL INPUT_SDL2_GAMEPAD_BUTTON_CUSTOM @@ -95,5 +94,5 @@ errorret_t inputInitPSP(void) { } float_t inputGetDeadzoneSDL2(const inputbutton_t button) { - return saveGetMeta()->deadzone; + return 0.25f; } \ No newline at end of file diff --git a/src/duskpsp/save/CMakeLists.txt b/src/duskpsp/save/CMakeLists.txt deleted file mode 100644 index 76c70403..00000000 --- a/src/duskpsp/save/CMakeLists.txt +++ /dev/null @@ -1,19 +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 - savepsp.c - savestreampsp.c -) - -# PSP only needs one Dusk-side save slot - a future main-menu save picker -# will let players manage multiple named saves through the OS's own -# sceUtilitySavedata browser instead of Dusk maintaining its own numbered -# slots (see save/saveslot.h for the default used by every other platform). -target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC - SAVE_SLOT_COUNT_MAX=1 -) diff --git a/src/duskpsp/save/saveplatform.h b/src/duskpsp/save/saveplatform.h deleted file mode 100644 index 6f5ea474..00000000 --- a/src/duskpsp/save/saveplatform.h +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "save/savepsp.h" -#include "save/savestreampsp.h" - -typedef savepsp_t saveplatform_t; -typedef savestreampsp_t saveplatformstream_t; - -#define saveInitPlatform saveInitPSP -#define saveDisposePlatform saveDisposePSP -#define saveSlotDeletePlatform saveDeleteSlotPSP - -#define saveStreamReadBytesPlatform(stream, buf, len) \ - saveStreamReadBytesPSP(&(stream)->platform, buf, len) -#define saveStreamWriteBytesPlatform(stream, buf, len) \ - saveStreamWriteBytesPSP(&(stream)->platform, buf, len) -#define saveStreamSeekPlatform(stream, pos) \ - saveStreamSeekPSP(&(stream)->platform, pos) -#define saveStreamTellPlatform(stream, out) \ - saveStreamTellPSP(&(stream)->platform, out) - -// Save/load go entirely through the native sceUtilitySavedata dialog -// (savePSPBeginSave/Load), which spans multiple frames - these bypass -// save.c's normal synchronous open/write-fields/close flow above (that's -// still used internally, just against an in-memory buffer, from within -// savePSPBeginSave/Load themselves) and are what save.c's saveWriteSlot()/ -// saveLoadSlot()/saveWriteMeta()/saveLoadMeta() actually call on this -// platform. Meta and the (one) slot are serialized together into the same -// buffer - there is no separate meta-only path on PSP - so the meta -// variants just drive the same dialog against SAVE_ACTIVE_SLOT. -#define saveSlotAsyncWritePlatform(slot, onComplete, user) \ - savePSPBeginSave(slot, onComplete, user) -#define saveSlotAsyncLoadPlatform(slot, onComplete, user) \ - savePSPBeginLoad(slot, onComplete, user) -#define saveMetaAsyncWritePlatform(onComplete, user) \ - savePSPBeginSave(SAVE_ACTIVE_SLOT, onComplete, user) -#define saveMetaAsyncLoadPlatform(onComplete, user) \ - savePSPBeginLoad(SAVE_ACTIVE_SLOT, onComplete, user) -#define saveIsBusyPlatform() savePSPIsBusy() -#define savePlatformUpdate() savePSPUpdate() - -// Meta only reaches memory via the native dialog now (folded into the same -// payload as the save slot) - running that multi-frame dialog on every -// single boot just to eagerly populate SAVE.meta would reintroduce the -// exact UX problem a lightweight settings-only file used to avoid, so -// saveInit() skips eager loading entirely on this platform. -#define saveSkipEagerLoadPlatform diff --git a/src/duskpsp/save/savepsp.c b/src/duskpsp/save/savepsp.c deleted file mode 100644 index 4fde8bd6..00000000 --- a/src/duskpsp/save/savepsp.c +++ /dev/null @@ -1,291 +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/savepsp.h" -#include "save/savestream.h" -#include "system/systempsp.h" -#include "util/memory.h" -#include "util/string.h" -#include "assert/assert.h" - -static void savePSPParamCommonInit(SceUtilitySavedataParam *param) { - memoryZero(param, sizeof(SceUtilitySavedataParam)); - param->base.size = sizeof(SceUtilitySavedataParam); - param->base.language = systemPSPGetLanguage(); - param->base.buttonSwap = systemPSPGetCrossButtonSetting(); - param->base.graphicsThread = 17; - param->base.accessThread = 19; - param->base.fontThread = 18; - param->base.soundThread = 16; - - stringCopy(param->gameName, SAVE_PSP_GAME_NAME, sizeof(param->gameName)); - stringCopy(param->fileName, SAVE_PSP_FILE_NAME, sizeof(param->fileName)); -} - -static void savePSPSaveNameForSlot( - char_t *out, const size_t max, const uint8_t slot -) { - stringFormat(out, max, "%02u", (uint32_t)slot); -} - -errorret_t saveInitPSP(void) { - SceIoStat stat; - if(sceIoGetstat(SAVE_PSP_ROOT, &stat) < 0) { - errorThrow("No memory stick detected"); - } - errorOk(); -} - -errorret_t saveDisposePSP(void) { - errorOk(); -} - -errorret_t saveDeleteSlotPSP(const uint8_t slot) { - char_t path[SAVE_PSP_PATH_MAX]; - stringFormat( - path, sizeof(path), SAVE_PSP_FILE_FORMAT, SAVE_PSP_GAME_NAME, - (uint32_t)slot - ); - - int32_t result = sceIoRemove(path); - if(result < 0 && result != (int32_t)0x80010002) { - errorThrow("Failed to delete save data for slot %u", (uint32_t)slot); - } - - char_t dir[SAVE_PSP_PATH_MAX]; - stringFormat( - dir, sizeof(dir), "ms0:/PSP/SAVEDATA/%s%02u", SAVE_PSP_GAME_NAME, - (uint32_t)slot - ); - char_t sfoPath[SAVE_PSP_PATH_MAX]; - stringFormat(sfoPath, sizeof(sfoPath), "%s/PARAM.SFO", dir); - // Best-effort - PARAM.SFO/the directory itself may not exist (e.g. this - // slot was written by the old raw-file format, pre-dating this dialog- - // based rewrite) or the directory may still contain other entries. - sceIoRemove(sfoPath); - sceIoRmdir(dir); - - errorOk(); -} - -void savePSPBeginSave( - const uint8_t slot, savecallback_t onComplete, void *user -) { - assertNotNull(onComplete, "onComplete cannot be NULL"); - assertTrue(SAVE.platform.op == SAVE_PSP_OP_NONE, "Save already in progress"); - - saveslot_t *slotData = &SAVE.slots[slot]; - - // Serialize meta then the slot into the buffer synchronously (plain - // memory writes, same header/version/CRC framing as every other - // platform) before the dialog ever starts - only the actual commit-to- - // storage step needs to wait on the dialog. - savestream_t stream; - memoryZero(&stream, sizeof(savestream_t)); - stream.platform.buffer = SAVE.platform.dataBuffer; - stream.platform.bufferSize = sizeof(SAVE.platform.dataBuffer); - - errorret_t ret = saveMetaSerializeWrite(&stream, &SAVE.meta); - if(errorIsOk(ret)) ret = saveSlotSerializeWrite(&stream, slotData); - if(errorIsNotOk(ret)) { - onComplete(ret, user); - return; - } - SAVE.platform.dataLength = stream.platform.length; - - SceUtilitySavedataParam *param = &SAVE.platform.param; - savePSPParamCommonInit(param); - // AUTOSAVE rather than SAVE - SAVE shows a "save to this data?" confirm - // screen even for a slot with no existing data, which isn't the UX we - // want for a menu-triggered "Save" action (that confirmation already - // happened when the player chose to save). AUTOSAVE writes silently - // (just a brief "saving" icon flash) while still generating the same - // PARAM.SFO/title/description as any other mode. - param->mode = PSP_UTILITY_SAVEDATA_AUTOSAVE; - param->overwrite = 1; - savePSPSaveNameForSlot(param->saveName, sizeof(param->saveName), slot); - - param->dataBuf = SAVE.platform.dataBuffer; - param->dataBufSize = sizeof(SAVE.platform.dataBuffer); - param->dataSize = SAVE.platform.dataLength; - - // No ICON0/PIC1/SND0 art exists in this project yet, so these are left - // zeroed (bufSize 0) - the utility treats that as "no icon/background/ - // sound" rather than an error. title/savedataTitle/detail are still - // fully functional and are what actually populates PARAM.SFO and the - // save browser entry. - stringCopy(param->sfoParam.title, "Dusk", sizeof(param->sfoParam.title)); - stringCopy( - param->sfoParam.savedataTitle, slotData->playerName, - sizeof(param->sfoParam.savedataTitle) - ); - stringCopy( - param->sfoParam.detail, "Dusk save file.", sizeof(param->sfoParam.detail) - ); - - int32_t initRet = sceUtilitySavedataInitStart(param); - if(initRet < 0) { - onComplete(errorThrowImpl( - &SAVE.errorState, ERROR_NOT_OK, __FILE__, __func__, __LINE__, - "Failed to start save dialog: 0x%08X", initRet - ), user); - return; - } - - SAVE.platform.op = SAVE_PSP_OP_SAVE; - SAVE.platform.slot = slot; - SAVE.platform.onComplete = onComplete; - SAVE.platform.onCompleteUser = user; -} - -void savePSPBeginLoad( - const uint8_t slot, savecallback_t onComplete, void *user -) { - assertNotNull(onComplete, "onComplete cannot be NULL"); - assertTrue(SAVE.platform.op == SAVE_PSP_OP_NONE, "Save already in progress"); - - char_t path[SAVE_PSP_PATH_MAX]; - stringFormat( - path, sizeof(path), SAVE_PSP_FILE_FORMAT, SAVE_PSP_GAME_NAME, - (uint32_t)slot - ); - - SceIoStat stat; - if(sceIoGetstat(path, &stat) < 0) { - // No save data yet - not an error (matches every other platform's - // "nothing to load yet" behavior), and deliberately skips showing the - // dialog at all rather than surfacing an empty "no data" native - // screen for data the player has never saved. - onComplete(errorOkImpl(), user); - return; - } - - SceUtilitySavedataParam *param = &SAVE.platform.param; - savePSPParamCommonInit(param); - param->mode = PSP_UTILITY_SAVEDATA_AUTOLOAD;// See savePSPBeginSave(). - savePSPSaveNameForSlot(param->saveName, sizeof(param->saveName), slot); - - param->dataBuf = SAVE.platform.dataBuffer; - param->dataBufSize = sizeof(SAVE.platform.dataBuffer); - - int32_t initRet = sceUtilitySavedataInitStart(param); - if(initRet < 0) { - onComplete(errorThrowImpl( - &SAVE.errorState, ERROR_NOT_OK, __FILE__, __func__, __LINE__, - "Failed to start load dialog: 0x%08X", initRet - ), user); - return; - } - - SAVE.platform.op = SAVE_PSP_OP_LOAD; - SAVE.platform.slot = slot; - SAVE.platform.onComplete = onComplete; - SAVE.platform.onCompleteUser = user; -} - -bool_t savePSPIsBusy(void) { - return SAVE.platform.op != SAVE_PSP_OP_NONE; -} - -errorret_t savePSPUpdate(void) { - if(SAVE.platform.op == SAVE_PSP_OP_NONE) errorOk(); - - int32_t status = sceUtilitySavedataGetStatus(); - switch(status) { - case PSP_UTILITY_DIALOG_INIT: - break; - - // NOTE: unlike the netconf dialog, this does not replicate Dusk's own - // GL state (blend/cull/depth + texture/color) before calling Update(). - // A prior fix for exactly that class of bug was documented for the - // network dialog, but no longer exists in the current codebase to - // copy from - if the save dialog's own text/icons don't render - // correctly on real hardware (PPSSPP won't reproduce this - it doesn't - // model pspGL's deferred state application), that state-priming - // pattern is the fix to reach for. See the network dialog's git - // history / the project's PSP dialog memory notes for the exact - // technique (state flags + a forced flush via a degenerate triangle - // draw). - case PSP_UTILITY_DIALOG_VISIBLE: - // sceUtilitySavedataUpdate() is void, unlike sceUtilityNetconfUpdate() - // - nothing to check here, GetStatus() next frame reflects any - // resulting state change. - sceUtilitySavedataUpdate(1); - break; - - case PSP_UTILITY_DIALOG_QUIT: - // The save/load operation itself has already finished (successfully - // or not) - this just starts tearing the dialog down. The actual - // result is read once that teardown settles, below - don't call - // ShutdownStart more than once while waiting for it to. - if(!SAVE.platform.shuttingDown) { - SAVE.platform.shuttingDown = true; - sceUtilitySavedataShutdownStart(); - } - break; - - // Confirmed under PPSSPP: status settles straight from QUIT to NONE, - // without FINISHED ever being separately observed in between - so - // both are treated identically here as "torn down, read the result", - // and it's shuttingDown (not which of these two codes we saw) that - // distinguishes that from a genuine disappearance. - case PSP_UTILITY_DIALOG_FINISHED: - case PSP_UTILITY_DIALOG_NONE: { - savepspop_t op = SAVE.platform.op; - uint8_t slot = SAVE.platform.slot; - savecallback_t cb = SAVE.platform.onComplete; - void *user = SAVE.platform.onCompleteUser; - bool_t reachedQuit = SAVE.platform.shuttingDown; - SAVE.platform.op = SAVE_PSP_OP_NONE; - SAVE.platform.shuttingDown = false; - - if(!reachedQuit) { - cb(errorThrowImpl( - &SAVE.errorState, ERROR_NOT_OK, __FILE__, __func__, __LINE__, - "Save dialog disappeared without a result" - ), user); - break; - } - - int32_t result = SAVE.platform.param.base.result; - if(result != 0) { - SAVE.available = false; - cb(errorThrowImpl( - &SAVE.errorState, ERROR_NOT_OK, __FILE__, __func__, __LINE__, - "Save dialog failed: 0x%08X", result - ), user); - break; - } - SAVE.available = true; - - if(op == SAVE_PSP_OP_LOAD) { - savestream_t stream; - memoryZero(&stream, sizeof(savestream_t)); - stream.platform.buffer = SAVE.platform.dataBuffer; - stream.platform.bufferSize = sizeof(SAVE.platform.dataBuffer); - stream.platform.length = SAVE.platform.param.dataSize; - - errorret_t ret = saveMetaSerializeRead(&stream, &SAVE.meta); - if(errorIsOk(ret)) { - ret = saveSlotSerializeRead(&stream, &SAVE.slots[slot]); - } - cb(ret, user); - } else { - SAVE.meta.exists = true; - SAVE.slots[slot].exists = true; - cb(errorOkImpl(), user); - } - break; - } - - default: - errorThrow("Unknown savedata dialog status: %d", status); - } - - errorOk(); -} diff --git a/src/duskpsp/save/savepsp.h b/src/duskpsp/save/savepsp.h deleted file mode 100644 index 14d55153..00000000 --- a/src/duskpsp/save/savepsp.h +++ /dev/null @@ -1,133 +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 -#include - -#define SAVE_PSP_PATH_MAX 256 -#define SAVE_PSP_ROOT "ms0:/" -#define SAVE_PSP_FILE_NAME "save.bin" -#define SAVE_PSP_FILE_FORMAT "ms0:/PSP/SAVEDATA/%s%02u/" SAVE_PSP_FILE_NAME -#define SAVE_PSP_DATA_BUFFER_SIZE 4096 - -#ifndef SAVE_PSP_GAME_NAME - #define SAVE_PSP_GAME_NAME "DUSK00001" -#endif - -typedef enum { - SAVE_PSP_OP_NONE, - SAVE_PSP_OP_SAVE, - SAVE_PSP_OP_LOAD -} savepspop_t; - -typedef struct { - SceUtilitySavedataParam param; - // Raw buffer sceUtilitySavedata reads/writes the whole save into/from - - // holds BOTH save meta and the (single) save slot back-to-back, - // populated by our own savestream_t serialization (see savestreampsp.h) - // before a save starts, and deserialized from after a load finishes. - // Meta lives in here rather than its own lightweight file specifically - // because a device-wide preference change is meant to feel like a real - // save on this platform (a brief native icon flash), not need its own - // separate storage mechanism. - uint8_t dataBuffer[SAVE_PSP_DATA_BUFFER_SIZE] __attribute__((aligned(64))); - size_t dataLength; - - savepspop_t op; - // True once sceUtilitySavedataShutdownStart() has been requested (dialog - // status PSP_UTILITY_DIALOG_QUIT seen) - distinguishes a normal "torn - // down after finishing" NONE/FINISHED from a genuinely unexpected one - // seen before ever reaching QUIT. Some implementations (confirmed on - // PPSSPP) settle straight to NONE after shutdown without a separately - // observable FINISHED step in between. - bool_t shuttingDown; - uint8_t slot; - savecallback_t onComplete; - void *onCompleteUser; -} savepsp_t; - -/** - * Initializes the save system on PSP. Confirms the memory stick is - * actually reachable (sceIoGetstat on SAVE_PSP_ROOT) rather than assuming - * so, since the savedata dialog otherwise only reports failure once a - * save/load is actually attempted. - * - * @return An error code if no memory stick is reachable. - */ -errorret_t saveInitPSP(void); - -/** - * Disposes of the save system on PSP. - * - * @return An error code if disposal fails. - */ -errorret_t saveDisposePSP(void); - -/** - * Deletes the (one) save data folder from the memory stick. - * - * @param slot The save slot index (always 0 on PSP - see - * SAVE_SLOT_COUNT_MAX's override in this platform's CMakeLists.txt). - * @return An error code if the delete fails. - */ -errorret_t saveDeleteSlotPSP(const uint8_t slot); - -/** - * Starts a save via the native sceUtilitySavedata dialog (mode AUTOSAVE - - * writes silently with just a brief icon flash, no confirm screen, since - * SAVE mode shows one even for a slot with no existing data - but - * PARAM.SFO/title/description are generated identically regardless of - * mode, and the OS handles the save browser entry either way). Serializes - * SAVE.meta then SAVE.slots[slot] into SAVE.platform.dataBuffer first, - * synchronously, then kicks off the dialog and returns - completion is - * reported later via onComplete, driven by savePSPUpdate() each frame. - * If no save data exists yet, sceUtilitySavedataInitStart() creates it. - * - * @param slot The save slot index. - * @param onComplete Callback invoked once the dialog finishes. - * @param user User data passed through to onComplete. - */ -void savePSPBeginSave( - const uint8_t slot, savecallback_t onComplete, void *user -); - -/** - * Starts a load via the native sceUtilitySavedata dialog (mode AUTOLOAD - - * see savePSPBeginSave() for why not the plain LOAD mode) unless a quick - * sceIoGetstat check finds no save data yet - in which case onComplete is - * invoked immediately with SAVE.meta/SAVE.slots[slot].exists left false, - * matching the other platforms' "no file yet" semantics, and no dialog is - * shown at all. - * - * @param slot The save slot index. - * @param onComplete Callback invoked once the dialog (or immediate - * not-found short-circuit) finishes. - * @param user User data passed through to onComplete. - */ -void savePSPBeginLoad( - const uint8_t slot, savecallback_t onComplete, void *user -); - -/** - * Pumps the in-progress save/load dialog one step, if any - must be called - * every engine frame (see saveUpdate()). No-op if no dialog is active. - * - * @return An error code indicating success or failure. - */ -errorret_t savePSPUpdate(void); - -/** - * True while a save/load dialog is in progress (see savePSPBeginSave()/ - * savePSPBeginLoad()). - * - * @return True if a save/load dialog is currently open. - */ -bool_t savePSPIsBusy(void); diff --git a/src/duskpsp/save/savestreampsp.c b/src/duskpsp/save/savestreampsp.c deleted file mode 100644 index 74da9162..00000000 --- a/src/duskpsp/save/savestreampsp.c +++ /dev/null @@ -1,46 +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/savestreampsp.h" -#include "util/memory.h" - -errorret_t saveStreamReadBytesPSP( - savestreampsp_t *p, void *buf, const size_t len -) { - if(p->position + len > p->length) { - errorThrow("Save stream read exceeds buffer length"); - } - memoryCopy(buf, p->buffer + p->position, len); - p->position += len; - errorOk(); -} - -errorret_t saveStreamWriteBytesPSP( - savestreampsp_t *p, const void *buf, const size_t len -) { - if(p->position + len > p->bufferSize) { - errorThrow("Save stream write exceeds buffer size"); - } - memoryCopy(p->buffer + p->position, buf, len); - p->position += len; - if(p->position > p->length) p->length = p->position; - errorOk(); -} - -errorret_t saveStreamSeekPSP(savestreampsp_t *p, const size_t pos) { - if(pos > p->bufferSize) { - errorThrow("Save stream seek out of range"); - } - p->position = pos; - errorOk(); -} - -errorret_t saveStreamTellPSP(savestreampsp_t *p, size_t *out) { - *out = p->position; - errorOk(); -} diff --git a/src/duskpsp/save/savestreampsp.h b/src/duskpsp/save/savestreampsp.h deleted file mode 100644 index 4fb5978d..00000000 --- a/src/duskpsp/save/savestreampsp.h +++ /dev/null @@ -1,64 +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 - -// Backed by SAVE.platform.dataBuffer (see savepsp.h) rather than owning its -// own memory - the buffer has to outlive a single saveFileWrite()/Load() -// call, since the actual save/load dialog it's handed to only completes -// several frames later. -typedef struct { - uint8_t *buffer; - size_t bufferSize; - size_t position; - size_t length; -} savestreampsp_t; - -/** - * Copies len bytes from the 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 populated data length. - */ -errorret_t saveStreamReadBytesPSP( - savestreampsp_t *p, void *buf, const size_t len -); - -/** - * Copies len bytes from buf into the buffer at the current position, - * growing p->length if this write extends past it. - * - * @param p Active stream. - * @param buf Source buffer. - * @param len Number of bytes to write. - * @return An error if the write would exceed bufferSize. - */ -errorret_t saveStreamWriteBytesPSP( - savestreampsp_t *p, const void *buf, const size_t len -); - -/** - * Sets the current read/write position within the buffer. - * - * @param p Active stream. - * @param pos Target byte offset from the start of the buffer. - * @return An error if pos is out of range. - */ -errorret_t saveStreamSeekPSP(savestreampsp_t *p, const size_t pos); - -/** - * Gets the current read/write position within the buffer. - * - * @param p Active stream. - * @param out Receives the current position. - * @return An error - always succeeds, matches saveStreamTellImpl's shape. - */ -errorret_t saveStreamTellPSP(savestreampsp_t *p, size_t *out); diff --git a/src/duskpsp/system/systemplatform.h b/src/duskpsp/system/systemplatform.h index 65a350c7..3afc4779 100644 --- a/src/duskpsp/system/systemplatform.h +++ b/src/duskpsp/system/systemplatform.h @@ -9,4 +9,5 @@ #include "system/systempsp.h" #define systemInitPlatform systemInitPSP -#define systemGetActiveDialogTypePlatform systemGetActiveDialogTypePSP \ No newline at end of file +#define systemGetActiveDialogTypePlatform systemGetActiveDialogTypePSP +#define systemGetLocalePlatform systemGetLocalePSP \ No newline at end of file diff --git a/src/duskpsp/system/systempsp.c b/src/duskpsp/system/systempsp.c index b4825ef1..4d7660ac 100644 --- a/src/duskpsp/system/systempsp.c +++ b/src/duskpsp/system/systempsp.c @@ -61,4 +61,12 @@ int_t systemPSPGetCrossButtonSetting() { return ( ret == 1 ? PSP_UTILITY_ACCEPT_CROSS : PSP_UTILITY_ACCEPT_CIRCLE ); +} + +const localeinfo_t * systemGetLocalePSP(void) { + switch(systemPSPGetLanguage()) { + case PSP_SYSTEMPARAM_LANGUAGE_JAPANESE: return &LOCALE_INFO_JP_JP; + case PSP_SYSTEMPARAM_LANGUAGE_SPANISH: return &LOCALE_INFO_ES_MX; + default: return &LOCALE_DEFAULT; + } } \ No newline at end of file diff --git a/src/duskpsp/system/systempsp.h b/src/duskpsp/system/systempsp.h index b2943148..6dc3b58c 100644 --- a/src/duskpsp/system/systempsp.h +++ b/src/duskpsp/system/systempsp.h @@ -37,4 +37,12 @@ int_t systemPSPGetLanguage(); * * @return PSP_UTILITY_ACCEPT_CROSS or PSP_UTILITY_ACCEPT_CIRCLE. */ -int_t systemPSPGetCrossButtonSetting(); \ No newline at end of file +int_t systemPSPGetCrossButtonSetting(); + +/** + * 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 * systemGetLocalePSP(void); \ No newline at end of file diff --git a/src/duskvita/input/inputvita.c b/src/duskvita/input/inputvita.c index b3b8f427..7194ceaf 100644 --- a/src/duskvita/input/inputvita.c +++ b/src/duskvita/input/inputvita.c @@ -6,7 +6,6 @@ */ #include "input/input.h" -#include "save/save.h" inputbuttondata_t INPUT_BUTTON_DATA[] = { { .name = "triangle", { @@ -84,5 +83,5 @@ inputbuttondata_t INPUT_BUTTON_DATA[] = { }; float_t inputGetDeadzoneSDL2(const inputbutton_t button) { - return saveGetMeta()->deadzone; + return 0.2f; } diff --git a/src/duskvita/save/CMakeLists.txt b/src/duskvita/save/CMakeLists.txt deleted file mode 100644 index ffa8c58f..00000000 --- a/src/duskvita/save/CMakeLists.txt +++ /dev/null @@ -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 - savevita.c - savestreamvita.c -) diff --git a/src/duskvita/save/saveplatform.h b/src/duskvita/save/saveplatform.h deleted file mode 100644 index 47dfeb6d..00000000 --- a/src/duskvita/save/saveplatform.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2026 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "save/savevita.h" -#include "save/savestreamvita.h" - -typedef savevita_t saveplatform_t; -typedef savestreamvita_t saveplatformstream_t; - -#define saveInitPlatform saveInitVita -#define saveDisposePlatform saveDisposeVita -#define saveDeletePlatform saveDeleteVita - -#define saveStreamOpenReadPlatform(stream, slot) \ - saveStreamOpenReadVita(&(stream)->platform, &(stream)->found, slot) -#define saveStreamOpenWritePlatform(stream, slot) \ - saveStreamOpenWriteVita(&(stream)->platform, slot) -#define saveStreamClosePlatform(stream) \ - saveStreamCloseVita(&(stream)->platform) -#define saveStreamReadBytesPlatform(stream, buf, len) \ - saveStreamReadBytesVita(&(stream)->platform, buf, len) -#define saveStreamWriteBytesPlatform(stream, buf, len) \ - saveStreamWriteBytesVita(&(stream)->platform, buf, len) -#define saveStreamSeekPlatform(stream, pos) \ - saveStreamSeekVita(&(stream)->platform, pos) diff --git a/src/duskvita/save/savestreamvita.c b/src/duskvita/save/savestreamvita.c deleted file mode 100644 index f3db7aa6..00000000 --- a/src/duskvita/save/savestreamvita.c +++ /dev/null @@ -1,77 +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/savestreamvita.h" - -errorret_t saveStreamOpenReadVita( - savestreamvita_t *p, bool_t *found, const uint8_t slot -) { - char_t path[SAVE_VITA_PATH_MAX]; - snprintf(path, SAVE_VITA_PATH_MAX, SAVE_VITA_FILE_FORMAT, - SAVE_VITA_TITLE_ID, (uint32_t)slot - ); - - p->fd = sceIoOpen(path, SCE_O_RDONLY, 0); - *found = (p->fd >= 0); - errorOk(); -} - -errorret_t saveStreamOpenWriteVita(savestreamvita_t *p, const uint8_t slot) { - char_t dir[SAVE_VITA_PATH_MAX]; - snprintf(dir, SAVE_VITA_PATH_MAX, SAVE_VITA_DIR_FORMAT, - SAVE_VITA_TITLE_ID, (uint32_t)slot - ); - sceIoMkdir(dir, 0777); - - char_t path[SAVE_VITA_PATH_MAX]; - snprintf(path, SAVE_VITA_PATH_MAX, SAVE_VITA_FILE_FORMAT, - SAVE_VITA_TITLE_ID, (uint32_t)slot - ); - - p->fd = sceIoOpen(path, SCE_O_WRONLY | SCE_O_CREAT | SCE_O_TRUNC, 0777); - if(p->fd < 0) { - errorThrow( - "Failed to open Vita save file for writing: slot %u", (uint32_t)slot - ); - } - errorOk(); -} - -void saveStreamCloseVita(savestreamvita_t *p) { - if(p->fd >= 0) { - sceIoClose(p->fd); - p->fd = -1; - } -} - -errorret_t saveStreamReadBytesVita( - savestreamvita_t *p, void *buf, const size_t len -) { - int32_t read = sceIoRead(p->fd, buf, (SceSize)len); - if(read != (int32_t)len) { - errorThrow("Unexpected end of Vita save file"); - } - errorOk(); -} - -errorret_t saveStreamWriteBytesVita( - savestreamvita_t *p, const void *buf, const size_t len -) { - int32_t written = sceIoWrite(p->fd, buf, (SceSize)len); - if(written != (int32_t)len) { - errorThrow("Failed to write Vita save data"); - } - errorOk(); -} - -errorret_t saveStreamSeekVita(savestreamvita_t *p, const size_t pos) { - if(sceIoLseek(p->fd, (SceOff)pos, SCE_SEEK_SET) < 0) { - errorThrow("Failed to seek in Vita save file"); - } - errorOk(); -} diff --git a/src/duskvita/save/savestreamvita.h b/src/duskvita/save/savestreamvita.h deleted file mode 100644 index dcd9ec90..00000000 --- a/src/duskvita/save/savestreamvita.h +++ /dev/null @@ -1,78 +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 -#include -#include - -typedef struct { - SceUID fd; -} savestreamvita_t; - -/** - * Opens a Vita save data file for reading. - * - * @param p Stream to initialize. - * @param found Set to true if the file exists, false if it does not. - * @param slot Save slot index. - * @return An error if the open fails for a reason other than missing file. - */ -errorret_t saveStreamOpenReadVita( - savestreamvita_t *p, bool_t *found, const uint8_t slot -); - -/** - * Opens a Vita save data file for writing, creating or truncating it. - * Creates the save data directory if it does not already exist. - * - * @param p Stream to initialize. - * @param slot Save slot index. - * @return An error if the file cannot be opened for writing. - */ -errorret_t saveStreamOpenWriteVita(savestreamvita_t *p, const uint8_t slot); - -/** - * Closes the file descriptor held by the stream. - * - * @param p Stream to close. - */ -void saveStreamCloseVita(savestreamvita_t *p); - -/** - * Reads len bytes from the stream into buf. - * - * @param p Active stream. - * @param buf Destination buffer. - * @param len Number of bytes to read. - * @return An error if fewer than len bytes are available. - */ -errorret_t saveStreamReadBytesVita( - savestreamvita_t *p, void *buf, const size_t len -); - -/** - * Writes len bytes from buf into the stream. - * - * @param p Active stream. - * @param buf Source buffer. - * @param len Number of bytes to write. - * @return An error if the write fails. - */ -errorret_t saveStreamWriteBytesVita( - savestreamvita_t *p, const void *buf, const size_t len -); - -/** - * Seeks to an absolute byte position within the stream. - * - * @param p Active stream. - * @param pos Target byte offset from the start of the file. - * @return An error if the seek fails. - */ -errorret_t saveStreamSeekVita(savestreamvita_t *p, const size_t pos);