Save file update (incomplete)
This commit is contained in:
@@ -76,5 +76,6 @@ add_subdirectory(system)
|
||||
add_subdirectory(time)
|
||||
add_subdirectory(ui)
|
||||
add_subdirectory(network)
|
||||
add_subdirectory(save)
|
||||
add_subdirectory(util)
|
||||
add_subdirectory(thread)
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "system/system.h"
|
||||
#include "console/console.h"
|
||||
#include "item/backpack.h"
|
||||
#include "save/save.h"
|
||||
|
||||
double jerry_port_current_time(void) {
|
||||
dusktimeepoch_t epoch = timeGetEpoch();
|
||||
@@ -50,6 +51,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
consoleInit();
|
||||
errorChain(inputInit());
|
||||
errorChain(assetInit());
|
||||
errorChain(saveInit());
|
||||
errorChain(localeManagerInit());
|
||||
errorChain(scriptManagerInit());
|
||||
errorChain(displayInit());
|
||||
@@ -105,6 +107,7 @@ errorret_t engineDispose(void) {
|
||||
uiDispose();
|
||||
consoleDispose();
|
||||
errorChain(displayDispose());
|
||||
errorChain(saveDispose());
|
||||
errorChain(assetDispose());
|
||||
|
||||
errorOk();
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# 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
|
||||
save.c
|
||||
savestream.c
|
||||
)
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 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"
|
||||
|
||||
save_t SAVE;
|
||||
|
||||
errorret_t saveInit(void) {
|
||||
memoryZero(&SAVE, sizeof(save_t));
|
||||
|
||||
#ifdef saveInitPlatform
|
||||
errorChain(saveInitPlatform());
|
||||
#endif
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveDispose(void) {
|
||||
#ifdef saveDisposePlatform
|
||||
errorChain(saveDisposePlatform());
|
||||
#endif
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveLoad(const uint8_t slot) {
|
||||
assertTrue(slot < SAVE_FILE_COUNT_MAX, "slot exceeds SAVE_FILE_COUNT_MAX");
|
||||
|
||||
savefile_t *file = &SAVE.files[slot];
|
||||
file->exists = false;
|
||||
|
||||
savestream_t stream;
|
||||
memoryZero(&stream, sizeof(savestream_t));
|
||||
|
||||
#ifdef saveStreamOpenReadPlatform
|
||||
errorChain(saveStreamOpenReadPlatform(&stream, slot));
|
||||
#endif
|
||||
|
||||
if(!stream.found) errorOk();
|
||||
|
||||
errorret_t ret = saveFileLoad(&stream, file);
|
||||
|
||||
#ifdef saveStreamClosePlatform
|
||||
saveStreamClosePlatform(&stream);
|
||||
#endif
|
||||
|
||||
if(ret.code != ERROR_OK) return ret;
|
||||
|
||||
errorChain(saveStreamVerifyChecksumImpl(&stream, slot));
|
||||
|
||||
file->exists = true;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveWrite(const uint8_t slot) {
|
||||
assertTrue(slot < SAVE_FILE_COUNT_MAX, "slot exceeds SAVE_FILE_COUNT_MAX");
|
||||
|
||||
savefile_t *file = &SAVE.files[slot];
|
||||
|
||||
savestream_t stream;
|
||||
memoryZero(&stream, sizeof(savestream_t));
|
||||
|
||||
#ifdef saveStreamOpenWritePlatform
|
||||
errorChain(saveStreamOpenWritePlatform(&stream, slot));
|
||||
#endif
|
||||
|
||||
errorret_t ret = saveFileWrite(&stream, file);
|
||||
|
||||
if(ret.code == ERROR_OK) {
|
||||
ret = saveStreamFinalizeWriteImpl(&stream);
|
||||
}
|
||||
|
||||
#ifdef saveStreamClosePlatform
|
||||
saveStreamClosePlatform(&stream);
|
||||
#endif
|
||||
|
||||
if(ret.code != ERROR_OK) return ret;
|
||||
|
||||
file->exists = true;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveDelete(const uint8_t slot) {
|
||||
assertTrue(slot < SAVE_FILE_COUNT_MAX, "slot exceeds SAVE_FILE_COUNT_MAX");
|
||||
|
||||
#ifdef saveDeletePlatform
|
||||
errorChain(saveDeletePlatform(slot));
|
||||
#endif
|
||||
|
||||
SAVE.files[slot].exists = false;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
bool_t saveExists(const uint8_t slot) {
|
||||
assertTrue(slot < SAVE_FILE_COUNT_MAX, "slot exceeds SAVE_FILE_COUNT_MAX");
|
||||
return SAVE.files[slot].exists;
|
||||
}
|
||||
|
||||
savefile_t * saveGet(const uint8_t slot) {
|
||||
assertTrue(slot < SAVE_FILE_COUNT_MAX, "slot exceeds SAVE_FILE_COUNT_MAX");
|
||||
return &SAVE.files[slot];
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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 "savefile.h"
|
||||
#include "save/saveplatform.h"
|
||||
|
||||
typedef struct {
|
||||
/** Per-slot save file data; indexed 0 to SAVE_FILE_COUNT_MAX - 1. */
|
||||
savefile_t files[SAVE_FILE_COUNT_MAX];
|
||||
/** Platform-specific save system state (paths, card handles, etc.). */
|
||||
saveplatform_t platform;
|
||||
} save_t;
|
||||
|
||||
extern save_t SAVE;
|
||||
|
||||
/**
|
||||
* Initializes the save system.
|
||||
*
|
||||
* @return An error code if initialization fails.
|
||||
*/
|
||||
errorret_t saveInit(void);
|
||||
|
||||
/**
|
||||
* Disposes of the save system.
|
||||
*
|
||||
* @return An error code if disposal fails.
|
||||
*/
|
||||
errorret_t saveDispose(void);
|
||||
|
||||
/**
|
||||
* Loads the save file for a given slot from persistent storage.
|
||||
*
|
||||
* @param slot The save slot index (0 to SAVE_FILE_COUNT_MAX - 1).
|
||||
* @return An error code if the load fails.
|
||||
*/
|
||||
errorret_t saveLoad(const uint8_t slot);
|
||||
|
||||
/**
|
||||
* Writes the save file for a given slot to persistent storage.
|
||||
*
|
||||
* @param slot The save slot index (0 to SAVE_FILE_COUNT_MAX - 1).
|
||||
* @return An error code if the write fails.
|
||||
*/
|
||||
errorret_t saveWrite(const uint8_t slot);
|
||||
|
||||
/**
|
||||
* Deletes the save file for a given slot from persistent storage.
|
||||
*
|
||||
* @param slot The save slot index (0 to SAVE_FILE_COUNT_MAX - 1).
|
||||
* @return An error code if the delete fails.
|
||||
*/
|
||||
errorret_t saveDelete(const uint8_t slot);
|
||||
|
||||
/**
|
||||
* Checks whether a save file exists for a given slot.
|
||||
*
|
||||
* @param slot The save slot index (0 to SAVE_FILE_COUNT_MAX - 1).
|
||||
* @return true if a save file exists for the slot, false otherwise.
|
||||
*/
|
||||
bool_t saveExists(const uint8_t slot);
|
||||
|
||||
/**
|
||||
* Gets a pointer to the save file data for a given slot.
|
||||
*
|
||||
* @param slot The save slot index (0 to SAVE_FILE_COUNT_MAX - 1).
|
||||
* @return A pointer to the savefile_t for the given slot.
|
||||
*/
|
||||
savefile_t * saveGet(const uint8_t slot);
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
/** Format version written into every save file. Increment on breaking change. */
|
||||
#define SAVE_FILE_VERSION 1
|
||||
|
||||
/** Magic bytes that identify a Dusk save file. */
|
||||
#define SAVE_FILE_HEADER "DSK"
|
||||
|
||||
/** Byte length of the magic header (excludes the null terminator). */
|
||||
#define SAVE_FILE_HEADER_SIZE (sizeof(SAVE_FILE_HEADER) - 1)
|
||||
|
||||
/** Maximum number of independent save slots supported. */
|
||||
#define SAVE_FILE_COUNT_MAX 3
|
||||
|
||||
typedef struct {
|
||||
/** Magic header bytes read from the file; must equal SAVE_FILE_HEADER. */
|
||||
char_t header[SAVE_FILE_HEADER_SIZE];
|
||||
/** Format version read from the file; used to branch on older layouts. */
|
||||
uint32_t version;
|
||||
/** Runtime flag — true if this slot was successfully loaded or written. */
|
||||
bool_t exists;
|
||||
} savefile_t;
|
||||
@@ -0,0 +1,336 @@
|
||||
/**
|
||||
* 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 saveStreamFinalizeWriteImpl(savestream_t *stream) {
|
||||
uint32_t finalCRC = cryptCRC32End(stream->checksum);
|
||||
uint32_t leChecksum = endianLittleToHost32(finalCRC);
|
||||
|
||||
#ifdef saveStreamSeekPlatform
|
||||
errorChain(saveStreamSeekPlatform(stream, SAVE_FILE_HEADER_SIZE));
|
||||
#endif
|
||||
|
||||
errorChain(saveStreamWriteBytesRawImpl(
|
||||
stream, &leChecksum, sizeof(uint32_t)
|
||||
));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveStreamVerifyChecksumImpl(
|
||||
savestream_t *stream, const uint8_t slot
|
||||
) {
|
||||
uint32_t computed = cryptCRC32End(stream->checksum);
|
||||
if(computed != stream->expectedChecksum) {
|
||||
errorThrow("Save slot %u has invalid checksum", (uint32_t)slot);
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
|
||||
errorret_t saveStreamReadHeaderImpl(
|
||||
savestream_t *stream, char_t header[SAVE_FILE_HEADER_SIZE]
|
||||
) {
|
||||
errorChain(saveStreamReadBytesRawImpl(stream, header, SAVE_FILE_HEADER_SIZE));
|
||||
|
||||
if(
|
||||
header[0] != SAVE_FILE_HEADER[0] ||
|
||||
header[1] != SAVE_FILE_HEADER[1] ||
|
||||
header[2] != SAVE_FILE_HEADER[2]
|
||||
) {
|
||||
errorThrow("Save file 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[SAVE_FILE_HEADER_SIZE]
|
||||
) {
|
||||
errorChain(saveStreamWriteBytesRawImpl(
|
||||
stream, header, SAVE_FILE_HEADER_SIZE
|
||||
));
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Bool
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Integers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Float
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// String
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Date (dusktimeepoch_t — stored as three little-endian 64-bit bit patterns)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
/**
|
||||
* 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 "savefile.h"
|
||||
#include "save/saveplatform.h"
|
||||
#include "time/timeepoch.h"
|
||||
|
||||
/**
|
||||
* Active I/O context for a single save slot read or write pass.
|
||||
*
|
||||
* Platform code fills/drains `platform`. The stream layer maintains the
|
||||
* running CRC32 accumulator so header/checksum bytes are excluded
|
||||
* automatically from the digest.
|
||||
*/
|
||||
typedef struct {
|
||||
/** Set true by the platform open-read function if the file exists. */
|
||||
bool_t found;
|
||||
/** Running CRC32 accumulator, updated by each CRC-covered read/write. */
|
||||
uint32_t checksum;
|
||||
/** CRC32 value read from the file header; verified after loading. */
|
||||
uint32_t expectedChecksum;
|
||||
/** Platform-specific stream state (file handle, buffer, etc.). */
|
||||
saveplatformstream_t platform;
|
||||
} savestream_t;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Internal functions — do not call directly; use the macros below.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
|
||||
/**
|
||||
* Finalizes a write stream: computes the final CRC32, seeks to the
|
||||
* checksum field in the header, and writes it in little-endian order.
|
||||
*
|
||||
* @param stream Active write stream.
|
||||
* @return An error if the seek or write fails.
|
||||
*/
|
||||
errorret_t saveStreamFinalizeWriteImpl(savestream_t *stream);
|
||||
|
||||
/**
|
||||
* Verifies that the CRC32 accumulated during loading matches the value
|
||||
* stored in the file header.
|
||||
*
|
||||
* @param stream Active read stream (loading must be complete).
|
||||
* @param slot Slot index used in the error message on mismatch.
|
||||
* @return An error if the checksum does not match.
|
||||
*/
|
||||
errorret_t saveStreamVerifyChecksumImpl(
|
||||
savestream_t *stream, const uint8_t slot
|
||||
);
|
||||
|
||||
/**
|
||||
* Reads and validates the magic header, then reads the stored CRC32 and
|
||||
* resets the running accumulator.
|
||||
*
|
||||
* @param stream Active read stream.
|
||||
* @param header Buffer of SAVE_FILE_HEADER_SIZE bytes to receive the header.
|
||||
* @return An error if the header is missing or invalid.
|
||||
*/
|
||||
errorret_t saveStreamReadHeaderImpl(
|
||||
savestream_t *stream, char_t header[SAVE_FILE_HEADER_SIZE]
|
||||
);
|
||||
|
||||
/**
|
||||
* Writes the magic header and a zero CRC32 placeholder, then resets the
|
||||
* running accumulator.
|
||||
*
|
||||
* @param stream Active write stream.
|
||||
* @param header Buffer of SAVE_FILE_HEADER_SIZE bytes to write.
|
||||
* @return An error if the write fails.
|
||||
*/
|
||||
errorret_t saveStreamWriteHeaderImpl(
|
||||
savestream_t *stream,
|
||||
const char_t header[SAVE_FILE_HEADER_SIZE]
|
||||
);
|
||||
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// User-facing macros — these embed errorChain so errors propagate
|
||||
// automatically from saveFileLoad / saveFileWrite.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#define saveFileReadHeader(stream, header) \
|
||||
errorChain(saveStreamReadHeaderImpl(stream, header))
|
||||
#define saveFileWriteHeader(stream, header) \
|
||||
errorChain(saveStreamWriteHeaderImpl(stream, header))
|
||||
|
||||
#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))
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Game code must implement these two functions.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Reads the contents of a save slot from the stream into the save file
|
||||
* struct. Use saveFileRead* macros to deserialize fields one at a time.
|
||||
*
|
||||
* @param stream Active read stream for this slot.
|
||||
* @param file Save file struct to populate.
|
||||
* @return An error code if loading fails.
|
||||
*/
|
||||
extern errorret_t saveFileLoad(savestream_t *stream, savefile_t *file);
|
||||
|
||||
/**
|
||||
* Writes the contents of the save file struct into the stream.
|
||||
* Use saveFileWrite* macros to serialize fields one at a time.
|
||||
*
|
||||
* @param stream Active write stream for this slot.
|
||||
* @param file Save file struct to serialize.
|
||||
* @return An error code if writing fails.
|
||||
*/
|
||||
extern errorret_t saveFileWrite(savestream_t *stream, savefile_t *file);
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "script/module/event/moduleEvent.h"
|
||||
#include "script/module/ui/moduletextbox.h"
|
||||
#include "script/module/ui/modulefullbox.h"
|
||||
#include "script/module/save/modulesave.h"
|
||||
|
||||
static void moduleRegister(void) {
|
||||
moduleInclude();
|
||||
@@ -51,4 +52,5 @@ static void moduleRegister(void) {
|
||||
moduleStory();
|
||||
moduleTextbox();
|
||||
moduleFullbox();
|
||||
moduleSave();
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ static void moduleBaseEval(const char_t *script) {
|
||||
|
||||
/**
|
||||
* Throw a type error from a module function.
|
||||
*
|
||||
*
|
||||
* @param message The error message to throw.
|
||||
* @return A JerryScript error value.
|
||||
*/
|
||||
@@ -184,6 +184,21 @@ static jerry_value_t moduleBaseThrow(const char_t *message) {
|
||||
return jerry_throw_sz(JERRY_ERROR_TYPE, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a C errorret_t into a JS exception, forwarding the error message
|
||||
* so that try/catch in JS sees the real error text. Clears the C error state.
|
||||
*
|
||||
* @param err The errorret_t returned by a failing C function.
|
||||
* @return A JerryScript error value carrying the C error message.
|
||||
*/
|
||||
static jerry_value_t moduleBaseThrowError(const errorret_t err) {
|
||||
assertNotNull(err.state, "Error state must not be NULL");
|
||||
assertNotNull(err.state->message, "Error message must not be NULL");
|
||||
jerry_value_t jsErr = jerry_throw_sz(JERRY_ERROR_TYPE, err.state->message);
|
||||
errorCatch(err);
|
||||
return jsErr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a global string constant.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "script/module/save/modulesaveslot.h"
|
||||
#include "save/save.h"
|
||||
|
||||
static scriptproto_t MODULE_SAVE_PROTO;
|
||||
|
||||
// Static Properties
|
||||
|
||||
moduleBaseFunction(moduleSaveGetCount) {
|
||||
return jerry_number((double)SAVE_FILE_COUNT_MAX);
|
||||
}
|
||||
|
||||
// Static Methods
|
||||
|
||||
moduleBaseFunction(moduleSaveLoad) {
|
||||
if(argc < 1) return moduleBaseThrow("Save.load: expected (slot)");
|
||||
moduleBaseRequireNumber(0);
|
||||
const uint8_t slot = (uint8_t)jerry_value_as_number(args[0]);
|
||||
if(slot >= SAVE_FILE_COUNT_MAX) {
|
||||
return moduleBaseThrow("Save.load: slot out of range");
|
||||
}
|
||||
errorret_t err = saveLoad(slot);
|
||||
if(err.code != ERROR_OK) return moduleBaseThrowError(err);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleSaveWrite) {
|
||||
if(argc < 1) return moduleBaseThrow("Save.write: expected (slot)");
|
||||
moduleBaseRequireNumber(0);
|
||||
const uint8_t slot = (uint8_t)jerry_value_as_number(args[0]);
|
||||
if(slot >= SAVE_FILE_COUNT_MAX) {
|
||||
return moduleBaseThrow("Save.write: slot out of range");
|
||||
}
|
||||
errorret_t err = saveWrite(slot);
|
||||
if(err.code != ERROR_OK) return moduleBaseThrowError(err);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleSaveDelete) {
|
||||
if(argc < 1) return moduleBaseThrow("Save.delete: expected (slot)");
|
||||
moduleBaseRequireNumber(0);
|
||||
const uint8_t slot = (uint8_t)jerry_value_as_number(args[0]);
|
||||
if(slot >= SAVE_FILE_COUNT_MAX) {
|
||||
return moduleBaseThrow("Save.delete: slot out of range");
|
||||
}
|
||||
errorret_t err = saveDelete(slot);
|
||||
if(err.code != ERROR_OK) return moduleBaseThrowError(err);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleSaveExists) {
|
||||
if(argc < 1) return moduleBaseThrow("Save.exists: expected (slot)");
|
||||
moduleBaseRequireNumber(0);
|
||||
const uint8_t slot = (uint8_t)jerry_value_as_number(args[0]);
|
||||
if(slot >= SAVE_FILE_COUNT_MAX) {
|
||||
return moduleBaseThrow("Save.exists: slot out of range");
|
||||
}
|
||||
return jerry_boolean(saveExists(slot));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleSaveGet) {
|
||||
if(argc < 1) return moduleBaseThrow("Save.get: expected (slot)");
|
||||
moduleBaseRequireNumber(0);
|
||||
const uint8_t slot = (uint8_t)jerry_value_as_number(args[0]);
|
||||
if(slot >= SAVE_FILE_COUNT_MAX) {
|
||||
return moduleBaseThrow("Save.get: slot out of range");
|
||||
}
|
||||
return moduleSaveSlotCreate(slot);
|
||||
}
|
||||
|
||||
static void moduleSave(void) {
|
||||
moduleSaveSlot();
|
||||
|
||||
scriptProtoInit(&MODULE_SAVE_PROTO, "Save", sizeof(uint8_t), NULL);
|
||||
|
||||
scriptProtoDefineStaticProp(&MODULE_SAVE_PROTO, "count",
|
||||
moduleSaveGetCount, NULL
|
||||
);
|
||||
scriptProtoDefineStaticFunc(&MODULE_SAVE_PROTO, "load", moduleSaveLoad);
|
||||
scriptProtoDefineStaticFunc(&MODULE_SAVE_PROTO, "write", moduleSaveWrite);
|
||||
scriptProtoDefineStaticFunc(&MODULE_SAVE_PROTO, "delete", moduleSaveDelete);
|
||||
scriptProtoDefineStaticFunc(&MODULE_SAVE_PROTO, "exists", moduleSaveExists);
|
||||
scriptProtoDefineStaticFunc(&MODULE_SAVE_PROTO, "get", moduleSaveGet);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "save/save.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t slot;
|
||||
} saveslotscript_t;
|
||||
|
||||
static scriptproto_t MODULE_SAVE_SLOT_PROTO;
|
||||
|
||||
static inline saveslotscript_t * moduleSaveSlotGet(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
return (saveslotscript_t *)scriptProtoGetValue(
|
||||
&MODULE_SAVE_SLOT_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
|
||||
// Properties
|
||||
|
||||
moduleBaseFunction(moduleSaveSlotGetSlot) {
|
||||
saveslotscript_t *s = moduleSaveSlotGet(callInfo);
|
||||
if(!s) return jerry_undefined();
|
||||
return jerry_number((double)s->slot);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleSaveSlotGetExists) {
|
||||
saveslotscript_t *s = moduleSaveSlotGet(callInfo);
|
||||
if(!s) return jerry_boolean(false);
|
||||
return jerry_boolean(saveExists(s->slot));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleSaveSlotGetVersion) {
|
||||
saveslotscript_t *s = moduleSaveSlotGet(callInfo);
|
||||
if(!s) return jerry_undefined();
|
||||
return jerry_number((double)saveGet(s->slot)->version);
|
||||
}
|
||||
|
||||
// Methods
|
||||
|
||||
moduleBaseFunction(moduleSaveSlotLoad) {
|
||||
saveslotscript_t *s = moduleSaveSlotGet(callInfo);
|
||||
if(!s) return jerry_undefined();
|
||||
errorret_t err = saveLoad(s->slot);
|
||||
if(err.code != ERROR_OK) return moduleBaseThrowError(err);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleSaveSlotWrite) {
|
||||
saveslotscript_t *s = moduleSaveSlotGet(callInfo);
|
||||
if(!s) return jerry_undefined();
|
||||
errorret_t err = saveWrite(s->slot);
|
||||
if(err.code != ERROR_OK) return moduleBaseThrowError(err);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleSaveSlotDelete) {
|
||||
saveslotscript_t *s = moduleSaveSlotGet(callInfo);
|
||||
if(!s) return jerry_undefined();
|
||||
errorret_t err = saveDelete(s->slot);
|
||||
if(err.code != ERROR_OK) return moduleBaseThrowError(err);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
jerry_value_t moduleSaveSlotCreate(const uint8_t slot) {
|
||||
saveslotscript_t s = { .slot = slot };
|
||||
return scriptProtoCreateValue(&MODULE_SAVE_SLOT_PROTO, &s);
|
||||
}
|
||||
|
||||
static void moduleSaveSlot(void) {
|
||||
scriptProtoInit(&MODULE_SAVE_SLOT_PROTO, "SaveSlot", sizeof(saveslotscript_t), NULL);
|
||||
|
||||
scriptProtoDefineProp(&MODULE_SAVE_SLOT_PROTO, "slot",
|
||||
moduleSaveSlotGetSlot, NULL
|
||||
);
|
||||
scriptProtoDefineProp(&MODULE_SAVE_SLOT_PROTO, "exists",
|
||||
moduleSaveSlotGetExists, NULL
|
||||
);
|
||||
scriptProtoDefineProp(&MODULE_SAVE_SLOT_PROTO, "version",
|
||||
moduleSaveSlotGetVersion, NULL
|
||||
);
|
||||
|
||||
scriptProtoDefineFunc(&MODULE_SAVE_SLOT_PROTO, "load", moduleSaveSlotLoad);
|
||||
scriptProtoDefineFunc(&MODULE_SAVE_SLOT_PROTO, "write", moduleSaveSlotWrite);
|
||||
scriptProtoDefineFunc(&MODULE_SAVE_SLOT_PROTO, "delete", moduleSaveSlotDelete);
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
array.c
|
||||
crypt.c
|
||||
endian.c
|
||||
memory.c
|
||||
string.c
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "util/crypt.h"
|
||||
|
||||
static void _cryptCRC32Step(uint32_t *crc, const uint8_t byte) {
|
||||
*crc ^= byte;
|
||||
for(int j = 0; j < 8; j++) {
|
||||
*crc = (*crc >> 1) ^ (0xEDB88320u & -(*crc & 1u));
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t cryptCRC32Begin(void) {
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
void cryptCRC32Update(uint32_t *crc, const void *data, const size_t size) {
|
||||
const uint8_t *bytes = (const uint8_t *)data;
|
||||
for(size_t i = 0; i < size; i++) {
|
||||
_cryptCRC32Step(crc, bytes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t cryptCRC32End(const uint32_t crc) {
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
uint32_t cryptCRC32(const void *data, const size_t size) {
|
||||
uint32_t crc = cryptCRC32Begin();
|
||||
cryptCRC32Update(&crc, data, size);
|
||||
return cryptCRC32End(crc);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
/**
|
||||
* Computes a CRC32 checksum over a block of data.
|
||||
*
|
||||
* @param data Pointer to the data to checksum.
|
||||
* @param size Number of bytes to checksum.
|
||||
* @return The CRC32 checksum.
|
||||
*/
|
||||
uint32_t cryptCRC32(const void *data, const size_t size);
|
||||
|
||||
/**
|
||||
* Returns the initial CRC32 accumulator value.
|
||||
*
|
||||
* @return The initial accumulator.
|
||||
*/
|
||||
uint32_t cryptCRC32Begin(void);
|
||||
|
||||
/**
|
||||
* Feeds bytes into a running CRC32 accumulator.
|
||||
*
|
||||
* @param crc Pointer to the current accumulator (updated in place).
|
||||
* @param data Pointer to the data to feed in.
|
||||
* @param size Number of bytes to process.
|
||||
*/
|
||||
void cryptCRC32Update(uint32_t *crc, const void *data, const size_t size);
|
||||
|
||||
/**
|
||||
* Finalizes a running CRC32 accumulator and returns the checksum.
|
||||
*
|
||||
* @param crc The current accumulator value.
|
||||
* @return The final CRC32 checksum.
|
||||
*/
|
||||
uint32_t cryptCRC32End(const uint32_t crc);
|
||||
@@ -20,5 +20,6 @@ add_subdirectory(log)
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(input)
|
||||
add_subdirectory(network)
|
||||
add_subdirectory(save)
|
||||
add_subdirectory(system)
|
||||
add_subdirectory(time)
|
||||
@@ -0,0 +1,11 @@
|
||||
# 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
|
||||
)
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "save/save.h"
|
||||
#include "util/string.h"
|
||||
|
||||
static void _saveGetFileName(const uint8_t slot, char_t *out, const size_t max) {
|
||||
snprintf(out, max, "%s_%u", SAVE_DOLPHIN_GAME_CODE, (uint32_t)slot);
|
||||
}
|
||||
|
||||
errorret_t saveInitDolphin(void) {
|
||||
SAVE.platform.mounted = false;
|
||||
|
||||
int32_t result = CARD_Mount(
|
||||
SAVE_DOLPHIN_CHANNEL,
|
||||
SAVE.platform.cardBuffer,
|
||||
NULL
|
||||
);
|
||||
|
||||
if(result < 0) {
|
||||
errorThrow("Failed to mount memory card (error %d)", 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 saveLoadDolphin(const uint8_t slot, savefile_t *file) {
|
||||
char_t fileName[SAVE_DOLPHIN_FILE_NAME_MAX];
|
||||
_saveGetFileName(slot, fileName, SAVE_DOLPHIN_FILE_NAME_MAX);
|
||||
|
||||
int32_t result = CARD_Open(SAVE_DOLPHIN_CHANNEL, fileName, &SAVE.platform.cardFile);
|
||||
if(result == CARD_ERROR_NOFILE) {
|
||||
file->exists = false;
|
||||
errorOk();
|
||||
}
|
||||
if(result < 0) {
|
||||
file->exists = false;
|
||||
errorThrow("Failed to open memory card file for slot %u (error %d)",
|
||||
(uint32_t)slot, result
|
||||
);
|
||||
}
|
||||
|
||||
void *buffer = memalign(32, SAVE_DOLPHIN_SECTOR_SIZE);
|
||||
if(!buffer) {
|
||||
CARD_Close(&SAVE.platform.cardFile);
|
||||
errorThrow("Failed to allocate memory card read buffer");
|
||||
}
|
||||
|
||||
result = CARD_Read(&SAVE.platform.cardFile, buffer, SAVE_DOLPHIN_SECTOR_SIZE, 0);
|
||||
CARD_Close(&SAVE.platform.cardFile);
|
||||
|
||||
if(result < 0) {
|
||||
free(buffer);
|
||||
file->exists = false;
|
||||
errorThrow("Failed to read memory card data for slot %u (error %d)",
|
||||
(uint32_t)slot, result
|
||||
);
|
||||
}
|
||||
|
||||
memoryCopy(file, buffer, sizeof(savefile_t));
|
||||
free(buffer);
|
||||
|
||||
file->exists = true;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveWriteDolphin(const uint8_t slot, const savefile_t *file) {
|
||||
char_t fileName[SAVE_DOLPHIN_FILE_NAME_MAX];
|
||||
_saveGetFileName(slot, fileName, SAVE_DOLPHIN_FILE_NAME_MAX);
|
||||
|
||||
void *buffer = memalign(32, SAVE_DOLPHIN_SECTOR_SIZE);
|
||||
if(!buffer) {
|
||||
errorThrow("Failed to allocate memory card write buffer");
|
||||
}
|
||||
memset(buffer, 0, SAVE_DOLPHIN_SECTOR_SIZE);
|
||||
memoryCopy(buffer, file, sizeof(savefile_t));
|
||||
|
||||
// Try open existing file first; create if absent.
|
||||
int32_t result = CARD_Open(SAVE_DOLPHIN_CHANNEL, fileName, &SAVE.platform.cardFile);
|
||||
if(result == CARD_ERROR_NOFILE) {
|
||||
result = CARD_Create(
|
||||
SAVE_DOLPHIN_CHANNEL,
|
||||
fileName,
|
||||
SAVE_DOLPHIN_SECTOR_SIZE,
|
||||
&SAVE.platform.cardFile
|
||||
);
|
||||
}
|
||||
|
||||
if(result < 0) {
|
||||
free(buffer);
|
||||
errorThrow("Failed to open/create memory card file for slot %u (error %d)",
|
||||
(uint32_t)slot, result
|
||||
);
|
||||
}
|
||||
|
||||
result = CARD_Write(&SAVE.platform.cardFile, buffer, SAVE_DOLPHIN_SECTOR_SIZE, 0);
|
||||
CARD_Close(&SAVE.platform.cardFile);
|
||||
free(buffer);
|
||||
|
||||
if(result < 0) {
|
||||
errorThrow("Failed to write memory card data for slot %u (error %d)",
|
||||
(uint32_t)slot, result
|
||||
);
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveDeleteDolphin(const uint8_t slot) {
|
||||
char_t fileName[SAVE_DOLPHIN_FILE_NAME_MAX];
|
||||
_saveGetFileName(slot, fileName, SAVE_DOLPHIN_FILE_NAME_MAX);
|
||||
|
||||
int32_t result = CARD_Delete(SAVE_DOLPHIN_CHANNEL, fileName);
|
||||
if(result < 0 && result != CARD_ERROR_NOFILE) {
|
||||
errorThrow("Failed to delete memory card file for slot %u (error %d)",
|
||||
(uint32_t)slot, result
|
||||
);
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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/savefile.h"
|
||||
#include <gccore.h>
|
||||
|
||||
#define SAVE_DOLPHIN_FILE_NAME_MAX 32
|
||||
#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
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* Loads a save file from the memory card for the given slot.
|
||||
*
|
||||
* @param slot The save slot index.
|
||||
* @param file Output save file data.
|
||||
* @return An error code if the load fails.
|
||||
*/
|
||||
errorret_t saveLoadDolphin(const uint8_t slot, savefile_t *file);
|
||||
|
||||
/**
|
||||
* Writes a save file to the memory card for the given slot.
|
||||
*
|
||||
* @param slot The save slot index.
|
||||
* @param file Save file data to write.
|
||||
* @return An error code if the write fails.
|
||||
*/
|
||||
errorret_t saveWriteDolphin(const uint8_t slot, const savefile_t *file);
|
||||
|
||||
/**
|
||||
* Deletes the save file for the given slot from the memory card.
|
||||
*
|
||||
* @param slot The save slot index.
|
||||
* @return An error code if the delete fails.
|
||||
*/
|
||||
errorret_t saveDeleteDolphin(const uint8_t slot);
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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 saveDeletePlatform saveDeleteDolphin
|
||||
|
||||
#define saveStreamOpenReadPlatform(stream, slot) \
|
||||
saveStreamOpenReadDolphin(&(stream)->platform, &(stream)->found, slot)
|
||||
#define saveStreamOpenWritePlatform(stream, slot) \
|
||||
saveStreamOpenWriteDolphin(&(stream)->platform, slot)
|
||||
#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)
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* 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"
|
||||
#include "util/string.h"
|
||||
|
||||
static void _saveStreamGetFileName(
|
||||
char_t *out, const size_t max, const uint8_t slot
|
||||
) {
|
||||
snprintf(out, max, "%s_%u", SAVE_DOLPHIN_GAME_CODE, (uint32_t)slot);
|
||||
}
|
||||
|
||||
errorret_t saveStreamOpenReadDolphin(
|
||||
savestreamdolphin_t *p, bool_t *found, const uint8_t slot
|
||||
) {
|
||||
char_t fileName[SAVE_DOLPHIN_FILE_NAME_MAX];
|
||||
_saveStreamGetFileName(fileName, SAVE_DOLPHIN_FILE_NAME_MAX, slot);
|
||||
|
||||
int32_t result = CARD_Open(
|
||||
SAVE_DOLPHIN_CHANNEL, fileName, &p->cardFile
|
||||
);
|
||||
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 for slot %u (error %d)",
|
||||
(uint32_t)slot, result
|
||||
);
|
||||
}
|
||||
|
||||
result = CARD_Read(&p->cardFile, p->buffer, SAVE_DOLPHIN_SECTOR_SIZE, 0);
|
||||
CARD_Close(&p->cardFile);
|
||||
if(result < 0) {
|
||||
*found = false;
|
||||
errorThrow("Failed to read memory card data for slot %u (error %d)",
|
||||
(uint32_t)slot, result
|
||||
);
|
||||
}
|
||||
|
||||
*found = true;
|
||||
p->position = 0;
|
||||
p->writing = false;
|
||||
p->slot = slot;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveStreamOpenWriteDolphin(
|
||||
savestreamdolphin_t *p, const uint8_t slot
|
||||
) {
|
||||
memoryZero(p->buffer, SAVE_DOLPHIN_SECTOR_SIZE);
|
||||
p->position = 0;
|
||||
p->writing = true;
|
||||
p->slot = slot;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void saveStreamCloseDolphin(savestreamdolphin_t *p) {
|
||||
if(!p->writing) return;
|
||||
|
||||
char_t fileName[SAVE_DOLPHIN_FILE_NAME_MAX];
|
||||
_saveStreamGetFileName(fileName, SAVE_DOLPHIN_FILE_NAME_MAX, p->slot);
|
||||
|
||||
int32_t result = CARD_Open(SAVE_DOLPHIN_CHANNEL, fileName, &p->cardFile);
|
||||
if(result == CARD_ERROR_NOFILE) {
|
||||
CARD_Create(
|
||||
SAVE_DOLPHIN_CHANNEL, fileName, SAVE_DOLPHIN_SECTOR_SIZE, &p->cardFile
|
||||
);
|
||||
}
|
||||
|
||||
CARD_Write(&p->cardFile, p->buffer, SAVE_DOLPHIN_SECTOR_SIZE, 0);
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "save/savedolphin.h"
|
||||
#include <gccore.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct {
|
||||
/** libogc memory card file handle. */
|
||||
card_file cardFile;
|
||||
/** In-memory sector buffer; all reads and writes operate on this. */
|
||||
uint8_t buffer[SAVE_DOLPHIN_SECTOR_SIZE] __attribute__((aligned(32)));
|
||||
/** Current read/write position within buffer. */
|
||||
size_t position;
|
||||
/** True when opened for writing; flushes buffer to card on close. */
|
||||
bool_t writing;
|
||||
/** Slot index stored at open time so Close can derive the filename. */
|
||||
uint8_t slot;
|
||||
} savestreamdolphin_t;
|
||||
|
||||
/**
|
||||
* Opens a memory card slot 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.
|
||||
* @param slot Save slot index.
|
||||
* @return An error if reading the card fails for a reason other than
|
||||
* missing file.
|
||||
*/
|
||||
errorret_t saveStreamOpenReadDolphin(
|
||||
savestreamdolphin_t *p, bool_t *found, const uint8_t slot
|
||||
);
|
||||
|
||||
/**
|
||||
* Opens a memory card slot for writing by zeroing the sector buffer.
|
||||
* The buffer is flushed to the card when savestreamCloseDolphin is called.
|
||||
*
|
||||
* @param p Stream to initialize.
|
||||
* @param slot Save slot index.
|
||||
* @return An error if initialization fails.
|
||||
*/
|
||||
errorret_t saveStreamOpenWriteDolphin(
|
||||
savestreamdolphin_t *p, const uint8_t slot
|
||||
);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
@@ -14,5 +14,6 @@ add_subdirectory(asset)
|
||||
add_subdirectory(log)
|
||||
add_subdirectory(input)
|
||||
add_subdirectory(network)
|
||||
add_subdirectory(save)
|
||||
add_subdirectory(system)
|
||||
add_subdirectory(time)
|
||||
@@ -0,0 +1,11 @@
|
||||
# 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
|
||||
savestreamlinux.c
|
||||
)
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "save/save.h"
|
||||
#include "util/string.h"
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
FILE *f = fopen(path, "rb");
|
||||
if(!f) {
|
||||
file->exists = false;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
size_t read = fread(file, sizeof(savefile_t), 1, f);
|
||||
fclose(f);
|
||||
|
||||
if(read != 1) {
|
||||
file->exists = false;
|
||||
errorThrow("Failed to read save data for slot %u", (uint32_t)slot);
|
||||
}
|
||||
|
||||
file->exists = true;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveWriteLinux(const uint8_t slot, const 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
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveDeleteLinux(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
|
||||
);
|
||||
|
||||
if(remove(path) != 0 && errno != ENOENT) {
|
||||
errorThrow("Failed to delete save file for slot %u", (uint32_t)slot);
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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/savefile.h"
|
||||
|
||||
#define SAVE_LINUX_PATH_MAX FILENAME_MAX
|
||||
#define SAVE_LINUX_FILE_FORMAT "%s/save_%u.dat"
|
||||
|
||||
#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.
|
||||
*
|
||||
* @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 file from disk for the given slot.
|
||||
*
|
||||
* @param slot The save slot index.
|
||||
* @param file Output save file data.
|
||||
* @return An error code if the load fails.
|
||||
*/
|
||||
errorret_t saveLoadLinux(const uint8_t slot, savefile_t *file);
|
||||
|
||||
/**
|
||||
* Writes a save file to disk for the given slot.
|
||||
*
|
||||
* @param slot The save slot index.
|
||||
* @param file Save file data to write.
|
||||
* @return An error code if the write fails.
|
||||
*/
|
||||
errorret_t saveWriteLinux(const uint8_t slot, const savefile_t *file);
|
||||
|
||||
/**
|
||||
* Deletes the save file for the given slot from disk.
|
||||
*
|
||||
* @param slot The save slot index.
|
||||
* @return An error code if the delete fails.
|
||||
*/
|
||||
errorret_t saveDeleteLinux(const uint8_t slot);
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "save/savelinux.h"
|
||||
#include "save/savestreamlinux.h"
|
||||
|
||||
typedef savelinux_t saveplatform_t;
|
||||
typedef savestreamlinux_t saveplatformstream_t;
|
||||
|
||||
#define saveInitPlatform saveInitLinux
|
||||
#define saveDisposePlatform saveDisposeLinux
|
||||
#define saveDeletePlatform saveDeleteLinux
|
||||
|
||||
#define saveStreamOpenReadPlatform(stream, slot) \
|
||||
saveStreamOpenReadLinux(&(stream)->platform, &(stream)->found, slot)
|
||||
#define saveStreamOpenWritePlatform(stream, slot) \
|
||||
saveStreamOpenWriteLinux(&(stream)->platform, slot)
|
||||
#define saveStreamClosePlatform(stream) \
|
||||
saveStreamCloseLinux(&(stream)->platform)
|
||||
#define saveStreamReadBytesPlatform(stream, buf, len) \
|
||||
saveStreamReadBytesLinux(&(stream)->platform, buf, len)
|
||||
#define saveStreamWriteBytesPlatform(stream, buf, len) \
|
||||
saveStreamWriteBytesLinux(&(stream)->platform, buf, len)
|
||||
#define saveStreamSeekPlatform(stream, pos) \
|
||||
saveStreamSeekLinux(&(stream)->platform, pos)
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "save/save.h"
|
||||
#include "save/savestreamlinux.h"
|
||||
#include "util/string.h"
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
static void _saveStreamGetPath(
|
||||
char_t *out, const size_t max, const uint8_t slot
|
||||
) {
|
||||
snprintf(
|
||||
out, max, SAVE_LINUX_FILE_FORMAT,
|
||||
SAVE.platform.savePath, (uint32_t)slot
|
||||
);
|
||||
}
|
||||
|
||||
errorret_t saveStreamOpenReadLinux(
|
||||
savestreamlinux_t *p, bool_t *found, const uint8_t slot
|
||||
) {
|
||||
char_t path[SAVE_LINUX_PATH_MAX];
|
||||
_saveStreamGetPath(path, SAVE_LINUX_PATH_MAX, slot);
|
||||
|
||||
p->file = fopen(path, "rb");
|
||||
*found = (p->file != NULL);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveStreamOpenWriteLinux(savestreamlinux_t *p, const uint8_t slot) {
|
||||
char_t path[SAVE_LINUX_PATH_MAX];
|
||||
_saveStreamGetPath(path, SAVE_LINUX_PATH_MAX, slot);
|
||||
|
||||
p->file = fopen(path, "wb");
|
||||
if(!p->file) {
|
||||
errorThrow("Failed to open save file for writing: slot %u", (uint32_t)slot);
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void saveStreamCloseLinux(savestreamlinux_t *p) {
|
||||
if(p->file) {
|
||||
fclose(p->file);
|
||||
p->file = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
errorret_t saveStreamReadBytesLinux(
|
||||
savestreamlinux_t *p, void *buf, const size_t len
|
||||
) {
|
||||
if(fread(buf, 1, len, p->file) != len) {
|
||||
errorThrow("Unexpected end of save file");
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveStreamWriteBytesLinux(
|
||||
savestreamlinux_t *p, const void *buf, const size_t len
|
||||
) {
|
||||
if(fwrite(buf, 1, len, p->file) != len) {
|
||||
errorThrow("Failed to write save data");
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveStreamSeekLinux(savestreamlinux_t *p, const size_t pos) {
|
||||
if(fseek(p->file, (long)pos, SEEK_SET) != 0) {
|
||||
errorThrow("Failed to seek in save file");
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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 <stdio.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct {
|
||||
FILE *file;
|
||||
} savestreamlinux_t;
|
||||
|
||||
/**
|
||||
* Opens a save slot 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 saveStreamOpenReadLinux(
|
||||
savestreamlinux_t *p, bool_t *found, const uint8_t slot
|
||||
);
|
||||
|
||||
/**
|
||||
* Opens a save slot file for writing, creating or truncating it.
|
||||
*
|
||||
* @param p Stream to initialize.
|
||||
* @param slot Save slot index.
|
||||
* @return An error if the file cannot be opened for writing.
|
||||
*/
|
||||
errorret_t saveStreamOpenWriteLinux(
|
||||
savestreamlinux_t *p, const uint8_t slot
|
||||
);
|
||||
|
||||
/**
|
||||
* Closes the file handle held by the stream.
|
||||
*
|
||||
* @param p Stream to close.
|
||||
*/
|
||||
void saveStreamCloseLinux(savestreamlinux_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 saveStreamReadBytesLinux(
|
||||
savestreamlinux_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 saveStreamWriteBytesLinux(
|
||||
savestreamlinux_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 saveStreamSeekLinux(savestreamlinux_t *p, const size_t pos);
|
||||
@@ -19,5 +19,6 @@ add_subdirectory(asset)
|
||||
add_subdirectory(input)
|
||||
add_subdirectory(log)
|
||||
add_subdirectory(network)
|
||||
add_subdirectory(save)
|
||||
add_subdirectory(system)
|
||||
add_subdirectory(time)
|
||||
@@ -0,0 +1,11 @@
|
||||
# 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
|
||||
)
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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 saveDeletePlatform saveDeletePSP
|
||||
|
||||
#define saveStreamOpenReadPlatform(stream, slot) \
|
||||
saveStreamOpenReadPSP(&(stream)->platform, &(stream)->found, slot)
|
||||
#define saveStreamOpenWritePlatform(stream, slot) \
|
||||
saveStreamOpenWritePSP(&(stream)->platform, slot)
|
||||
#define saveStreamClosePlatform(stream) \
|
||||
saveStreamClosePSP(&(stream)->platform)
|
||||
#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)
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "save/save.h"
|
||||
|
||||
errorret_t saveInitPSP(void) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveDisposePSP(void) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveLoadPSP(const uint8_t slot, savefile_t *file) {
|
||||
char_t path[SAVE_PSP_PATH_MAX];
|
||||
snprintf(path, SAVE_PSP_PATH_MAX, SAVE_PSP_FILE_FORMAT,
|
||||
SAVE_PSP_TITLE_ID, (uint32_t)slot
|
||||
);
|
||||
|
||||
SceUID fd = sceIoOpen(path, PSP_O_RDONLY, 0);
|
||||
if(fd < 0) {
|
||||
file->exists = false;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
int32_t read = sceIoRead(fd, file, sizeof(savefile_t));
|
||||
sceIoClose(fd);
|
||||
|
||||
if(read != (int32_t)sizeof(savefile_t)) {
|
||||
file->exists = false;
|
||||
errorThrow("Failed to read save data for slot %u", (uint32_t)slot);
|
||||
}
|
||||
|
||||
file->exists = true;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveWritePSP(const uint8_t slot, const savefile_t *file) {
|
||||
char_t dir[SAVE_PSP_PATH_MAX];
|
||||
snprintf(dir, SAVE_PSP_PATH_MAX, SAVE_PSP_DIR_FORMAT,
|
||||
SAVE_PSP_TITLE_ID, (uint32_t)slot
|
||||
);
|
||||
sceIoMkdir(dir, 0777);
|
||||
|
||||
char_t path[SAVE_PSP_PATH_MAX];
|
||||
snprintf(path, SAVE_PSP_PATH_MAX, SAVE_PSP_FILE_FORMAT,
|
||||
SAVE_PSP_TITLE_ID, (uint32_t)slot
|
||||
);
|
||||
|
||||
SceUID fd = sceIoOpen(path, PSP_O_WRONLY | PSP_O_CREAT | PSP_O_TRUNC, 0777);
|
||||
if(fd < 0) {
|
||||
errorThrow("Failed to open save file for writing: slot %u", (uint32_t)slot);
|
||||
}
|
||||
|
||||
int32_t written = sceIoWrite(fd, file, sizeof(savefile_t));
|
||||
sceIoClose(fd);
|
||||
|
||||
if(written != (int32_t)sizeof(savefile_t)) {
|
||||
errorThrow("Failed to write save data for slot %u", (uint32_t)slot);
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveDeletePSP(const uint8_t slot) {
|
||||
char_t path[SAVE_PSP_PATH_MAX];
|
||||
snprintf(path, SAVE_PSP_PATH_MAX, SAVE_PSP_FILE_FORMAT,
|
||||
SAVE_PSP_TITLE_ID, (uint32_t)slot
|
||||
);
|
||||
|
||||
int32_t result = sceIoRemove(path);
|
||||
if(result < 0 && result != (int32_t)0x80010002) {
|
||||
errorThrow("Failed to delete save file for slot %u", (uint32_t)slot);
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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/savefile.h"
|
||||
#include <pspiofilemgr.h>
|
||||
|
||||
#define SAVE_PSP_PATH_MAX 256
|
||||
#define SAVE_PSP_FILE_FORMAT "ms0:/PSP/SAVEDATA/%s%02u/save.dat"
|
||||
#define SAVE_PSP_DIR_FORMAT "ms0:/PSP/SAVEDATA/%s%02u"
|
||||
|
||||
#ifndef SAVE_PSP_TITLE_ID
|
||||
#define SAVE_PSP_TITLE_ID "DUSK00001"
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
uint8_t unused;
|
||||
} savepsp_t;
|
||||
|
||||
/**
|
||||
* Initializes the save system on PSP.
|
||||
*
|
||||
* @return An error code if initialization fails.
|
||||
*/
|
||||
errorret_t saveInitPSP(void);
|
||||
|
||||
/**
|
||||
* Disposes of the save system on PSP.
|
||||
*
|
||||
* @return An error code if disposal fails.
|
||||
*/
|
||||
errorret_t saveDisposePSP(void);
|
||||
|
||||
/**
|
||||
* Loads a save file from PSP save data for the given slot.
|
||||
*
|
||||
* @param slot The save slot index.
|
||||
* @param file Output save file data.
|
||||
* @return An error code if the load fails.
|
||||
*/
|
||||
errorret_t saveLoadPSP(const uint8_t slot, savefile_t *file);
|
||||
|
||||
/**
|
||||
* Writes a save file to PSP save data for the given slot.
|
||||
*
|
||||
* @param slot The save slot index.
|
||||
* @param file Save file data to write.
|
||||
* @return An error code if the write fails.
|
||||
*/
|
||||
errorret_t saveWritePSP(const uint8_t slot, const savefile_t *file);
|
||||
|
||||
/**
|
||||
* Deletes the save file for the given slot from PSP save data.
|
||||
*
|
||||
* @param slot The save slot index.
|
||||
* @return An error code if the delete fails.
|
||||
*/
|
||||
errorret_t saveDeletePSP(const uint8_t slot);
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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"
|
||||
|
||||
errorret_t saveStreamOpenReadPSP(
|
||||
savestreampsp_t *p, bool_t *found, const uint8_t slot
|
||||
) {
|
||||
char_t path[SAVE_PSP_PATH_MAX];
|
||||
snprintf(path, SAVE_PSP_PATH_MAX, SAVE_PSP_FILE_FORMAT,
|
||||
SAVE_PSP_TITLE_ID, (uint32_t)slot
|
||||
);
|
||||
|
||||
p->fd = sceIoOpen(path, PSP_O_RDONLY, 0);
|
||||
*found = (p->fd >= 0);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveStreamOpenWritePSP(savestreampsp_t *p, const uint8_t slot) {
|
||||
char_t dir[SAVE_PSP_PATH_MAX];
|
||||
snprintf(dir, SAVE_PSP_PATH_MAX, SAVE_PSP_DIR_FORMAT,
|
||||
SAVE_PSP_TITLE_ID, (uint32_t)slot
|
||||
);
|
||||
sceIoMkdir(dir, 0777);
|
||||
|
||||
char_t path[SAVE_PSP_PATH_MAX];
|
||||
snprintf(path, SAVE_PSP_PATH_MAX, SAVE_PSP_FILE_FORMAT,
|
||||
SAVE_PSP_TITLE_ID, (uint32_t)slot
|
||||
);
|
||||
|
||||
p->fd = sceIoOpen(path, PSP_O_WRONLY | PSP_O_CREAT | PSP_O_TRUNC, 0777);
|
||||
if(p->fd < 0) {
|
||||
errorThrow(
|
||||
"Failed to open PSP save file for writing: slot %u", (uint32_t)slot
|
||||
);
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void saveStreamClosePSP(savestreampsp_t *p) {
|
||||
if(p->fd >= 0) {
|
||||
sceIoClose(p->fd);
|
||||
p->fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
errorret_t saveStreamReadBytesPSP(
|
||||
savestreampsp_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 PSP save file");
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveStreamWriteBytesPSP(
|
||||
savestreampsp_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 PSP save data");
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t saveStreamSeekPSP(savestreampsp_t *p, const size_t pos) {
|
||||
if(sceIoLseek(p->fd, (SceOff)pos, PSP_SEEK_SET) < 0) {
|
||||
errorThrow("Failed to seek in PSP save file");
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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 <pspiofilemgr.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct {
|
||||
SceUID fd;
|
||||
} savestreampsp_t;
|
||||
|
||||
/**
|
||||
* Opens a PSP 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 saveStreamOpenReadPSP(
|
||||
savestreampsp_t *p, bool_t *found, const uint8_t slot
|
||||
);
|
||||
|
||||
/**
|
||||
* Opens a PSP 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 saveStreamOpenWritePSP(savestreampsp_t *p, const uint8_t slot);
|
||||
|
||||
/**
|
||||
* Closes the file descriptor held by the stream.
|
||||
*
|
||||
* @param p Stream to close.
|
||||
*/
|
||||
void saveStreamClosePSP(savestreampsp_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 saveStreamReadBytesPSP(
|
||||
savestreampsp_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 saveStreamWriteBytesPSP(
|
||||
savestreampsp_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 saveStreamSeekPSP(savestreampsp_t *p, const size_t pos);
|
||||
@@ -18,3 +18,4 @@ target_sources(${DUSK_BINARY_TARGET_NAME}
|
||||
add_subdirectory(asset)
|
||||
add_subdirectory(input)
|
||||
add_subdirectory(log)
|
||||
add_subdirectory(save)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# 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
|
||||
)
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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)
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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 <psp2/io/fcntl.h>
|
||||
#include <psp2/io/stat.h>
|
||||
#include <stddef.h>
|
||||
|
||||
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);
|
||||
Reference in New Issue
Block a user