Add versioned save slot/settings schemas with strict version checks

Splits saveslot_t/savesettings_t into a stable, version-independent
in-memory struct (save/slot/, save/settings/) and a separate frozen
per-schema-version struct (saveslotver1_t/savesettingsver1_t) used only
for JSON serialization, converted between manually. Every write now
stamps a "version" field (SAVE_SLOT_CURRENT/SAVE_SETTINGS_CURRENT), and
every read requires it to match exactly, rejecting anything else -
laying the groundwork for a real migration path instead of silently
misreading old/incompatible save data.
This commit is contained in:
2026-09-08 12:49:06 -05:00
parent 327c2bec70
commit 8139a0ea54
28 changed files with 634 additions and 115 deletions
+10 -10
View File
@@ -57,7 +57,7 @@ static void test_saveInit_setsDefaults(void **state) {
assert_int_equal(SAVE.slotCurrent, 0xFF);
assert_false(SAVE.findingAvailableDevice);
assert_false(SAVE.noAvailableDeviceFound);
assert_int_equal(SAVE.settings.someSetting, 0);
assert_true(SAVE.settings.audioMasterVolume == 1.0f);
assert_int_equal(SAVE.devices[0].state, SAVE_DEVICE_STATE_UNKNOWN);
for(uint8_t i = 0; i < SAVE_SLOT_COUNT; i++) {
@@ -229,7 +229,7 @@ static void test_saveSaveSettings_notDirty_isNoop(void **state) {
// if someone changes the no-op condition without meaning to.
makeDeviceAvailable();
assert_false(SAVE.settingsDirty);
SAVE.settings.someSetting = 123;
SAVE.settings.audioMasterVolume = 12.0f;
errorret_t ret = saveSaveSettings();
assert_true(errorIsOk(ret));
@@ -238,7 +238,7 @@ static void test_saveSaveSettings_notDirty_isNoop(void **state) {
saveSettingsInit(&onDisk);
errorret_t readRet = saveDeviceSettingsRead(&SAVE.devices[0], &onDisk);
assert_true(errorIsOk(readRet));
assert_int_equal(onDisk.someSetting, 0);// never actually written
assert_true(onDisk.audioMasterVolume == 1.0f);// never actually written
}
static void test_saveSaveSettings_dirtyForced_writesAndClearsFlag(
@@ -246,7 +246,7 @@ static void test_saveSaveSettings_dirtyForced_writesAndClearsFlag(
) {
makeDeviceAvailable();
SAVE.settingsDirty = true;// nothing in real code sets this - forced here
SAVE.settings.someSetting = 55;
SAVE.settings.audioMasterVolume = 5.5f;
errorret_t ret = saveSaveSettings();
assert_true(errorIsOk(ret));
@@ -256,7 +256,7 @@ static void test_saveSaveSettings_dirtyForced_writesAndClearsFlag(
saveSettingsInit(&onDisk);
errorret_t readRet = saveDeviceSettingsRead(&SAVE.devices[0], &onDisk);
assert_true(errorIsOk(readRet));
assert_int_equal(onDisk.someSetting, 55);
assert_true(onDisk.audioMasterVolume == 5.5f);
}
static void test_saveLoadSettings_noCurrentDeviceAsserts(void **state) {
@@ -268,16 +268,16 @@ static void test_saveLoadSettings_readsAndClearsDirtyFlag(void **state) {
savesettings_t onDisk;
saveSettingsInit(&onDisk);
onDisk.someSetting = 99;
onDisk.audioMasterVolume = 9.25f;
errorret_t writeRet = saveDeviceSettingsWrite(&SAVE.devices[0], &onDisk);
assert_true(errorIsOk(writeRet));
SAVE.settingsDirty = true;
SAVE.settings.someSetting = 0;
SAVE.settings.audioMasterVolume = 0.0f;
errorret_t ret = saveLoadSettings();
assert_true(errorIsOk(ret));
assert_int_equal(SAVE.settings.someSetting, 99);
assert_true(SAVE.settings.audioMasterVolume == 9.25f);
assert_false(SAVE.settingsDirty);
}
@@ -470,7 +470,7 @@ static void test_saveUpdate_foundDevice_rewritesSettingsUnconditionally(
// it, regardless of settingsDirty - independent of (and unguarded by) the
// same dirty-flag check saveSaveSettings() itself honors.
resetFindCallbackState();
SAVE.settings.someSetting = 321;
SAVE.settings.audioMasterVolume = 3.25f;
assert_false(SAVE.settingsDirty);
saveFindAvailableDevice(findAvailableCallback, NULL);
@@ -482,7 +482,7 @@ static void test_saveUpdate_foundDevice_rewritesSettingsUnconditionally(
saveSettingsInit(&onDisk);
errorret_t readRet = saveDeviceSettingsRead(&SAVE.devices[0], &onDisk);
assert_true(errorIsOk(readRet));
assert_int_equal(onDisk.someSetting, 321);
assert_true(onDisk.audioMasterVolume == 3.25f);
}
int main(void) {