Network conn and disconn

This commit is contained in:
2026-07-16 19:32:44 -05:00
parent d4a17bd98d
commit 2ffc65b1b1
9 changed files with 369 additions and 612 deletions
+77 -1
View File
@@ -17,6 +17,7 @@
#include "ui/ui.h"
#include "assert/assert.h"
#include "network/network.h"
#include "network/http/networkhttprequest.h"
#include "system/system.h"
#include "console/console.h"
#include "save/save.h"
@@ -45,6 +46,13 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
errorChain(networkInit());
errorChain(sceneInit());
networkRequestConnection(
engineNetworkOnConnected,
engineNetworkOnFailed,
engineNetworkOnDisconnect,
NULL
);
consolePrint("Engine initialized");
#ifdef DUSK_ASSERTIONS_FAKED
@@ -55,7 +63,6 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
sceneSet(SCENE_TYPE_OVERWORLD);
errorOk();
}
@@ -63,6 +70,15 @@ errorret_t engineUpdate(void) {
// Order here is important.
errorChain(networkUpdate());
timeUpdate();
if(
ENGINE.networkDisconnectTestPending &&
TIME.time >= ENGINE.networkDisconnectTestAt
) {
ENGINE.networkDisconnectTestPending = false;
networkRequestDisconnection(engineNetworkDisconnectTestOnComplete, NULL);
}
inputUpdate();
consoleUpdate();
errorChain(rpgUpdate());
@@ -93,3 +109,63 @@ errorret_t engineDispose(void) {
errorOk();
}
void engineHttpTestOnComplete(void *params, void *user) {
networkhttprequest_t *request = (networkhttprequest_t *)params;
const networkhttpresponse_t *response = &request->response;
consolePrint("frogfind.com response status: %d", response->status);
for(uint32_t i = 0; i < response->headers.count; i++) {
const networkhttpheader_t *header = &response->headers.headers[i];
consolePrint("frogfind.com header: %s: %s", header->name, header->value);
}
consolePrint("frogfind.com body length: %d", (int32_t)response->bodyLength);
const size_t chunkMax = CONSOLE_LINE_MAX - 32;
for(size_t offset = 0; offset < response->bodyLength; offset += chunkMax) {
size_t chunkLength = response->bodyLength - offset;
if(chunkLength > chunkMax) chunkLength = chunkMax;
consolePrint("frogfind.com body: %.*s",
(int32_t)chunkLength, (const char_t *)response->body + offset);
}
}
void engineHttpTestOnError(void *params, void *user) {
consolePrint("frogfind.com request failed");
}
void engineNetworkOnConnected(void *user) {
consolePrint("Network connected");
errorret_t ret = networkHttpRequest(
NETWORK_HTTP_METHOD_GET,
"http://frogfind.com",
NULL, 0,
NULL, 0,
NULL, 0,
engineHttpTestOnComplete,
engineHttpTestOnError,
NULL
);
errorCatch(errorPrint(ret));
ENGINE.networkDisconnectTestAt = TIME.time + 10.0f;
ENGINE.networkDisconnectTestPending = true;
}
void engineNetworkOnFailed(errorret_t error, void *user) {
consolePrint("Network connection failed");
errorCatch(errorPrint(error));
}
void engineNetworkOnDisconnect(errorret_t error, void *user) {
consolePrint("Network disconnected");
errorCatch(errorPrint(error));
}
void engineNetworkDisconnectTestOnComplete(void *user) {
consolePrint("Network disconnect test complete");
}
+52
View File
@@ -16,6 +16,10 @@ typedef struct {
int32_t argc;
const char_t **argv;
const char_t *version;
// Test: disconnects the network 10 seconds after it connects.
bool_t networkDisconnectTestPending;
float_t networkDisconnectTestAt;
} engine_t;
extern engine_t ENGINE;
@@ -38,3 +42,51 @@ errorret_t engineUpdate(void);
*/
errorret_t engineDispose(void);
/**
* Logs the response status, headers and body of the frogfind.com test
* request to the console.
*
* @param params The completed networkhttprequest_t.
* @param user Unused.
*/
void engineHttpTestOnComplete(void *params, void *user);
/**
* Logs that the frogfind.com test request failed.
*
* @param params The failed networkhttprequest_t.
* @param user Unused.
*/
void engineHttpTestOnError(void *params, void *user);
/**
* Fires the frogfind.com test request once the network connection is
* up (some platforms, such as PSP, only bring the network stack up
* asynchronously after networkRequestConnection is called).
*
* @param user Unused.
*/
void engineNetworkOnConnected(void *user);
/**
* Logs that the network connection could not be established.
*
* @param error The error describing why the connection failed.
* @param user Unused.
*/
void engineNetworkOnFailed(errorret_t error, void *user);
/**
* Logs that the network connection was lost after having connected.
*
* @param error The error describing why the connection was lost.
* @param user Unused.
*/
void engineNetworkOnDisconnect(errorret_t error, void *user);
/**
* Logs that the 10-second test disconnect completed.
*
* @param user Unused.
*/
void engineNetworkDisconnectTestOnComplete(void *user);
+38
View File
@@ -12,6 +12,7 @@
#include "util/math.h"
#include "time/time.h"
#include "event/event.h"
#include "save/save.h"
input_t INPUT;
@@ -46,6 +47,43 @@ errorret_t inputInit(void) {
errorOk();
}
errorret_t inputSaveSettings(void) {
yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL);
yyjson_mut_val *root = yyjson_mut_obj(doc);
yyjson_mut_doc_set_root(doc, root);
yyjson_mut_val *settings = yyjson_mut_obj(doc);
yyjson_mut_obj_add_val(doc, root, "settings", settings);
yyjson_mut_val *inputSettings = yyjson_mut_obj(doc);
yyjson_mut_obj_add_val(doc, settings, "input", inputSettings);
yyjson_mut_obj_add_real(doc, inputSettings, "deadzone", INPUT.deadzone);
errorret_t ret = saveWrite(SAVE_SETTINGS_SLOT, doc);
yyjson_mut_doc_free(doc);
if(errorIsNotOk(ret)) return ret;
errorOk();
}
errorret_t inputLoadSettings(void) {
yyjson_doc *doc = NULL;
errorChain(saveLoad(SAVE_SETTINGS_SLOT, &doc));
if(!doc) errorOk();
yyjson_val *root = yyjson_doc_get_root(doc);
yyjson_val *settings = yyjson_obj_get(root, "settings");
yyjson_val *inputSettings = yyjson_obj_get(settings, "input");
yyjson_val *deadzone = yyjson_obj_get(inputSettings, "deadzone");
if(deadzone) INPUT.deadzone = (float_t)yyjson_get_num(deadzone);
yyjson_doc_free(doc);
errorOk();
}
void inputUpdate(void) {
#ifdef inputUpdatePlatform
inputUpdatePlatform();
+17
View File
@@ -37,6 +37,23 @@ errorret_t inputInit(void);
*/
void inputUpdate(void);
/**
* Persists the current input settings (currently just the gamepad
* deadzone) to the save system as JSON, under settings.input.
*
* @return An error code if serialization or the write fails.
*/
errorret_t inputSaveSettings(void);
/**
* Loads input settings (currently just the gamepad deadzone) from the
* save system's JSON payload under settings.input and applies them to
* INPUT. Leaves INPUT unchanged if no settings file exists yet.
*
* @return An error code if the load or parse fails.
*/
errorret_t inputLoadSettings(void);
/**
* Gets the current value of a specific input action.
*
+10 -10
View File
@@ -29,8 +29,11 @@ errorret_t saveDispose(void) {
errorOk();
}
errorret_t saveLoad(const uint8_t slot) {
errorret_t saveLoad(const uint8_t slot, yyjson_doc **outDoc) {
assertTrue(slot < SAVE_FILE_COUNT_MAX, "slot exceeds SAVE_FILE_COUNT_MAX");
assertNotNull(outDoc, "outDoc must not be NULL");
*outDoc = NULL;
savefile_t *file = &SAVE.files[slot];
file->exists = false;
@@ -44,7 +47,8 @@ errorret_t saveLoad(const uint8_t slot) {
if(!stream.found) errorOk();
errorret_t ret = saveFileLoad(&stream, file);
yyjson_doc *doc = NULL;
errorret_t ret = saveFileLoad(&stream, file, &doc);
#ifdef saveStreamClosePlatform
saveStreamClosePlatform(&stream);
@@ -52,14 +56,14 @@ errorret_t saveLoad(const uint8_t slot) {
if(errorIsNotOk(ret)) return ret;
errorChain(saveStreamVerifyChecksumImpl(&stream, slot));
*outDoc = doc;
file->exists = true;
errorOk();
}
errorret_t saveWrite(const uint8_t slot) {
errorret_t saveWrite(const uint8_t slot, yyjson_mut_doc *doc) {
assertTrue(slot < SAVE_FILE_COUNT_MAX, "slot exceeds SAVE_FILE_COUNT_MAX");
assertNotNull(doc, "doc must not be NULL");
savefile_t *file = &SAVE.files[slot];
@@ -70,11 +74,7 @@ errorret_t saveWrite(const uint8_t slot) {
errorChain(saveStreamOpenWritePlatform(&stream, slot));
#endif
errorret_t ret = saveFileWrite(&stream, file);
if(errorIsOk(ret)) {
ret = saveStreamFinalizeWriteImpl(&stream);
}
errorret_t ret = saveFileWrite(&stream, file, doc);
#ifdef saveStreamClosePlatform
saveStreamClosePlatform(&stream);
+21 -9
View File
@@ -9,9 +9,10 @@
#include "error/error.h"
#include "savefile.h"
#include "save/saveplatform.h"
#include "yyjson.h"
typedef struct {
/** Per-slot save file data; indexed 0 to SAVE_FILE_COUNT_MAX - 1. */
/** Per-slot save file metadata; 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;
@@ -34,20 +35,30 @@ errorret_t saveInit(void);
errorret_t saveDispose(void);
/**
* Loads the save file for a given slot from persistent storage.
* Loads the save file for a given slot from persistent storage and parses
* its JSON payload. The payload is read and parsed fresh on every call and
* is not retained anywhere else - the caller owns the returned document
* and must release it with yyjson_doc_free() once done reading from it.
*
* @param slot The save slot index (0 to SAVE_FILE_COUNT_MAX - 1).
* @return An error code if the load fails.
* @param slot The save slot index (0 to SAVE_FILE_COUNT_MAX - 1).
* @param outDoc Receives the parsed JSON document, or NULL if the slot has
* no save file on disk.
* @return An error code if the load or checksum validation fails.
*/
errorret_t saveLoad(const uint8_t slot);
errorret_t saveLoad(const uint8_t slot, yyjson_doc **outDoc);
/**
* Writes the save file for a given slot to persistent storage.
* Serializes the given JSON document and writes it to persistent storage
* for a given slot, alongside its byte size and CRC32 checksum. The
* document is serialized to bytes fresh on every call and is not retained
* anywhere else - the caller keeps ownership of doc and must dispose of it
* with yyjson_mut_doc_free() once done.
*
* @param slot The save slot index (0 to SAVE_FILE_COUNT_MAX - 1).
* @return An error code if the write fails.
* @param doc The JSON document to serialize and write.
* @return An error code if serialization or the write fails.
*/
errorret_t saveWrite(const uint8_t slot);
errorret_t saveWrite(const uint8_t slot, yyjson_mut_doc *doc);
/**
* Deletes the save file for a given slot from persistent storage.
@@ -66,7 +77,8 @@ errorret_t saveDelete(const uint8_t slot);
bool_t saveExists(const uint8_t slot);
/**
* Gets a pointer to the save file data for a given slot.
* Gets a pointer to the save file metadata for a given slot. This does
* not include the JSON payload itself - see saveLoad().
*
* @param slot The save slot index (0 to SAVE_FILE_COUNT_MAX - 1).
* @return A pointer to the savefile_t for the given slot.
+10
View File
@@ -20,11 +20,21 @@
/** Maximum number of independent save slots supported. */
#define SAVE_FILE_COUNT_MAX 3
/**
* Slot used to persist game settings. Shares the save slot range with
* game saves for now, until settings get a dedicated file of their own.
*/
#define SAVE_SETTINGS_SLOT 0
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;
/** Byte length of the serialized JSON payload. */
uint32_t size;
/** CRC32 checksum of the serialized JSON payload, for validation. */
uint32_t checksum;
/** Runtime flag - true if this slot was successfully loaded or written. */
bool_t exists;
} savefile_t;
+75 -251
View File
@@ -8,10 +8,10 @@
#include "save/savestream.h"
#include "util/crypt.h"
#include "util/endian.h"
#include "util/string.h"
#include "util/memory.h"
#include <stdlib.h>
errorret_t saveStreamReadBytesRawImpl(
errorret_t saveStreamReadBytesImpl(
savestream_t *stream, void *buf, const size_t len
) {
#ifdef saveStreamReadBytesPlatform
@@ -20,7 +20,7 @@ errorret_t saveStreamReadBytesRawImpl(
errorOk();
}
errorret_t saveStreamWriteBytesRawImpl(
errorret_t saveStreamWriteBytesImpl(
savestream_t *stream, const void *buf, const size_t len
) {
#ifdef saveStreamWriteBytesPlatform
@@ -29,51 +29,10 @@ errorret_t saveStreamWriteBytesRawImpl(
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));
errorChain(saveStreamReadBytesImpl(stream, header, SAVE_FILE_HEADER_SIZE));
if(
header[0] != SAVE_FILE_HEADER[0] ||
@@ -83,25 +42,13 @@ errorret_t saveStreamReadHeaderImpl(
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();
errorChain(saveStreamWriteBytesImpl(stream, header, SAVE_FILE_HEADER_SIZE));
errorOk();
}
@@ -120,100 +67,14 @@ errorret_t saveStreamWriteVersionImpl(
errorOk();
}
errorret_t saveStreamReadBoolImpl(savestream_t *stream, bool_t *out) {
uint8_t raw;
errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint8_t)));
*out = (bool_t)(raw != 0);
errorOk();
}
errorret_t saveStreamWriteBoolImpl(savestream_t *stream, const bool_t *input) {
uint8_t raw = *input ? 1 : 0;
errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint8_t)));
errorOk();
}
errorret_t saveStreamReadInt8Impl(savestream_t *stream, int8_t *out) {
errorChain(saveStreamReadBytesImpl(stream, out, sizeof(int8_t)));
errorOk();
}
errorret_t saveStreamWriteInt8Impl(savestream_t *stream, const int8_t *input) {
errorChain(saveStreamWriteBytesImpl(stream, input, sizeof(int8_t)));
errorOk();
}
errorret_t saveStreamReadUInt8Impl(savestream_t *stream, uint8_t *out) {
errorChain(saveStreamReadBytesImpl(stream, out, sizeof(uint8_t)));
errorOk();
}
errorret_t saveStreamWriteUInt8Impl(
savestream_t *stream, const uint8_t *input
) {
errorChain(saveStreamWriteBytesImpl(stream, input, sizeof(uint8_t)));
errorOk();
}
errorret_t saveStreamReadInt16Impl(savestream_t *stream, int16_t *out) {
uint16_t raw;
errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint16_t)));
uint16_t host = endianLittleToHost16(raw);
memoryCopy(out, &host, sizeof(int16_t));
errorOk();
}
errorret_t saveStreamWriteInt16Impl(
savestream_t *stream, const int16_t *input
) {
uint16_t raw;
memoryCopy(&raw, input, sizeof(int16_t));
raw = endianLittleToHost16(raw);
errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint16_t)));
errorOk();
}
errorret_t saveStreamReadUInt16Impl(savestream_t *stream, uint16_t *out) {
uint16_t raw;
errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint16_t)));
*out = endianLittleToHost16(raw);
errorOk();
}
errorret_t saveStreamWriteUInt16Impl(
savestream_t *stream, const uint16_t *input
) {
uint16_t raw = endianLittleToHost16(*input);
errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint16_t)));
errorOk();
}
errorret_t saveStreamReadInt32Impl(savestream_t *stream, int32_t *out) {
uint32_t raw;
errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint32_t)));
uint32_t host = endianLittleToHost32(raw);
memoryCopy(out, &host, sizeof(int32_t));
errorOk();
}
errorret_t saveStreamWriteInt32Impl(
savestream_t *stream, const int32_t *input
) {
uint32_t raw;
memoryCopy(&raw, input, sizeof(int32_t));
raw = endianLittleToHost32(raw);
errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint32_t)));
errorOk();
}
errorret_t saveStreamReadUInt32Impl(savestream_t *stream, uint32_t *out) {
errorret_t saveStreamReadSizeImpl(savestream_t *stream, uint32_t *out) {
uint32_t raw;
errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint32_t)));
*out = endianLittleToHost32(raw);
errorOk();
}
errorret_t saveStreamWriteUInt32Impl(
errorret_t saveStreamWriteSizeImpl(
savestream_t *stream, const uint32_t *input
) {
uint32_t raw = endianLittleToHost32(*input);
@@ -221,120 +82,83 @@ errorret_t saveStreamWriteUInt32Impl(
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));
errorret_t saveStreamReadChecksumImpl(savestream_t *stream, uint32_t *out) {
uint32_t raw;
errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint32_t)));
*out = endianLittleToHost32(raw);
errorOk();
}
errorret_t saveStreamWriteInt64Impl(
savestream_t *stream, const int64_t *input
errorret_t saveStreamWriteChecksumImpl(
savestream_t *stream, const uint32_t *input
) {
uint64_t raw;
memoryCopy(&raw, input, sizeof(int64_t));
raw = endianLittleToHost64(raw);
errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint64_t)));
uint32_t raw = endianLittleToHost32(*input);
errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint32_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
errorret_t saveFileWritePayloadImpl(
savestream_t *stream,
const savefile_t *file,
const char_t *json,
const size_t len
) {
uint64_t raw = endianLittleToHost64(*input);
errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint64_t)));
errorOk();
}
errorret_t saveStreamReadFloatImpl(savestream_t *stream, float_t *out) {
float_t raw;
errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(float_t)));
*out = endianLittleToHostFloat(raw);
errorOk();
}
errorret_t saveStreamWriteFloatImpl(
savestream_t *stream, const float_t *input
) {
float_t raw = endianLittleToHostFloat(*input);
errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(float_t)));
errorOk();
}
errorret_t saveStreamReadStringImpl(
savestream_t *stream, char_t *out, const size_t maxLen
) {
for(size_t i = 0; i < maxLen; i++) {
errorChain(saveStreamReadBytesImpl(stream, &out[i], sizeof(char_t)));
if(out[i] == '\0') errorOk();
}
out[maxLen - 1] = '\0';
errorOk();
}
errorret_t saveStreamWriteStringImpl(
savestream_t *stream, const char_t *input, const size_t maxLen
) {
size_t len = strlen(input);
if(len >= maxLen) len = maxLen - 1;
errorChain(saveStreamWriteBytesImpl(stream, input, len + 1));
errorOk();
}
errorret_t saveStreamReadDateImpl(savestream_t *stream, dusktimeepoch_t *out) {
uint64_t raw;
errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint64_t)));
raw = endianLittleToHost64(raw);
memoryCopy(&out->time, &raw, sizeof(double_t));
errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint64_t)));
raw = endianLittleToHost64(raw);
memoryCopy(&out->timeZone, &raw, sizeof(double_t));
errorChain(saveStreamReadBytesImpl(stream, &raw, sizeof(uint64_t)));
raw = endianLittleToHost64(raw);
memoryCopy(&out->offsetTime, &raw, sizeof(double_t));
errorOk();
}
errorret_t saveStreamWriteDateImpl(
savestream_t *stream, const dusktimeepoch_t *input
) {
uint64_t raw;
memoryCopy(&raw, &input->time, sizeof(double_t));
raw = endianLittleToHost64(raw);
errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint64_t)));
memoryCopy(&raw, &input->timeZone, sizeof(double_t));
raw = endianLittleToHost64(raw);
errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint64_t)));
memoryCopy(&raw, &input->offsetTime, sizeof(double_t));
raw = endianLittleToHost64(raw);
errorChain(saveStreamWriteBytesImpl(stream, &raw, sizeof(uint64_t)));
errorOk();
}
errorret_t saveFileLoad(savestream_t *stream, savefile_t *file) {
saveFileReadHeader(stream, file->header);
saveFileReadVersion(stream, &file->version);
errorOk();
}
errorret_t saveFileWrite(savestream_t *stream, savefile_t *file) {
saveFileWriteHeader(stream, file->header);
saveFileWriteVersion(stream, &file->version);
saveFileWriteSize(stream, &file->size);
saveFileWriteChecksum(stream, &file->checksum);
errorChain(saveStreamWriteBytesImpl(stream, json, len));
errorOk();
}
errorret_t saveFileLoad(
savestream_t *stream, savefile_t *file, yyjson_doc **outDoc
) {
saveFileReadHeader(stream, file->header);
saveFileReadVersion(stream, &file->version);
saveFileReadSize(stream, &file->size);
saveFileReadChecksum(stream, &file->checksum);
uint8_t *buf = memoryAllocate(file->size);
errorret_t ret = saveStreamReadBytesImpl(stream, buf, file->size);
if(errorIsNotOk(ret)) {
memoryFree(buf);
return ret;
}
if(cryptCRC32(buf, file->size) != file->checksum) {
memoryFree(buf);
errorThrow("Save file has invalid checksum");
}
yyjson_doc *doc = yyjson_read((const char_t *)buf, file->size, 0);
memoryFree(buf);
if(!doc) errorThrow("Failed to parse save file JSON");
*outDoc = doc;
errorOk();
}
errorret_t saveFileWrite(
savestream_t *stream, savefile_t *file, yyjson_mut_doc *doc
) {
size_t len = 0;
char_t *json = yyjson_mut_write(doc, 0, &len);
if(!json) errorThrow("Failed to serialize save data to JSON");
memoryCopy(file->header, SAVE_FILE_HEADER, SAVE_FILE_HEADER_SIZE);
file->version = SAVE_FILE_VERSION;
file->size = (uint32_t)len;
file->checksum = cryptCRC32(json, len);
errorret_t ret = saveFileWritePayloadImpl(stream, file, json, len);
// json comes from yyjson's own allocator (malloc), not the project
// allocator, so it must be released with free() rather than memoryFree().
free(json);
if(errorIsNotOk(ret)) return ret;
errorOk();
}
+69 -341
View File
@@ -9,41 +9,17 @@
#include "error/error.h"
#include "savefile.h"
#include "save/saveplatform.h"
#include "time/timeepoch.h"
#include "yyjson.h"
typedef struct {
/** True if the platform found and opened an existing save file. */
bool_t found;
uint32_t checksum;
uint32_t expectedChecksum;
/** Platform-specific stream handle (file handle, card slot, etc.). */
saveplatformstream_t platform;
} savestream_t;
/**
* Reads bytes from the platform stream without updating the CRC.
*
* @param stream Active stream.
* @param buf Destination buffer.
* @param len Number of bytes to read.
* @return An error if the read fails.
*/
errorret_t saveStreamReadBytesRawImpl(
savestream_t *stream, void *buf, const size_t len
);
/**
* Writes bytes to the platform stream without updating the CRC.
*
* @param stream Active stream.
* @param buf Source buffer.
* @param len Number of bytes to write.
* @return An error if the write fails.
*/
errorret_t saveStreamWriteBytesRawImpl(
savestream_t *stream, const void *buf, const size_t len
);
/**
* Reads bytes from the platform stream and accumulates them into the CRC.
* Reads raw bytes from the platform stream.
*
* @param stream Active stream.
* @param buf Destination buffer.
@@ -55,7 +31,7 @@ errorret_t saveStreamReadBytesImpl(
);
/**
* Updates the CRC then writes bytes to the platform stream.
* Writes raw bytes to the platform stream.
*
* @param stream Active stream.
* @param buf Source buffer.
@@ -67,29 +43,7 @@ errorret_t saveStreamWriteBytesImpl(
);
/**
* 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.
* Reads and validates the magic header from the stream.
*
* @param stream Active read stream.
* @param header Buffer of SAVE_FILE_HEADER_SIZE bytes to receive the header.
@@ -100,8 +54,7 @@ errorret_t saveStreamReadHeaderImpl(
);
/**
* Writes the magic header and a zero CRC32 placeholder, then resets the
* running accumulator.
* Writes the magic header to the stream.
*
* @param stream Active write stream.
* @param header Buffer of SAVE_FILE_HEADER_SIZE bytes to write.
@@ -133,268 +86,94 @@ errorret_t saveStreamWriteVersionImpl(
);
/**
* 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.
* Reads a little-endian uint32 JSON payload size 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 saveStreamReadInt16Impl(savestream_t *stream, int16_t *out);
errorret_t saveStreamReadSizeImpl(savestream_t *stream, uint32_t *out);
/**
* Writes a signed 16-bit integer to the stream in little-endian order.
* Writes a uint32 JSON payload size 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 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(
errorret_t saveStreamWriteSizeImpl(
savestream_t *stream, const uint32_t *input
);
/**
* Reads a little-endian signed 64-bit integer from the stream.
* Reads a little-endian uint32 CRC32 checksum 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 saveStreamReadInt64Impl(savestream_t *stream, int64_t *out);
errorret_t saveStreamReadChecksumImpl(savestream_t *stream, uint32_t *out);
/**
* Writes a signed 64-bit integer to the stream in little-endian order.
* Writes a uint32 CRC32 checksum 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 saveStreamWriteInt64Impl(
savestream_t *stream, const int64_t *input
errorret_t saveStreamWriteChecksumImpl(
savestream_t *stream, const uint32_t *input
);
/**
* Reads a little-endian unsigned 64-bit integer from the stream.
*
* @param stream Active read stream.
* @param out Receives the host-order value.
* @return An error if the read fails.
*/
errorret_t saveStreamReadUInt64Impl(savestream_t *stream, uint64_t *out);
/**
* Writes an unsigned 64-bit integer to the stream in little-endian order.
*
* @param stream Active write stream.
* @param input Value to write.
* @return An error if the write fails.
*/
errorret_t saveStreamWriteUInt64Impl(
savestream_t *stream, const uint64_t *input
);
/**
* Reads a little-endian float from the stream.
*
* @param stream Active read stream.
* @param out Receives the host-order value.
* @return An error if the read fails.
*/
errorret_t saveStreamReadFloatImpl(savestream_t *stream, float_t *out);
/**
* Writes a float to the stream in little-endian order.
*
* @param stream Active write stream.
* @param input Value to write.
* @return An error if the write fails.
*/
errorret_t saveStreamWriteFloatImpl(
savestream_t *stream, const float_t *input
);
/**
* Reads a null-terminated string from the stream up to maxLen bytes
* (including the terminator). Always null-terminates the output buffer.
*
* @param stream Active read stream.
* @param out Destination buffer of at least maxLen bytes.
* @param maxLen Maximum bytes to read, including the null terminator.
* @return An error if the read fails.
*/
errorret_t saveStreamReadStringImpl(
savestream_t *stream, char_t *out, const size_t maxLen
);
/**
* Writes a null-terminated string to the stream, truncating to maxLen-1
* characters and always appending a null terminator.
*
* @param stream Active write stream.
* @param input Source string.
* @param maxLen Maximum bytes to write, including the null terminator.
* @return An error if the write fails.
*/
errorret_t saveStreamWriteStringImpl(
savestream_t *stream, const char_t *input, const size_t maxLen
);
/**
* Reads a dusktimeepoch_t as three little-endian 64-bit IEEE 754 doubles
* (time, timeZone, offsetTime).
*
* @param stream Active read stream.
* @param out Receives the epoch value.
* @return An error if the read fails.
*/
errorret_t saveStreamReadDateImpl(
savestream_t *stream, dusktimeepoch_t *out
);
/**
* Writes a dusktimeepoch_t as three little-endian 64-bit IEEE 754 doubles
* (time, timeZone, offsetTime).
*
* @param stream Active write stream.
* @param input Epoch value to write.
* @return An error if the write fails.
*/
errorret_t saveStreamWriteDateImpl(
savestream_t *stream, const dusktimeepoch_t *input
);
/**
* Reads 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.
*/
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.
* Writes the header, version, size and checksum fields already populated
* on file, followed by the raw JSON payload bytes. Split out of
* saveFileWrite so the caller can free its serialized JSON buffer exactly
* once regardless of which step fails.
*
* @param stream Active write stream for this slot.
* @param file Save file struct to serialize.
* @return An error code if writing fails.
* @param file Save file metadata with header/version/size/checksum set.
* @param json Serialized JSON payload bytes.
* @param len Byte length of json.
* @return An error code if any write fails.
*/
errorret_t saveFileWrite(savestream_t *stream, savefile_t *file);
errorret_t saveFileWritePayloadImpl(
savestream_t *stream,
const savefile_t *file,
const char_t *json,
const size_t len
);
/**
* Reads a save slot's header, version and JSON payload from the stream,
* validating the payload against its stored size and CRC32 checksum. The
* JSON is parsed fresh into a new document each call; no payload bytes are
* kept resident beyond this call.
*
* @param stream Active read stream for this slot.
* @param file Save file metadata struct to populate.
* @param outDoc Receives the parsed JSON document, owned by the caller.
* @return An error code if loading, validation or parsing fails.
*/
errorret_t saveFileLoad(
savestream_t *stream, savefile_t *file, yyjson_doc **outDoc
);
/**
* Serializes the given JSON document and writes the header, version,
* size, checksum and payload to the stream. The serialized bytes are not
* kept resident beyond this call.
*
* @param stream Active write stream for this slot.
* @param file Save file metadata struct to populate and serialize.
* @param doc The JSON document to serialize and write.
* @return An error code if serialization or writing fails.
*/
errorret_t saveFileWrite(
savestream_t *stream, savefile_t *file, yyjson_mut_doc *doc
);
#define saveFileReadHeader(stream, header) \
errorChain(saveStreamReadHeaderImpl(stream, header))
@@ -406,63 +185,12 @@ errorret_t saveFileWrite(savestream_t *stream, savefile_t *file);
#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))
#define saveFileReadSize(stream, out) \
errorChain(saveStreamReadSizeImpl(stream, out))
#define saveFileWriteSize(stream, input) \
errorChain(saveStreamWriteSizeImpl(stream, input))
#define saveFileReadChecksum(stream, out) \
errorChain(saveStreamReadChecksumImpl(stream, out))
#define saveFileWriteChecksum(stream, input) \
errorChain(saveStreamWriteChecksumImpl(stream, input))