From 674f86b18ae88040cd3ac6f16961199ad78656ae Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Sun, 16 Aug 2026 20:14:16 -0500 Subject: [PATCH] Add JSON (de)serialization for save slots/settings and device write/read hooks Introduces saveslot/savesettings structs with a shared savejson.h macro toolkit (has/require/write/read per common type) for populating and parsing yyjson objects, wires saveManager save/load calls through new saveDevice slot/settings write/read entry points (still stubbed at the device level), and drops the old binary-format scaffolding in favor of JSON only. --- src/dusk/save/CMakeLists.txt | 2 + src/dusk/save/savedevice.c | 42 ++++ src/dusk/save/savedevice.h | 56 +++++- src/dusk/save/savefile.h | 11 - src/dusk/save/savejson.h | 377 +++++++++++++++++++++++++++++++++++ src/dusk/save/savemanager.c | 55 +++++ src/dusk/save/savemanager.h | 42 +++- src/dusk/save/savesettings.c | 40 ++++ src/dusk/save/savesettings.h | 49 +++++ src/dusk/save/saveslot.c | 50 +++++ src/dusk/save/saveslot.h | 64 ++++++ 11 files changed, 774 insertions(+), 14 deletions(-) delete mode 100644 src/dusk/save/savefile.h create mode 100644 src/dusk/save/savejson.h create mode 100644 src/dusk/save/savesettings.c create mode 100644 src/dusk/save/savesettings.h create mode 100644 src/dusk/save/saveslot.c create mode 100644 src/dusk/save/saveslot.h diff --git a/src/dusk/save/CMakeLists.txt b/src/dusk/save/CMakeLists.txt index aee88303..0babc5c0 100644 --- a/src/dusk/save/CMakeLists.txt +++ b/src/dusk/save/CMakeLists.txt @@ -8,4 +8,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC savemanager.c savedevice.c + saveslot.c + savesettings.c ) diff --git a/src/dusk/save/savedevice.c b/src/dusk/save/savedevice.c index ed7e9f57..0cd3a6ad 100644 --- a/src/dusk/save/savedevice.c +++ b/src/dusk/save/savedevice.c @@ -79,6 +79,48 @@ void saveDeviceCheckAvailability( saveDeviceCheckAvailabilityPlatform(device); } +errorret_t saveDeviceSlotWrite( + savedevice_t *device, + saveslot_t *slot, + uint8_t slotIndex +) { + assertNotNull(device, "device cannot be null"); + assertNotNull(slot, "slot cannot be null"); + + errorOk(); +} + +errorret_t saveDeviceSlotRead( + savedevice_t *device, + saveslot_t *slot, + uint8_t slotIndex +) { + assertNotNull(device, "device cannot be null"); + assertNotNull(slot, "slot cannot be null"); + + errorOk(); +} + +errorret_t saveDeviceSettingsWrite( + savedevice_t *device, + savesettings_t *settings +) { + assertNotNull(device, "device cannot be null"); + assertNotNull(settings, "settings cannot be null"); + + errorOk(); +} + +errorret_t saveDeviceSettingsRead( + savedevice_t *device, + savesettings_t *settings +) { + assertNotNull(device, "device cannot be null"); + assertNotNull(settings, "settings cannot be null"); + + errorOk(); +} + errorret_t saveDeviceDispose(savedevice_t *device) { assertNotNull(device, "device cannot be null"); errorChain(saveDevicePlatformDispose(device)); diff --git a/src/dusk/save/savedevice.h b/src/dusk/save/savedevice.h index c0f1f4d9..2011369c 100644 --- a/src/dusk/save/savedevice.h +++ b/src/dusk/save/savedevice.h @@ -15,6 +15,8 @@ #endif typedef struct savedevice_s savedevice_t; +typedef struct savesettings_s savesettings_t; +typedef struct saveslot_s saveslot_t; typedef void (*savedevicestatecallback_t)(savedevice_t *device, void *user); @@ -72,9 +74,61 @@ void saveDeviceCheckAvailability( void *user ); +/** + * Writes the given save slot to the device at the given slot index. + * + * @param device The save device to write to. + * @param slot The save slot to write. + * @param slotIndex The slot index to write to. + * @return Error state if any. + */ +errorret_t saveDeviceSlotWrite( + savedevice_t *device, + saveslot_t *slot, + uint8_t slotIndex +); + +/** + * Reads a save slot from the device at the given slot index. + * + * @param device The save device to read from. + * @param slot The save slot to read into. + * @param slotIndex The slot index to read from. + * @return Error state if any. + */ +errorret_t saveDeviceSlotRead( + savedevice_t *device, + saveslot_t *slot, + uint8_t slotIndex +); + +/** + * Writes the given save settings to the device. + * + * @param device The save device to write to. + * @param settings The save settings to write. + * @return Error state if any. + */ +errorret_t saveDeviceSettingsWrite( + savedevice_t *device, + savesettings_t *settings +); + +/** + * Reads the save settings from the device. + * + * @param device The save device to read from. + * @param settings The save settings to read into. + * @return Error state if any. + */ +errorret_t saveDeviceSettingsRead( + savedevice_t *device, + savesettings_t *settings +); + /** * Disposes of the save device. - * + * * @param device The save device to dispose. * @return Error state if any. */ diff --git a/src/dusk/save/savefile.h b/src/dusk/save/savefile.h deleted file mode 100644 index a7044bb3..00000000 --- a/src/dusk/save/savefile.h +++ /dev/null @@ -1,11 +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" - -typedef uint8_t saveslot_t; \ No newline at end of file diff --git a/src/dusk/save/savejson.h b/src/dusk/save/savejson.h new file mode 100644 index 00000000..2cb558fc --- /dev/null +++ b/src/dusk/save/savejson.h @@ -0,0 +1,377 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "yyjson.h" +#include "error/error.h" +#include "assert/assert.h" +#include "util/string.h" +#include "time/timeepoch.h" + +#define SAVE_JSON_STRING_BUFFER_SIZE 256 + +/** + * Creates a new mutable JSON document with an empty root object, ready for + * the writeX macros below to populate. Declares `doc` and `object` in the + * calling scope. + */ +#define writeInit() \ + yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); \ + assertNotNull(doc, "Failed to create JSON document"); \ + yyjson_mut_val *object = yyjson_mut_obj(doc); \ + yyjson_mut_doc_set_root(doc, object) + +/** + * Parses the given JSON text and exposes its root object for the readX + * macros below to consume. Declares `jsonDoc` (keep it around to dispose + * with yyjson_doc_free once reading is done) and `object` in the calling + * scope. Errors if the text fails to parse or has no object root. + * + * @param jsonText The JSON text to parse. + * @param jsonLength The length of the JSON text, in bytes. + */ +#define readInit(jsonText, jsonLength) \ + yyjson_doc *jsonDoc = yyjson_read((jsonText), (jsonLength), 0); \ + if(jsonDoc == NULL) errorThrow("Failed to parse save JSON"); \ + yyjson_val *object = yyjson_doc_get_root(jsonDoc); \ + if(object == NULL) errorThrow("Save JSON missing root object") + +/** + * Checks if the given key exists on the current JSON object. + * + * @param key The key to check for. + */ +#define hasInt32(key) (yyjson_obj_get(object, key) != NULL) + +/** + * Errors if the given key does not exist on the current JSON object. + * + * @param key The key that must exist. + */ +#define requireInt32(key) \ + if(!hasInt32(key)) errorThrow("Save JSON missing '%s' key", key) + +/** + * Writes an int32_t to the current JSON object, omitting the key entirely + * if it matches the given default. + * + * @param key The key to write to. + * @param value The value to write. + * @param def The default value; if value equals this, the key is omitted. + */ +#define writeInt32(key, value, def) \ + if((value) != (def)) yyjson_mut_obj_add_int(doc, object, key, (int64_t)(value)) + +/** + * Reads an int32_t from the current JSON object, falling back to the given + * default if the key is missing. + * + * @param key The key to read from. + * @param dest The destination variable to assign to. + * @param def The default value to use if the key is missing. + */ +#define readInt32(key, dest, def) \ + (dest) = hasInt32(key) ? \ + (int32_t)yyjson_get_int(yyjson_obj_get(object, key)) : (int32_t)(def) + +/** + * Checks if the given key exists on the current JSON object. + * + * @param key The key to check for. + */ +#define hasUInt32(key) (yyjson_obj_get(object, key) != NULL) + +/** + * Errors if the given key does not exist on the current JSON object. + * + * @param key The key that must exist. + */ +#define requireUInt32(key) \ + if(!hasUInt32(key)) errorThrow("Save JSON missing '%s' key", key) + +/** + * Writes a uint32_t to the current JSON object, omitting the key entirely + * if it matches the given default. + * + * @param key The key to write to. + * @param value The value to write. + * @param def The default value; if value equals this, the key is omitted. + */ +#define writeUInt32(key, value, def) \ + if((value) != (def)) yyjson_mut_obj_add_uint(doc, object, key, (uint64_t)(value)) + +/** + * Reads a uint32_t from the current JSON object, falling back to the given + * default if the key is missing. + * + * @param key The key to read from. + * @param dest The destination variable to assign to. + * @param def The default value to use if the key is missing. + */ +#define readUInt32(key, dest, def) \ + (dest) = hasUInt32(key) ? \ + (uint32_t)yyjson_get_uint(yyjson_obj_get(object, key)) : (uint32_t)(def) + +/** + * Checks if the given key exists on the current JSON object. + * + * @param key The key to check for. + */ +#define hasUInt8(key) (yyjson_obj_get(object, key) != NULL) + +/** + * Errors if the given key does not exist on the current JSON object. + * + * @param key The key that must exist. + */ +#define requireUInt8(key) \ + if(!hasUInt8(key)) errorThrow("Save JSON missing '%s' key", key) + +/** + * Writes a uint8_t to the current JSON object, omitting the key entirely + * if it matches the given default. + * + * @param key The key to write to. + * @param value The value to write. + * @param def The default value; if value equals this, the key is omitted. + */ +#define writeUInt8(key, value, def) \ + if((value) != (def)) yyjson_mut_obj_add_uint(doc, object, key, (uint64_t)(value)) + +/** + * Reads a uint8_t from the current JSON object, falling back to the given + * default if the key is missing. + * + * @param key The key to read from. + * @param dest The destination variable to assign to. + * @param def The default value to use if the key is missing. + */ +#define readUInt8(key, dest, def) \ + (dest) = hasUInt8(key) ? \ + (uint8_t)yyjson_get_uint(yyjson_obj_get(object, key)) : (uint8_t)(def) + +/** + * Checks if the given key exists on the current JSON object. + * + * @param key The key to check for. + */ +#define hasInt64(key) (yyjson_obj_get(object, key) != NULL) + +/** + * Errors if the given key does not exist on the current JSON object. + * + * @param key The key that must exist. + */ +#define requireInt64(key) \ + if(!hasInt64(key)) errorThrow("Save JSON missing '%s' key", key) + +/** + * Writes an int64_t to the current JSON object, omitting the key entirely + * if it matches the given default. + * + * @param key The key to write to. + * @param value The value to write. + * @param def The default value; if value equals this, the key is omitted. + */ +#define writeInt64(key, value, def) \ + if((value) != (def)) yyjson_mut_obj_add_sint(doc, object, key, (int64_t)(value)) + +/** + * Reads an int64_t from the current JSON object, falling back to the given + * default if the key is missing. + * + * @param key The key to read from. + * @param dest The destination variable to assign to. + * @param def The default value to use if the key is missing. + */ +#define readInt64(key, dest, def) \ + (dest) = hasInt64(key) ? \ + yyjson_get_sint(yyjson_obj_get(object, key)) : (int64_t)(def) + +/** + * Checks if the given key exists on the current JSON object. + * + * @param key The key to check for. + */ +#define hasUInt64(key) (yyjson_obj_get(object, key) != NULL) + +/** + * Errors if the given key does not exist on the current JSON object. + * + * @param key The key that must exist. + */ +#define requireUInt64(key) \ + if(!hasUInt64(key)) errorThrow("Save JSON missing '%s' key", key) + +/** + * Writes a uint64_t to the current JSON object, omitting the key entirely + * if it matches the given default. + * + * @param key The key to write to. + * @param value The value to write. + * @param def The default value; if value equals this, the key is omitted. + */ +#define writeUInt64(key, value, def) \ + if((value) != (def)) yyjson_mut_obj_add_uint(doc, object, key, (uint64_t)(value)) + +/** + * Reads a uint64_t from the current JSON object, falling back to the given + * default if the key is missing. + * + * @param key The key to read from. + * @param dest The destination variable to assign to. + * @param def The default value to use if the key is missing. + */ +#define readUInt64(key, dest, def) \ + (dest) = hasUInt64(key) ? \ + yyjson_get_uint(yyjson_obj_get(object, key)) : (uint64_t)(def) + +/** + * Checks if the given key exists on the current JSON object. + * + * @param key The key to check for. + */ +#define hasFloat(key) (yyjson_obj_get(object, key) != NULL) + +/** + * Errors if the given key does not exist on the current JSON object. + * + * @param key The key that must exist. + */ +#define requireFloat(key) \ + if(!hasFloat(key)) errorThrow("Save JSON missing '%s' key", key) + +/** + * Writes a float_t to the current JSON object, omitting the key entirely + * if it matches the given default. + * + * @param key The key to write to. + * @param value The value to write. + * @param def The default value; if value equals this, the key is omitted. + */ +#define writeFloat(key, value, def) \ + if((value) != (def)) yyjson_mut_obj_add_real(doc, object, key, (double)(value)) + +/** + * Reads a float_t from the current JSON object, falling back to the given + * default if the key is missing. + * + * @param key The key to read from. + * @param dest The destination variable to assign to. + * @param def The default value to use if the key is missing. + */ +#define readFloat(key, dest, def) \ + (dest) = hasFloat(key) ? \ + (float_t)yyjson_get_real(yyjson_obj_get(object, key)) : (float_t)(def) + +/** + * Checks if the given key exists on the current JSON object. + * + * @param key The key to check for. + */ +#define hasString(key) (yyjson_obj_get(object, key) != NULL) + +/** + * Errors if the given key does not exist on the current JSON object. + * + * @param key The key that must exist. + */ +#define requireString(key) \ + if(!hasString(key)) errorThrow("Save JSON missing '%s' key", key) + +/** + * Writes a string to the current JSON object, omitting the key entirely if + * it matches the given default. The string is copied, so it does not need + * to outlive the JSON document. + * + * @param key The key to write to. + * @param value The value to write. + * @param def The default value; if value equals this, the key is omitted. + */ +#define writeString(key, value, def) \ + if(!stringEquals((value), (def))) yyjson_mut_obj_add_strcpy(doc, object, key, value) + +/** + * Reads a string from the current JSON object into dest, falling back to + * the given default if the key is missing. Errors if the JSON string is + * longer than maxLength. Uses the caller's `saveJsonStringBuffer` local as + * scratch space. + * + * @param key The key to read from. + * @param dest The destination buffer to copy into. + * @param def The default value to use if the key is missing. + * @param maxLength The maximum length of dest, excluding the null terminator. + */ +#define readString(key, dest, def, maxLength) { \ + yyjson_val *saveJsonStrVal = yyjson_obj_get(object, key); \ + if(saveJsonStrVal != NULL) { \ + if(yyjson_get_len(saveJsonStrVal) > (size_t)(maxLength)) { \ + errorThrow( \ + "Save JSON string '%s' exceeds max length of %d", \ + key, (int)(maxLength) \ + ); \ + } \ + stringCopy(saveJsonStringBuffer, yyjson_get_str(saveJsonStrVal), (maxLength)); \ + } else { \ + stringCopy(saveJsonStringBuffer, (def), (maxLength)); \ + } \ + stringCopy((dest), saveJsonStringBuffer, (maxLength)); \ +} + +/** + * Checks if the given key exists on the current JSON object. + * + * @param key The key to check for. + */ +#define hasTime(key) (yyjson_obj_get(object, key) != NULL) + +/** + * Errors if the given key does not exist on the current JSON object. + * + * @param key The key that must exist. + */ +#define requireTime(key) \ + if(!hasTime(key)) errorThrow("Save JSON missing '%s' key", key) + +/** + * Writes a dusktimeepoch_t to the current JSON object as a nested object of + * its time/timeZone/offsetTime fields. + * + * @param key The key to write to. + * @param value The dusktimeepoch_t value to write. + */ +#define writeTime(key, value) { \ + yyjson_mut_val *saveJsonTimeObj = yyjson_mut_obj_add_obj(doc, object, key); \ + yyjson_mut_obj_add_real(doc, saveJsonTimeObj, "time", (value).time); \ + yyjson_mut_obj_add_real(doc, saveJsonTimeObj, "timeZone", (value).timeZone); \ + yyjson_mut_obj_add_real(doc, saveJsonTimeObj, "offsetTime", (value).offsetTime); \ +} + +/** + * Reads a dusktimeepoch_t from the current JSON object's nested time/ + * timeZone/offsetTime fields. Errors if the key or any sub-field is missing. + * + * @param key The key to read from. + * @param dest The destination dusktimeepoch_t to assign to. + */ +#define readTime(key, dest) { \ + yyjson_val *saveJsonTimeObj = yyjson_obj_get(object, key); \ + if(saveJsonTimeObj == NULL) errorThrow("Save JSON missing '%s' key", key); \ + yyjson_val *saveJsonTimeTime = yyjson_obj_get(saveJsonTimeObj, "time"); \ + yyjson_val *saveJsonTimeZone = yyjson_obj_get(saveJsonTimeObj, "timeZone"); \ + yyjson_val *saveJsonTimeOffset = yyjson_obj_get(saveJsonTimeObj, "offsetTime"); \ + if(saveJsonTimeTime == NULL) errorThrow("Save JSON missing '%s.time' key", key); \ + if(saveJsonTimeZone == NULL) { \ + errorThrow("Save JSON missing '%s.timeZone' key", key); \ + } \ + if(saveJsonTimeOffset == NULL) { \ + errorThrow("Save JSON missing '%s.offsetTime' key", key); \ + } \ + (dest).time = yyjson_get_real(saveJsonTimeTime); \ + (dest).timeZone = yyjson_get_real(saveJsonTimeZone); \ + (dest).offsetTime = yyjson_get_real(saveJsonTimeOffset); \ +} diff --git a/src/dusk/save/savemanager.c b/src/dusk/save/savemanager.c index 63504a00..bcd1f1e9 100644 --- a/src/dusk/save/savemanager.c +++ b/src/dusk/save/savemanager.c @@ -15,6 +15,13 @@ errorret_t saveManagerInit() { memoryZero(&SAVE_MANAGER, sizeof(savemanager_t)); SAVE_MANAGER.deviceCurrent = 0xFF;// No current device. + SAVE_MANAGER.slotCurrent = 0xFF;// No current slot. + + // Initialize the default slot + saveSlotInit(&SAVE_MANAGER.slot); + + // Initialize the default settings. + saveSettingsInit(&SAVE_MANAGER.settings); // Start by initializing each of the save devices. savedevice_t *device = &SAVE_MANAGER.devices[0]; @@ -141,6 +148,54 @@ void saveManagerOnDeviceAvailabilityChecked(savedevice_t *device, void *user) { SAVE_MANAGER.deviceCurrent = (uint8_t)(device - &SAVE_MANAGER.devices[0]); } +errorret_t saveManagerSaveSettings() { + assertTrue(SAVE_MANAGER.deviceCurrent != 0xFF, "No current device"); + + errorChain(saveDeviceSettingsWrite( + &SAVE_MANAGER.devices[SAVE_MANAGER.deviceCurrent], + &SAVE_MANAGER.settings + )); + + errorOk(); +} + +errorret_t saveManagerLoadSettings() { + assertTrue(SAVE_MANAGER.deviceCurrent != 0xFF, "No current device"); + + errorChain(saveDeviceSettingsRead( + &SAVE_MANAGER.devices[SAVE_MANAGER.deviceCurrent], + &SAVE_MANAGER.settings + )); + + errorOk(); +} + +errorret_t saveManagerSaveSlot() { + assertTrue(SAVE_MANAGER.deviceCurrent != 0xFF, "No current device"); + assertTrue(SAVE_MANAGER.slotCurrent != 0xFF, "No current slot"); + + errorChain(saveDeviceSlotWrite( + &SAVE_MANAGER.devices[SAVE_MANAGER.deviceCurrent], + &SAVE_MANAGER.slot, + SAVE_MANAGER.slotCurrent + )); + + errorOk(); +} + +errorret_t saveManagerLoadSlot() { + assertTrue(SAVE_MANAGER.deviceCurrent != 0xFF, "No current device"); + assertTrue(SAVE_MANAGER.slotCurrent != 0xFF, "No current slot"); + + errorChain(saveDeviceSlotRead( + &SAVE_MANAGER.devices[SAVE_MANAGER.deviceCurrent], + &SAVE_MANAGER.slot, + SAVE_MANAGER.slotCurrent + )); + + errorOk(); +} + errorret_t saveManagerDispose() { // Dispose each device. savedevice_t *device = &SAVE_MANAGER.devices[0]; diff --git a/src/dusk/save/savemanager.h b/src/dusk/save/savemanager.h index db22da8c..9befdb5a 100644 --- a/src/dusk/save/savemanager.h +++ b/src/dusk/save/savemanager.h @@ -7,11 +7,21 @@ #pragma once #include "savedevice.h" +#include "saveslot.h" +#include "savesettings.h" typedef struct { + // File state + savesettings_t settings; + saveslot_t slot; + + uint8_t slotCurrent; + bool_t settingsDirty; + bool_t slotDirty; + + // Device state savedevice_t devices[SAVE_DEVICE_COUNT]; uint8_t deviceCurrent; - bool_t findingAvailableDevice; bool_t noAvailableDeviceFound; savedevicestatecallback_t findAvailableCallback; @@ -56,9 +66,37 @@ void saveManagerFindAvailableDevice( */ void saveManagerOnDeviceAvailabilityChecked(savedevice_t *device, void *user); +/** + * Saves the current settings to the current device. + * + * @return Error state if any. + */ +errorret_t saveManagerSaveSettings(); + +/** + * Loads the current settings from the current device. + * + * @return Error state if any. + */ +errorret_t saveManagerLoadSettings(); + +/** + * Saves the current slot to the current device, at the current slot index. + * + * @return Error state if any. + */ +errorret_t saveManagerSaveSlot(); + +/** + * Loads the current slot from the current device, at the current slot index. + * + * @return Error state if any. + */ +errorret_t saveManagerLoadSlot(); + /** * Disposes of the save manager. - * + * * @return Error state if any. */ errorret_t saveManagerDispose(); \ No newline at end of file diff --git a/src/dusk/save/savesettings.c b/src/dusk/save/savesettings.c new file mode 100644 index 00000000..90f805e9 --- /dev/null +++ b/src/dusk/save/savesettings.c @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "savesettings.h" +#include "assert/assert.h" +#include "util/memory.h" +#include "savejson.h" + +void saveSettingsInit(savesettings_t *settings) { + assertNotNull(settings, "Settings cannot be null"); + + memorySet(settings, 0, sizeof(savesettings_t)); +} + +errorret_t saveSettingsWriteJSON( + savesettings_t *settings, + yyjson_mut_doc *doc, + yyjson_mut_val *object +) { + assertNotNull(settings, "Settings cannot be null"); + assertNotNull(doc, "Doc cannot be null"); + assertNotNull(object, "Object cannot be null"); + + writeInt32("someSetting", settings->someSetting, 0); + + errorOk(); +} + +errorret_t saveSettingsReadJSON(savesettings_t *settings, yyjson_val *object) { + assertNotNull(settings, "Settings cannot be null"); + assertNotNull(object, "Object cannot be null"); + + readInt32("someSetting", settings->someSetting, 0); + + errorOk(); +} \ No newline at end of file diff --git a/src/dusk/save/savesettings.h b/src/dusk/save/savesettings.h new file mode 100644 index 00000000..1a1b711d --- /dev/null +++ b/src/dusk/save/savesettings.h @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "time/timeepoch.h" +#include "savedevice.h" +#include "yyjson.h" + +#pragma pack(push, 1) +typedef struct savesettings_s { + int32_t someSetting; +} savesettings_t; +#pragma pack(pop) + +/** + * Inits the save settings with the default state, this is functionally "new + * game" but will not set the player name, as that is what we use to determine + * if a save slot is "in use" or not. + * + * @param settings The save settings to init. + */ +void saveSettingsInit(savesettings_t *settings); + +/** + * Writes the given save settings out to the given JSON object. + * + * @param settings The save settings to write. + * @param doc The mutable JSON document that owns the object. + * @param object The mutable JSON object to write into. + * @return Error state if any. + */ +errorret_t saveSettingsWriteJSON( + savesettings_t *settings, + yyjson_mut_doc *doc, + yyjson_mut_val *object +); + +/** + * Reads the given save settings in from the given JSON object. + * + * @param settings The save settings to read into. + * @param object The JSON object to read from. + * @return Error state if any. + */ +errorret_t saveSettingsReadJSON(savesettings_t *settings, yyjson_val *object); \ No newline at end of file diff --git a/src/dusk/save/saveslot.c b/src/dusk/save/saveslot.c new file mode 100644 index 00000000..cd6b6a69 --- /dev/null +++ b/src/dusk/save/saveslot.c @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "saveslot.h" +#include "assert/assert.h" +#include "util/memory.h" +#include "savejson.h" + +void saveSlotInit(saveslot_t *slot) { + assertNotNull(slot, "Slot cannot be null"); + + memorySet(slot, 0, sizeof(saveslot_t)); + slot->version = 1; +} + +bool_t saveSlotInUse(saveslot_t *slot) { + assertNotNull(slot, "Slot cannot be null"); + return slot->name[0] != '\0'; +} + +errorret_t saveSlotWriteJSON( + saveslot_t *slot, + yyjson_mut_doc *doc, + yyjson_mut_val *object +) { + assertNotNull(slot, "Slot cannot be null"); + assertNotNull(doc, "Doc cannot be null"); + assertNotNull(object, "Object cannot be null"); + + writeString("name", slot->name, ""); + writeTime("time", slot->time); + + errorOk(); +} + +errorret_t saveSlotReadJSON(saveslot_t *slot, yyjson_val *object) { + assertNotNull(slot, "Slot cannot be null"); + assertNotNull(object, "Object cannot be null"); + + char_t saveJsonStringBuffer[SAVE_JSON_STRING_BUFFER_SIZE]; + + readString("name", slot->name, "", SAVE_SLOT_NAME_LENGTH); + readTime("time", slot->time); + + errorOk(); +} \ No newline at end of file diff --git a/src/dusk/save/saveslot.h b/src/dusk/save/saveslot.h new file mode 100644 index 00000000..e1865493 --- /dev/null +++ b/src/dusk/save/saveslot.h @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "time/timeepoch.h" +#include "savedevice.h" +#include "yyjson.h" + +#define SAVE_SLOT_NAME_LENGTH 8 + +#pragma pack(push, 1) +typedef struct saveslot_s { + uint8_t version; + uint8_t dataType; + + char_t name[SAVE_SLOT_NAME_LENGTH + 1];// 8 characters + null terminator + dusktimeepoch_t time; +} saveslot_t; +#pragma pack(pop) + +/** + * Inits the save slot with the default state, this is functionally "new game" + * but will not set the player name, as that is what we use to determine if a + * save slot is "in use" or not. + * + * @param slot The save slot to init. + */ +void saveSlotInit(saveslot_t *slot); + +/** + * Checks if the save slot is in use, this is determined by checking if the + * player name is set or not. + * + * @param slot The save slot to check. + * @return True if the save slot is in use, false otherwise. + */ +bool_t saveSlotInUse(saveslot_t *slot); + +/** + * Writes the given save slot out to the given JSON object. + * + * @param slot The save slot to write. + * @param doc The mutable JSON document that owns the object. + * @param object The mutable JSON object to write into. + * @return Error state if any. + */ +errorret_t saveSlotWriteJSON( + saveslot_t *slot, + yyjson_mut_doc *doc, + yyjson_mut_val *object +); + +/** + * Reads the given save slot in from the given JSON object. + * + * @param slot The save slot to read into. + * @param object The JSON object to read from. + * @return Error state if any. + */ +errorret_t saveSlotReadJSON(saveslot_t *slot, yyjson_val *object); \ No newline at end of file