Unify save system into one save.h/.c; diverge storage format per platform
Renames savefile_t to saveslot_t and folds last session's standalone settings.h/.c module back in as savemeta_t, so there's one save system (SAVE.slots[] + SAVE.meta) instead of two parallel ones - while letting each platform pick its own physical format for the two concepts: - Linux now writes human-editable JSON (slot0.json, settings.json, ...) via yyjson's mutable writer API, so players can hand-fix a bad setting. - PSP folds meta into the same sceUtilitySavedata binary payload as its one save slot (SAVE_SLOT_COUNT_MAX=1 there - a future save picker will let players manage multiple named saves via the OS's own browser). - GameCube consolidates the 3 per-slot memory card files and the separate settings file into one combined card file. Also fixes two bugs surfaced while building this: the CRC finalize step seeked to a hardcoded offset (only safe for one section per file, breaks once meta+slots share a buffer), and save.c's async/sync dispatch left an unconditional fallback call that doesn't exist on PSP-only platforms. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,8 +6,9 @@
|
||||
*/
|
||||
|
||||
#include "save/save.h"
|
||||
#include "save/savejsonlinux.h"
|
||||
#include "util/string.h"
|
||||
#include <stdio.h>
|
||||
#include "util/memory.h"
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
@@ -25,60 +26,121 @@ errorret_t saveDisposeLinux(void) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveLoadLinux(const uint8_t slot, savefile_t *file) {
|
||||
char_t path[SAVE_LINUX_PATH_MAX];
|
||||
snprintf(path, SAVE_LINUX_PATH_MAX, SAVE_LINUX_FILE_FORMAT,
|
||||
SAVE.platform.savePath, (uint32_t)slot
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
FILE *f = fopen(path, "rb");
|
||||
if(!f) {
|
||||
file->exists = false;
|
||||
errorOk();
|
||||
}
|
||||
static void _saveMetaPathLinux(char_t *out, const size_t max) {
|
||||
snprintf(out, max, SAVE_LINUX_META_FILE_FORMAT, SAVE.platform.savePath);
|
||||
}
|
||||
|
||||
size_t read = fread(file, sizeof(savefile_t), 1, f);
|
||||
fclose(f);
|
||||
errorret_t saveSlotLoadLinux(const uint8_t slot, saveslot_t *out) {
|
||||
char_t path[SAVE_LINUX_PATH_MAX];
|
||||
_saveSlotPathLinux(path, SAVE_LINUX_PATH_MAX, slot);
|
||||
|
||||
if(read != 1) {
|
||||
file->exists = false;
|
||||
errorThrow("Failed to read save data for slot %u", (uint32_t)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();
|
||||
|
||||
file->exists = true;
|
||||
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 saveWriteLinux(const uint8_t slot, const savefile_t *file) {
|
||||
errorret_t saveSlotWriteLinux(const uint8_t slot, saveslot_t *slotData) {
|
||||
char_t path[SAVE_LINUX_PATH_MAX];
|
||||
snprintf(path, SAVE_LINUX_PATH_MAX, SAVE_LINUX_FILE_FORMAT,
|
||||
SAVE.platform.savePath, (uint32_t)slot
|
||||
_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
|
||||
);
|
||||
|
||||
FILE *f = fopen(path, "wb");
|
||||
if(!f) {
|
||||
errorThrow("Failed to open save file for writing: slot %u", (uint32_t)slot);
|
||||
}
|
||||
|
||||
size_t written = fwrite(file, sizeof(savefile_t), 1, f);
|
||||
fclose(f);
|
||||
|
||||
if(written != 1) {
|
||||
errorThrow("Failed to write save data for slot %u", (uint32_t)slot);
|
||||
}
|
||||
errorret_t ret = saveJsonWriterSaveLinux(&writer, path);
|
||||
saveJsonWriterDisposeLinux(&writer);
|
||||
errorChain(ret);
|
||||
|
||||
slotData->exists = true;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveDeleteLinux(const uint8_t slot) {
|
||||
errorret_t saveDeleteSlotLinux(const uint8_t slot) {
|
||||
char_t path[SAVE_LINUX_PATH_MAX];
|
||||
snprintf(path, SAVE_LINUX_PATH_MAX, SAVE_LINUX_FILE_FORMAT,
|
||||
SAVE.platform.savePath, (uint32_t)slot
|
||||
);
|
||||
_saveSlotPathLinux(path, SAVE_LINUX_PATH_MAX, slot);
|
||||
|
||||
if(remove(path) != 0 && errno != ENOENT) {
|
||||
errorThrow("Failed to delete save file for slot %u", (uint32_t)slot);
|
||||
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->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);
|
||||
|
||||
errorret_t ret = saveJsonWriterSaveLinux(&writer, path);
|
||||
saveJsonWriterDisposeLinux(&writer);
|
||||
errorChain(ret);
|
||||
|
||||
meta->exists = true;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user