Split device settings out of the per-slot save file
Deadzone (and future prefs like locale) now live in their own settingsfile_t/settings.c, loaded eagerly at boot and saved immediately on Apply, instead of inside savefile_t - a setting shouldn't reset or diverge just because the player is on a different save slot, and this also fixes settings changes not actually reaching disk until the next full game Save. PSP settings use a new plain sceIo path rather than sceUtilitySavedata, since that dialog would flash its native icon on every settings tweak. GameCube reuses the save system's existing memory card mount rather than mounting it twice (settingsInit() now runs after saveInit()). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
#include "system/system.h"
|
||||
#include "console/console.h"
|
||||
#include "save/save.h"
|
||||
#include "save/settings.h"
|
||||
|
||||
engine_t ENGINE;
|
||||
|
||||
@@ -38,6 +39,10 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
errorChain(inputInit());
|
||||
errorChain(assetInit());
|
||||
errorChain(saveInit());
|
||||
// Must run after saveInit() - on GameCube, settingsInit() reuses the
|
||||
// memory card mount saveInit() already established rather than mounting
|
||||
// it a second time (see settingsInitDolphin()).
|
||||
errorChain(settingsInit());
|
||||
errorChain(localeManagerInit());
|
||||
errorChain(displayInit());
|
||||
errorChain(uiInit());
|
||||
@@ -89,6 +94,7 @@ errorret_t engineDispose(void) {
|
||||
errorChain(uiDispose());
|
||||
consoleDispose();
|
||||
errorChain(displayDispose());
|
||||
errorChain(settingsDispose());
|
||||
errorChain(saveDispose());
|
||||
errorChain(assetDispose());
|
||||
|
||||
|
||||
@@ -8,4 +8,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
save.c
|
||||
savestream.c
|
||||
settings.c
|
||||
settingsstream.c
|
||||
)
|
||||
|
||||
@@ -16,13 +16,6 @@ save_t SAVE;
|
||||
errorret_t saveInit(void) {
|
||||
memoryZero(&SAVE, sizeof(save_t));
|
||||
|
||||
// Establishes the default for a slot that hasn't actually been loaded
|
||||
// from disk yet - saveLoad() overwrites this the moment a real file is
|
||||
// found, so this only matters for a brand new save.
|
||||
for(uint8_t i = 0; i < SAVE_FILE_COUNT_MAX; i++) {
|
||||
SAVE.files[i].deadzone = SAVE_DEADZONE_DEFAULT;
|
||||
}
|
||||
|
||||
#ifdef saveInitPlatform
|
||||
// A missing/unreachable save medium is expected, recoverable state,
|
||||
// not a reason to fail booting the whole game - log it and carry on
|
||||
|
||||
@@ -40,15 +40,6 @@
|
||||
*/
|
||||
#define SAVE_GLOBAL_ITEM_COUNT_MAX 64
|
||||
|
||||
/**
|
||||
* Default gamepad deadzone for a save slot that's never actually been
|
||||
* loaded from disk yet (see saveInit(), which stamps this onto every
|
||||
* slot up front) - defined here, rather than by the input system, since
|
||||
* the save file is now the single source of truth for this value (see
|
||||
* savefile_t.deadzone) - nothing else stores or defaults it.
|
||||
*/
|
||||
#define SAVE_DEADZONE_DEFAULT 0.1f
|
||||
|
||||
/**
|
||||
* Maximum number of story flags the save format can hold - see
|
||||
* rpg/story/storyflag.h. Bounded/fixed here (with real headroom over the
|
||||
@@ -69,12 +60,6 @@ typedef struct {
|
||||
char_t playerName[SAVE_PLAYER_NAME_MAX];
|
||||
/** Per-global-ID "already collected" flags - see globalitemstore.h. */
|
||||
bool_t globalItemCollected[SAVE_GLOBAL_ITEM_COUNT_MAX];
|
||||
/**
|
||||
* User-configured gamepad deadzone (0.0f-1.0f) - the save file is the
|
||||
* only place this lives; read it directly via saveGet(SAVE_ACTIVE_SLOT)
|
||||
* ->deadzone rather than caching it anywhere else.
|
||||
*/
|
||||
float_t deadzone;
|
||||
/**
|
||||
* Story flag values, indexed by storyflag_t - the save file is the only
|
||||
* place these live; read/write via storyFlagGet()/storyFlagSet() (see
|
||||
|
||||
@@ -334,7 +334,6 @@ errorret_t saveFileLoad(savestream_t *stream, savefile_t *file) {
|
||||
for(size_t i = 0; i < SAVE_GLOBAL_ITEM_COUNT_MAX; i++) {
|
||||
saveFileReadBool(stream, &file->globalItemCollected[i]);
|
||||
}
|
||||
saveFileReadFloat(stream, &file->deadzone);
|
||||
for(size_t i = 0; i < SAVE_STORY_FLAG_COUNT_MAX; i++) {
|
||||
saveFileReadUInt8(stream, &file->storyFlags[i]);
|
||||
}
|
||||
@@ -348,7 +347,6 @@ errorret_t saveFileWrite(savestream_t *stream, savefile_t *file) {
|
||||
for(size_t i = 0; i < SAVE_GLOBAL_ITEM_COUNT_MAX; i++) {
|
||||
saveFileWriteBool(stream, &file->globalItemCollected[i]);
|
||||
}
|
||||
saveFileWriteFloat(stream, &file->deadzone);
|
||||
for(size_t i = 0; i < SAVE_STORY_FLAG_COUNT_MAX; i++) {
|
||||
saveFileWriteUInt8(stream, &file->storyFlags[i]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "save/settings.h"
|
||||
#include "save/settingsstream.h"
|
||||
#include "util/memory.h"
|
||||
#include "error/error.h"
|
||||
|
||||
settings_t SETTINGS;
|
||||
|
||||
static errorret_t settingsLoad(void) {
|
||||
settingsstream_t stream;
|
||||
memoryZero(&stream, sizeof(settingsstream_t));
|
||||
|
||||
#ifdef settingsStreamOpenReadPlatform
|
||||
errorret_t openRet = settingsStreamOpenReadPlatform(&stream);
|
||||
SETTINGS.available = errorIsOk(openRet);
|
||||
errorChain(openRet);
|
||||
#endif
|
||||
|
||||
if(!stream.found) errorOk();
|
||||
|
||||
errorret_t ret = settingsFileLoad(&stream, &SETTINGS.file);
|
||||
|
||||
#ifdef settingsStreamClosePlatform
|
||||
settingsStreamClosePlatform(&stream);
|
||||
#endif
|
||||
|
||||
if(errorIsOk(ret)) ret = settingsStreamVerifyChecksumImpl(&stream);
|
||||
SETTINGS.file.exists = errorIsOk(ret);
|
||||
errorChain(ret);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsInit(void) {
|
||||
memoryZero(&SETTINGS, sizeof(settings_t));
|
||||
SETTINGS.file.deadzone = SETTINGS_DEADZONE_DEFAULT;
|
||||
|
||||
#ifdef settingsInitPlatform
|
||||
// A missing/unreachable storage medium is expected, recoverable state,
|
||||
// not a reason to fail booting the whole game - log it and carry on
|
||||
// with SETTINGS.available false instead of chaining the error upward.
|
||||
errorret_t result = settingsInitPlatform();
|
||||
SETTINGS.available = errorIsOk(result);
|
||||
if(!SETTINGS.available) errorCatch(errorPrint(result));
|
||||
#else
|
||||
SETTINGS.available = false;
|
||||
#endif
|
||||
|
||||
if(SETTINGS.available) {
|
||||
errorret_t loadRet = settingsLoad();
|
||||
if(errorIsNotOk(loadRet)) errorCatch(errorPrint(loadRet));
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
bool_t settingsIsAvailable(void) {
|
||||
return SETTINGS.available;
|
||||
}
|
||||
|
||||
errorret_t settingsDispose(void) {
|
||||
#ifdef settingsDisposePlatform
|
||||
errorChain(settingsDisposePlatform());
|
||||
#endif
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsSave(void) {
|
||||
memoryCopy(
|
||||
SETTINGS.file.header, SETTINGS_FILE_HEADER, SETTINGS_FILE_HEADER_SIZE
|
||||
);
|
||||
SETTINGS.file.version = SETTINGS_FILE_VERSION;
|
||||
|
||||
settingsstream_t stream;
|
||||
memoryZero(&stream, sizeof(settingsstream_t));
|
||||
|
||||
#ifdef settingsStreamOpenWritePlatform
|
||||
errorret_t openRet = settingsStreamOpenWritePlatform(&stream);
|
||||
SETTINGS.available = errorIsOk(openRet);
|
||||
errorChain(openRet);
|
||||
#endif
|
||||
|
||||
errorret_t ret = settingsFileWrite(&stream, &SETTINGS.file);
|
||||
|
||||
if(errorIsOk(ret)) {
|
||||
ret = settingsStreamFinalizeWriteImpl(&stream);
|
||||
}
|
||||
|
||||
#ifdef settingsStreamClosePlatform
|
||||
settingsStreamClosePlatform(&stream);
|
||||
#endif
|
||||
|
||||
SETTINGS.file.exists = errorIsOk(ret);
|
||||
errorChain(ret);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
settingsfile_t * settingsGet(void) {
|
||||
return &SETTINGS.file;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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 "settingsfile.h"
|
||||
#include "save/settingsplatform.h"
|
||||
|
||||
typedef struct {
|
||||
/** The single settings file - see settingsfile.h. */
|
||||
settingsfile_t file;
|
||||
/** Platform-specific settings storage state (paths, card handles, etc.). */
|
||||
settingsplatform_t platform;
|
||||
/**
|
||||
* True if the settings storage medium was reachable the last time it
|
||||
* was checked - at settingsInit(), and refreshed by every subsequent
|
||||
* settingsSave() attempt. Missing storage is expected (e.g. no memory
|
||||
* card/stick inserted), not fatal - see settingsIsAvailable().
|
||||
*/
|
||||
bool_t available;
|
||||
} settings_t;
|
||||
|
||||
extern settings_t SETTINGS;
|
||||
|
||||
/**
|
||||
* Initializes the settings system and immediately loads the settings file
|
||||
* if one exists, so SETTINGS.file reflects the last-saved values as soon
|
||||
* as this returns - unlike save.h's per-slot game saves (which need an
|
||||
* explicit "new game vs continue" choice before loading), there's no such
|
||||
* ambiguity for device-wide settings, so this always loads eagerly.
|
||||
*
|
||||
* Never fails the way settingsSave() can - if the storage medium isn't
|
||||
* reachable, that's logged and reflected in settingsIsAvailable() rather
|
||||
* than treated as fatal, since the game should still be playable with
|
||||
* just the built-in defaults.
|
||||
*
|
||||
* @return An error code only for unexpected platform failures.
|
||||
*/
|
||||
errorret_t settingsInit(void);
|
||||
|
||||
/**
|
||||
* Checks whether the settings storage medium was reachable as of the last
|
||||
* load/save attempt.
|
||||
*
|
||||
* @return true if the settings medium was available last time it was
|
||||
* checked.
|
||||
*/
|
||||
bool_t settingsIsAvailable(void);
|
||||
|
||||
/**
|
||||
* Disposes of the settings system.
|
||||
*
|
||||
* @return An error code if disposal fails.
|
||||
*/
|
||||
errorret_t settingsDispose(void);
|
||||
|
||||
/**
|
||||
* Writes the current settings to persistent storage. Always synchronous -
|
||||
* unlike save.h's game saves, no platform needs a multi-frame native
|
||||
* dialog for this, so there's no callback/busy-polling to deal with.
|
||||
*
|
||||
* @return An error code if the write fails.
|
||||
*/
|
||||
errorret_t settingsSave(void);
|
||||
|
||||
/**
|
||||
* Gets a pointer to the live settings data. Modify fields directly, then
|
||||
* call settingsSave() to persist them.
|
||||
*
|
||||
* @return A pointer to the settings file.
|
||||
*/
|
||||
settingsfile_t * settingsGet(void);
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
/** Settings file format version. Increment on breaking change. */
|
||||
#define SETTINGS_FILE_VERSION 1
|
||||
|
||||
/** Magic bytes that identify a Dusk settings file. */
|
||||
#define SETTINGS_FILE_HEADER "DST"
|
||||
|
||||
/** Byte length of the magic header (excludes the null terminator). */
|
||||
#define SETTINGS_FILE_HEADER_SIZE (sizeof(SETTINGS_FILE_HEADER) - 1)
|
||||
|
||||
/**
|
||||
* Default gamepad deadzone for a settings file that's never actually been
|
||||
* loaded from disk yet (see settingsInit(), which stamps this on first
|
||||
* boot) - the settings file is the single source of truth for this value
|
||||
* (see settingsfile_t.deadzone); nothing else stores or defaults it.
|
||||
*/
|
||||
#define SETTINGS_DEADZONE_DEFAULT 0.1f
|
||||
|
||||
/**
|
||||
* Device/user-wide preferences, independent of any individual game save
|
||||
* slot (see savefile.h) - there's exactly one of these, not one per slot,
|
||||
* since a setting like gamepad deadzone shouldn't reset or diverge just
|
||||
* because the player started a new game in a different slot.
|
||||
*/
|
||||
typedef struct {
|
||||
/** Magic header bytes read from the file; must equal SETTINGS_FILE_HEADER. */
|
||||
char_t header[SETTINGS_FILE_HEADER_SIZE];
|
||||
/** Format version read from the file; used to branch on older layouts. */
|
||||
uint32_t version;
|
||||
/** Runtime flag - true if settings were successfully loaded or written. */
|
||||
bool_t exists;
|
||||
/**
|
||||
* User-configured gamepad deadzone (0.0f-1.0f) - the settings file is
|
||||
* the only place this lives; read it directly via settingsGet()->deadzone
|
||||
* rather than caching it anywhere else.
|
||||
*/
|
||||
float_t deadzone;
|
||||
} settingsfile_t;
|
||||
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "save/settingsstream.h"
|
||||
#include "util/crypt.h"
|
||||
#include "util/endian.h"
|
||||
|
||||
errorret_t settingsStreamReadBytesRawImpl(
|
||||
settingsstream_t *stream, void *buf, const size_t len
|
||||
) {
|
||||
#ifdef settingsStreamReadBytesPlatform
|
||||
errorChain(settingsStreamReadBytesPlatform(stream, buf, len));
|
||||
#endif
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamWriteBytesRawImpl(
|
||||
settingsstream_t *stream, const void *buf, const size_t len
|
||||
) {
|
||||
#ifdef settingsStreamWriteBytesPlatform
|
||||
errorChain(settingsStreamWriteBytesPlatform(stream, buf, len));
|
||||
#endif
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamReadBytesImpl(
|
||||
settingsstream_t *stream, void *buf, const size_t len
|
||||
) {
|
||||
errorChain(settingsStreamReadBytesRawImpl(stream, buf, len));
|
||||
cryptCRC32Update(&stream->checksum, buf, len);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamWriteBytesImpl(
|
||||
settingsstream_t *stream, const void *buf, const size_t len
|
||||
) {
|
||||
cryptCRC32Update(&stream->checksum, buf, len);
|
||||
errorChain(settingsStreamWriteBytesRawImpl(stream, buf, len));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamFinalizeWriteImpl(settingsstream_t *stream) {
|
||||
uint32_t finalCRC = cryptCRC32End(stream->checksum);
|
||||
uint32_t leChecksum = endianLittleToHost32(finalCRC);
|
||||
|
||||
#ifdef settingsStreamSeekPlatform
|
||||
errorChain(settingsStreamSeekPlatform(stream, SETTINGS_FILE_HEADER_SIZE));
|
||||
#endif
|
||||
|
||||
errorChain(settingsStreamWriteBytesRawImpl(
|
||||
stream, &leChecksum, sizeof(uint32_t)
|
||||
));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamVerifyChecksumImpl(settingsstream_t *stream) {
|
||||
uint32_t computed = cryptCRC32End(stream->checksum);
|
||||
if(computed != stream->expectedChecksum) {
|
||||
errorThrow("Settings file has invalid checksum");
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamReadHeaderImpl(
|
||||
settingsstream_t *stream, char_t header[SETTINGS_FILE_HEADER_SIZE]
|
||||
) {
|
||||
errorChain(settingsStreamReadBytesRawImpl(
|
||||
stream, header, SETTINGS_FILE_HEADER_SIZE
|
||||
));
|
||||
|
||||
if(
|
||||
header[0] != SETTINGS_FILE_HEADER[0] ||
|
||||
header[1] != SETTINGS_FILE_HEADER[1] ||
|
||||
header[2] != SETTINGS_FILE_HEADER[2]
|
||||
) {
|
||||
errorThrow("Settings file has invalid header");
|
||||
}
|
||||
|
||||
uint32_t leChecksum;
|
||||
errorChain(settingsStreamReadBytesRawImpl(
|
||||
stream, &leChecksum, sizeof(uint32_t)
|
||||
));
|
||||
stream->expectedChecksum = endianLittleToHost32(leChecksum);
|
||||
stream->checksum = cryptCRC32Begin();
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamWriteHeaderImpl(
|
||||
settingsstream_t *stream, const char_t header[SETTINGS_FILE_HEADER_SIZE]
|
||||
) {
|
||||
errorChain(settingsStreamWriteBytesRawImpl(
|
||||
stream, header, SETTINGS_FILE_HEADER_SIZE
|
||||
));
|
||||
|
||||
uint32_t placeholder = 0;
|
||||
errorChain(settingsStreamWriteBytesRawImpl(
|
||||
stream, &placeholder, sizeof(uint32_t)
|
||||
));
|
||||
stream->checksum = cryptCRC32Begin();
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamReadVersionImpl(settingsstream_t *stream, uint32_t *out) {
|
||||
uint32_t raw;
|
||||
errorChain(settingsStreamReadBytesImpl(stream, &raw, sizeof(uint32_t)));
|
||||
*out = endianLittleToHost32(raw);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamWriteVersionImpl(
|
||||
settingsstream_t *stream, const uint32_t *input
|
||||
) {
|
||||
uint32_t raw = endianLittleToHost32(*input);
|
||||
errorChain(settingsStreamWriteBytesImpl(stream, &raw, sizeof(uint32_t)));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamReadBoolImpl(settingsstream_t *stream, bool_t *out) {
|
||||
uint8_t raw;
|
||||
errorChain(settingsStreamReadBytesImpl(stream, &raw, sizeof(uint8_t)));
|
||||
*out = (bool_t)(raw != 0);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamWriteBoolImpl(
|
||||
settingsstream_t *stream, const bool_t *input
|
||||
) {
|
||||
uint8_t raw = *input ? 1 : 0;
|
||||
errorChain(settingsStreamWriteBytesImpl(stream, &raw, sizeof(uint8_t)));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamReadFloatImpl(settingsstream_t *stream, float_t *out) {
|
||||
float_t raw;
|
||||
errorChain(settingsStreamReadBytesImpl(stream, &raw, sizeof(float_t)));
|
||||
*out = endianLittleToHostFloat(raw);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamWriteFloatImpl(
|
||||
settingsstream_t *stream, const float_t *input
|
||||
) {
|
||||
float_t raw = endianLittleToHostFloat(*input);
|
||||
errorChain(settingsStreamWriteBytesImpl(stream, &raw, sizeof(float_t)));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsFileLoad(settingsstream_t *stream, settingsfile_t *file) {
|
||||
settingsFileReadHeader(stream, file->header);
|
||||
settingsFileReadVersion(stream, &file->version);
|
||||
settingsFileReadFloat(stream, &file->deadzone);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsFileWrite(settingsstream_t *stream, settingsfile_t *file) {
|
||||
settingsFileWriteHeader(stream, file->header);
|
||||
settingsFileWriteVersion(stream, &file->version);
|
||||
settingsFileWriteFloat(stream, &file->deadzone);
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* 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 "settingsfile.h"
|
||||
#include "save/settingsplatform.h"
|
||||
|
||||
typedef struct {
|
||||
bool_t found;
|
||||
uint32_t checksum;
|
||||
uint32_t expectedChecksum;
|
||||
settingsplatformstream_t platform;
|
||||
} settingsstream_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 settingsStreamReadBytesRawImpl(
|
||||
settingsstream_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 settingsStreamWriteBytesRawImpl(
|
||||
settingsstream_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 settingsStreamReadBytesImpl(
|
||||
settingsstream_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 settingsStreamWriteBytesImpl(
|
||||
settingsstream_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 settingsStreamFinalizeWriteImpl(settingsstream_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).
|
||||
* @return An error if the checksum does not match.
|
||||
*/
|
||||
errorret_t settingsStreamVerifyChecksumImpl(settingsstream_t *stream);
|
||||
|
||||
/**
|
||||
* 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 SETTINGS_FILE_HEADER_SIZE bytes to receive the
|
||||
* header.
|
||||
* @return An error if the header is missing or invalid.
|
||||
*/
|
||||
errorret_t settingsStreamReadHeaderImpl(
|
||||
settingsstream_t *stream, char_t header[SETTINGS_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 SETTINGS_FILE_HEADER_SIZE bytes to write.
|
||||
* @return An error if the write fails.
|
||||
*/
|
||||
errorret_t settingsStreamWriteHeaderImpl(
|
||||
settingsstream_t *stream,
|
||||
const char_t header[SETTINGS_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 settingsStreamReadVersionImpl(settingsstream_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 settingsStreamWriteVersionImpl(
|
||||
settingsstream_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 settingsStreamReadBoolImpl(settingsstream_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 settingsStreamWriteBoolImpl(
|
||||
settingsstream_t *stream, const bool_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 settingsStreamReadFloatImpl(settingsstream_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 settingsStreamWriteFloatImpl(
|
||||
settingsstream_t *stream, const float_t *input
|
||||
);
|
||||
|
||||
/**
|
||||
* Reads the contents of the settings file from the stream.
|
||||
*
|
||||
* @param stream Active read stream.
|
||||
* @param file Settings file struct to populate.
|
||||
* @return An error code if loading fails.
|
||||
*/
|
||||
errorret_t settingsFileLoad(settingsstream_t *stream, settingsfile_t *file);
|
||||
|
||||
/**
|
||||
* Writes the contents of the settings file struct into the stream.
|
||||
*
|
||||
* @param stream Active write stream.
|
||||
* @param file Settings file struct to serialize.
|
||||
* @return An error code if writing fails.
|
||||
*/
|
||||
errorret_t settingsFileWrite(settingsstream_t *stream, settingsfile_t *file);
|
||||
|
||||
#define settingsFileReadHeader(stream, header) \
|
||||
errorChain(settingsStreamReadHeaderImpl(stream, header))
|
||||
#define settingsFileWriteHeader(stream, header) \
|
||||
errorChain(settingsStreamWriteHeaderImpl(stream, header))
|
||||
|
||||
#define settingsFileReadVersion(stream, out) \
|
||||
errorChain(settingsStreamReadVersionImpl(stream, out))
|
||||
#define settingsFileWriteVersion(stream, input) \
|
||||
errorChain(settingsStreamWriteVersionImpl(stream, input))
|
||||
|
||||
#define settingsFileReadBool(stream, out) \
|
||||
errorChain(settingsStreamReadBoolImpl(stream, out))
|
||||
#define settingsFileWriteBool(stream, input) \
|
||||
errorChain(settingsStreamWriteBoolImpl(stream, input))
|
||||
|
||||
#define settingsFileReadFloat(stream, out) \
|
||||
errorChain(settingsStreamReadFloatImpl(stream, out))
|
||||
#define settingsFileWriteFloat(stream, input) \
|
||||
errorChain(settingsStreamWriteFloatImpl(stream, input))
|
||||
@@ -11,7 +11,7 @@
|
||||
#include "util/memory.h"
|
||||
#include "locale/localemanager.h"
|
||||
#include "asset/loader/locale/assetlocaleloader.h"
|
||||
#include "save/save.h"
|
||||
#include "save/settings.h"
|
||||
|
||||
void uiSettingsInputSelected(
|
||||
const uimenu_t *menu,
|
||||
@@ -39,7 +39,7 @@ errorret_t uiSettingsInputInit(uisettingsdata_t *data) {
|
||||
UI_SETTINGS_INPUT_LABEL_MAX
|
||||
));
|
||||
MENU_SLIDER_FLOAT(
|
||||
input->deadzoneLabel, SAVE_DEADZONE_DEFAULT, 0.0f, 1.0f, 0.05f
|
||||
input->deadzoneLabel, SETTINGS_DEADZONE_DEFAULT, 0.0f, 1.0f, 0.05f
|
||||
);
|
||||
#else
|
||||
MENU_LABEL("No input settings yet");
|
||||
@@ -55,16 +55,17 @@ void uiSettingsInputLoad(void) {
|
||||
#ifdef DUSK_INPUT_GAMEPAD
|
||||
uiSliderSetFloat(
|
||||
&UI_SETTINGS.data.input.items[UI_SETTINGS_INPUT_INDEX_DEADZONE].slider,
|
||||
saveGet(SAVE_ACTIVE_SLOT)->deadzone
|
||||
settingsGet()->deadzone
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
void uiSettingsInputApply(void) {
|
||||
#ifdef DUSK_INPUT_GAMEPAD
|
||||
saveGet(SAVE_ACTIVE_SLOT)->deadzone = uiSliderGetFloat(
|
||||
settingsGet()->deadzone = uiSliderGetFloat(
|
||||
&UI_SETTINGS.data.input.items[UI_SETTINGS_INPUT_INDEX_DEADZONE].slider
|
||||
);
|
||||
errorCatch(errorPrint(settingsSave()));
|
||||
#endif
|
||||
uiMenuClose(&UI_SETTINGS.data.input.menu);
|
||||
}
|
||||
@@ -73,7 +74,7 @@ bool_t uiSettingsInputHasChanges(void) {
|
||||
#ifdef DUSK_INPUT_GAMEPAD
|
||||
if(uiSliderGetFloat(
|
||||
&UI_SETTINGS.data.input.items[UI_SETTINGS_INPUT_INDEX_DEADZONE].slider
|
||||
) != saveGet(SAVE_ACTIVE_SLOT)->deadzone) return true;
|
||||
) != settingsGet()->deadzone) return true;
|
||||
#endif
|
||||
|
||||
return false;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "assert/assert.h"
|
||||
#include "log/log.h"
|
||||
#include "util/string.h"
|
||||
#include "save/save.h"
|
||||
#include "save/settings.h"
|
||||
|
||||
inputbuttondata_t INPUT_BUTTON_DATA[] = {
|
||||
#ifdef DUSK_INPUT_GAMEPAD
|
||||
@@ -188,5 +188,5 @@ float_t inputButtonGetValueDolphin(const inputbutton_t button) {
|
||||
}
|
||||
|
||||
float_t inputGetDeadzoneDolphin(const inputbutton_t button) {
|
||||
return saveGet(SAVE_ACTIVE_SLOT)->deadzone;
|
||||
return settingsGet()->deadzone;
|
||||
}
|
||||
@@ -8,4 +8,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
savedolphin.c
|
||||
savestreamdolphin.c
|
||||
settingsdolphin.c
|
||||
settingsstreamdolphin.c
|
||||
)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "save/save.h"
|
||||
#include "save/settings.h"
|
||||
|
||||
errorret_t settingsInitDolphin(void) {
|
||||
if(!SAVE.platform.mounted) {
|
||||
errorThrow("No memory card mounted");
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsDisposeDolphin(void) {
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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/settingsfile.h"
|
||||
|
||||
#ifndef SETTINGS_DOLPHIN_FILE_NAME
|
||||
#define SETTINGS_DOLPHIN_FILE_NAME "DUSK_CFG"
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
/** No dedicated state - settings reuse the memory card mount that the
|
||||
* game-save system (see savedolphin.h) already establishes. */
|
||||
uint8_t reserved;
|
||||
} settingsdolphin_t;
|
||||
|
||||
/**
|
||||
* Initializes the settings system on GameCube. Does not mount the memory
|
||||
* card itself - reuses the mount already established by the game-save
|
||||
* system (see saveInitDolphin()), since there's only one memory card
|
||||
* subsystem to go around. settingsInit() must therefore run after
|
||||
* saveInit() on this platform.
|
||||
*
|
||||
* @return An error code if no memory card is currently mounted.
|
||||
*/
|
||||
errorret_t settingsInitDolphin(void);
|
||||
|
||||
/**
|
||||
* Disposes of the settings system on GameCube. Does not unmount the
|
||||
* memory card - that's owned by the game-save system.
|
||||
*
|
||||
* @return An error code if disposal fails.
|
||||
*/
|
||||
errorret_t settingsDisposeDolphin(void);
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "save/settingsdolphin.h"
|
||||
#include "save/settingsstreamdolphin.h"
|
||||
|
||||
typedef settingsdolphin_t settingsplatform_t;
|
||||
typedef settingsstreamdolphin_t settingsplatformstream_t;
|
||||
|
||||
#define settingsInitPlatform settingsInitDolphin
|
||||
#define settingsDisposePlatform settingsDisposeDolphin
|
||||
|
||||
#define settingsStreamOpenReadPlatform(stream) \
|
||||
settingsStreamOpenReadDolphin(&(stream)->platform, &(stream)->found)
|
||||
#define settingsStreamOpenWritePlatform(stream) \
|
||||
settingsStreamOpenWriteDolphin(&(stream)->platform)
|
||||
#define settingsStreamClosePlatform(stream) \
|
||||
settingsStreamCloseDolphin(&(stream)->platform)
|
||||
#define settingsStreamReadBytesPlatform(stream, buf, len) \
|
||||
settingsStreamReadBytesDolphin(&(stream)->platform, buf, len)
|
||||
#define settingsStreamWriteBytesPlatform(stream, buf, len) \
|
||||
settingsStreamWriteBytesDolphin(&(stream)->platform, buf, len)
|
||||
#define settingsStreamSeekPlatform(stream, pos) \
|
||||
settingsStreamSeekDolphin(&(stream)->platform, pos)
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "save/save.h"
|
||||
#include "save/settingsdolphin.h"
|
||||
#include "save/settingsstreamdolphin.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
errorret_t settingsStreamOpenReadDolphin(
|
||||
settingsstreamdolphin_t *p, bool_t *found
|
||||
) {
|
||||
if(!SAVE.platform.mounted) {
|
||||
*found = false;
|
||||
errorThrow("No memory card mounted");
|
||||
}
|
||||
|
||||
int32_t result;
|
||||
do {
|
||||
result = CARD_Open(
|
||||
SAVE_DOLPHIN_CHANNEL, SETTINGS_DOLPHIN_FILE_NAME, &p->cardFile
|
||||
);
|
||||
} while(result == CARD_ERROR_BUSY);
|
||||
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 settings file: %s (%d)",
|
||||
saveCardErrorStringDolphin(result), result
|
||||
);
|
||||
}
|
||||
|
||||
do {
|
||||
result = CARD_Read(&p->cardFile, p->buffer, SAVE_DOLPHIN_SECTOR_SIZE, 0);
|
||||
} while(result == CARD_ERROR_BUSY);
|
||||
CARD_Close(&p->cardFile);
|
||||
if(result < 0) {
|
||||
*found = false;
|
||||
errorThrow("Failed to read memory card settings data: %s (%d)",
|
||||
saveCardErrorStringDolphin(result), result
|
||||
);
|
||||
}
|
||||
|
||||
*found = true;
|
||||
p->position = 0;
|
||||
p->writing = false;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamOpenWriteDolphin(settingsstreamdolphin_t *p) {
|
||||
if(!SAVE.platform.mounted) errorThrow("No memory card mounted");
|
||||
|
||||
memoryZero(p->buffer, SAVE_DOLPHIN_SECTOR_SIZE);
|
||||
p->position = 0;
|
||||
p->writing = true;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void settingsStreamCloseDolphin(settingsstreamdolphin_t *p) {
|
||||
if(!p->writing) return;
|
||||
|
||||
int32_t result;
|
||||
do {
|
||||
result = CARD_Open(
|
||||
SAVE_DOLPHIN_CHANNEL, SETTINGS_DOLPHIN_FILE_NAME, &p->cardFile
|
||||
);
|
||||
} while(result == CARD_ERROR_BUSY);
|
||||
if(result == CARD_ERROR_NOFILE) {
|
||||
do {
|
||||
result = CARD_Create(
|
||||
SAVE_DOLPHIN_CHANNEL, SETTINGS_DOLPHIN_FILE_NAME,
|
||||
SAVE_DOLPHIN_SECTOR_SIZE, &p->cardFile
|
||||
);
|
||||
} while(result == CARD_ERROR_BUSY);
|
||||
}
|
||||
|
||||
do {
|
||||
result = CARD_Write(&p->cardFile, p->buffer, SAVE_DOLPHIN_SECTOR_SIZE, 0);
|
||||
} while(result == CARD_ERROR_BUSY);
|
||||
CARD_Close(&p->cardFile);
|
||||
}
|
||||
|
||||
errorret_t settingsStreamReadBytesDolphin(
|
||||
settingsstreamdolphin_t *p, void *buf, const size_t len
|
||||
) {
|
||||
if(p->position + len > SAVE_DOLPHIN_SECTOR_SIZE) {
|
||||
errorThrow("Settings stream read exceeds sector size");
|
||||
}
|
||||
memoryCopy(buf, p->buffer + p->position, len);
|
||||
p->position += len;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamWriteBytesDolphin(
|
||||
settingsstreamdolphin_t *p, const void *buf, const size_t len
|
||||
) {
|
||||
if(p->position + len > SAVE_DOLPHIN_SECTOR_SIZE) {
|
||||
errorThrow("Settings stream write exceeds sector size");
|
||||
}
|
||||
memoryCopy(p->buffer + p->position, buf, len);
|
||||
p->position += len;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamSeekDolphin(
|
||||
settingsstreamdolphin_t *p, const size_t pos
|
||||
) {
|
||||
if(pos >= SAVE_DOLPHIN_SECTOR_SIZE) {
|
||||
errorThrow("Settings stream seek out of range");
|
||||
}
|
||||
p->position = pos;
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 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;
|
||||
} settingsstreamdolphin_t;
|
||||
|
||||
/**
|
||||
* Opens the settings memory card file 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.
|
||||
* @return An error if reading the card fails for a reason other than
|
||||
* missing file.
|
||||
*/
|
||||
errorret_t settingsStreamOpenReadDolphin(
|
||||
settingsstreamdolphin_t *p, bool_t *found
|
||||
);
|
||||
|
||||
/**
|
||||
* Opens the settings memory card file for writing by zeroing the sector
|
||||
* buffer. The buffer is flushed to the card when
|
||||
* settingsStreamCloseDolphin is called.
|
||||
*
|
||||
* @param p Stream to initialize.
|
||||
* @return An error if initialization fails.
|
||||
*/
|
||||
errorret_t settingsStreamOpenWriteDolphin(settingsstreamdolphin_t *p);
|
||||
|
||||
/**
|
||||
* Flushes the sector buffer to the memory card (write mode only) and
|
||||
* releases the card file handle.
|
||||
*
|
||||
* @param p Stream to close.
|
||||
*/
|
||||
void settingsStreamCloseDolphin(settingsstreamdolphin_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 settingsStreamReadBytesDolphin(
|
||||
settingsstreamdolphin_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 settingsStreamWriteBytesDolphin(
|
||||
settingsstreamdolphin_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 settingsStreamSeekDolphin(
|
||||
settingsstreamdolphin_t *p, const size_t pos
|
||||
);
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
#include "input/input.h"
|
||||
#include "save/save.h"
|
||||
#include "save/settings.h"
|
||||
|
||||
inputbuttondata_t INPUT_BUTTON_DATA[] = {
|
||||
#ifdef DUSK_INPUT_GAMEPAD
|
||||
@@ -548,5 +548,5 @@ errorret_t inputInitLinux(void) {
|
||||
}
|
||||
|
||||
float_t inputGetDeadzoneSDL2(const inputbutton_t button) {
|
||||
return saveGet(SAVE_ACTIVE_SLOT)->deadzone;
|
||||
return settingsGet()->deadzone;
|
||||
}
|
||||
@@ -8,4 +8,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
savelinux.c
|
||||
savestreamlinux.c
|
||||
settingslinux.c
|
||||
settingsstreamlinux.c
|
||||
)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "save/settings.h"
|
||||
#include "util/string.h"
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
errorret_t settingsInitLinux(void) {
|
||||
stringCopy(
|
||||
SETTINGS.platform.path, SETTINGS_LINUX_PATH, SETTINGS_LINUX_PATH_MAX
|
||||
);
|
||||
|
||||
if(mkdir(SETTINGS.platform.path, 0755) != 0 && errno != EEXIST) {
|
||||
errorThrow(
|
||||
"Failed to create settings directory: %s", SETTINGS.platform.path
|
||||
);
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsDisposeLinux(void) {
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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/settingsfile.h"
|
||||
|
||||
#define SETTINGS_LINUX_PATH_MAX FILENAME_MAX
|
||||
#define SETTINGS_LINUX_FILE_FORMAT "%s/settings.dat"
|
||||
|
||||
#ifndef SETTINGS_LINUX_PATH
|
||||
#define SETTINGS_LINUX_PATH "./saves"
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
char_t path[SETTINGS_LINUX_PATH_MAX];
|
||||
} settingslinux_t;
|
||||
|
||||
/**
|
||||
* Initializes the settings system on Linux - ensures the settings
|
||||
* directory exists, independent of whether the game-save system (see
|
||||
* savelinux.h) has been initialized.
|
||||
*
|
||||
* @return An error code if initialization fails.
|
||||
*/
|
||||
errorret_t settingsInitLinux(void);
|
||||
|
||||
/**
|
||||
* Disposes of the settings system on Linux.
|
||||
*
|
||||
* @return An error code if disposal fails.
|
||||
*/
|
||||
errorret_t settingsDisposeLinux(void);
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "save/settingslinux.h"
|
||||
#include "save/settingsstreamlinux.h"
|
||||
|
||||
typedef settingslinux_t settingsplatform_t;
|
||||
typedef settingsstreamlinux_t settingsplatformstream_t;
|
||||
|
||||
#define settingsInitPlatform settingsInitLinux
|
||||
#define settingsDisposePlatform settingsDisposeLinux
|
||||
|
||||
#define settingsStreamOpenReadPlatform(stream) \
|
||||
settingsStreamOpenReadLinux(&(stream)->platform, &(stream)->found)
|
||||
#define settingsStreamOpenWritePlatform(stream) \
|
||||
settingsStreamOpenWriteLinux(&(stream)->platform)
|
||||
#define settingsStreamClosePlatform(stream) \
|
||||
settingsStreamCloseLinux(&(stream)->platform)
|
||||
#define settingsStreamReadBytesPlatform(stream, buf, len) \
|
||||
settingsStreamReadBytesLinux(&(stream)->platform, buf, len)
|
||||
#define settingsStreamWriteBytesPlatform(stream, buf, len) \
|
||||
settingsStreamWriteBytesLinux(&(stream)->platform, buf, len)
|
||||
#define settingsStreamSeekPlatform(stream, pos) \
|
||||
settingsStreamSeekLinux(&(stream)->platform, pos)
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "save/settings.h"
|
||||
#include "save/settingsstreamlinux.h"
|
||||
#include "util/string.h"
|
||||
|
||||
static void _settingsStreamGetPath(char_t *out, const size_t max) {
|
||||
snprintf(out, max, SETTINGS_LINUX_FILE_FORMAT, SETTINGS.platform.path);
|
||||
}
|
||||
|
||||
errorret_t settingsStreamOpenReadLinux(
|
||||
settingsstreamlinux_t *p, bool_t *found
|
||||
) {
|
||||
char_t path[SETTINGS_LINUX_PATH_MAX];
|
||||
_settingsStreamGetPath(path, SETTINGS_LINUX_PATH_MAX);
|
||||
|
||||
p->file = fopen(path, "rb");
|
||||
*found = (p->file != NULL);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamOpenWriteLinux(settingsstreamlinux_t *p) {
|
||||
char_t path[SETTINGS_LINUX_PATH_MAX];
|
||||
_settingsStreamGetPath(path, SETTINGS_LINUX_PATH_MAX);
|
||||
|
||||
p->file = fopen(path, "wb");
|
||||
if(!p->file) {
|
||||
errorThrow("Failed to open settings file for writing");
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void settingsStreamCloseLinux(settingsstreamlinux_t *p) {
|
||||
if(p->file) {
|
||||
fclose(p->file);
|
||||
p->file = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
errorret_t settingsStreamReadBytesLinux(
|
||||
settingsstreamlinux_t *p, void *buf, const size_t len
|
||||
) {
|
||||
if(fread(buf, 1, len, p->file) != len) {
|
||||
errorThrow("Unexpected end of settings file");
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamWriteBytesLinux(
|
||||
settingsstreamlinux_t *p, const void *buf, const size_t len
|
||||
) {
|
||||
if(fwrite(buf, 1, len, p->file) != len) {
|
||||
errorThrow("Failed to write settings data");
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamSeekLinux(settingsstreamlinux_t *p, const size_t pos) {
|
||||
if(fseek(p->file, (long)pos, SEEK_SET) != 0) {
|
||||
errorThrow("Failed to seek in settings file");
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
@@ -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 <stdio.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct {
|
||||
FILE *file;
|
||||
} settingsstreamlinux_t;
|
||||
|
||||
/**
|
||||
* Opens the settings file for reading.
|
||||
*
|
||||
* @param p Stream to initialize.
|
||||
* @param found Set to true if the file exists, false if it does not.
|
||||
* @return An error if the open fails for a reason other than missing file.
|
||||
*/
|
||||
errorret_t settingsStreamOpenReadLinux(
|
||||
settingsstreamlinux_t *p, bool_t *found
|
||||
);
|
||||
|
||||
/**
|
||||
* Opens the settings file for writing, creating or truncating it.
|
||||
*
|
||||
* @param p Stream to initialize.
|
||||
* @return An error if the file cannot be opened for writing.
|
||||
*/
|
||||
errorret_t settingsStreamOpenWriteLinux(settingsstreamlinux_t *p);
|
||||
|
||||
/**
|
||||
* Closes the file handle held by the stream.
|
||||
*
|
||||
* @param p Stream to close.
|
||||
*/
|
||||
void settingsStreamCloseLinux(settingsstreamlinux_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 settingsStreamReadBytesLinux(
|
||||
settingsstreamlinux_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 settingsStreamWriteBytesLinux(
|
||||
settingsstreamlinux_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 settingsStreamSeekLinux(settingsstreamlinux_t *p, const size_t pos);
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
#include "input/input.h"
|
||||
#include "save/save.h"
|
||||
#include "save/settings.h"
|
||||
|
||||
// #define INPUT_PSP_GAMEPAD_BUTTON_ACCEPT INPUT_SDL2_GAMEPAD_BUTTON_CUSTOM
|
||||
// #define INPUT_PSP_GAMEPAD_BUTTON_CANCEL INPUT_SDL2_GAMEPAD_BUTTON_CUSTOM
|
||||
@@ -95,5 +95,5 @@ errorret_t inputInitPSP(void) {
|
||||
}
|
||||
|
||||
float_t inputGetDeadzoneSDL2(const inputbutton_t button) {
|
||||
return saveGet(SAVE_ACTIVE_SLOT)->deadzone;
|
||||
return settingsGet()->deadzone;
|
||||
}
|
||||
@@ -8,4 +8,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
savepsp.c
|
||||
savestreampsp.c
|
||||
settingspsp.c
|
||||
settingsstreampsp.c
|
||||
)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "save/settingspsp.h"
|
||||
#include "save/settingsstreampsp.h"
|
||||
|
||||
typedef settingspsp_t settingsplatform_t;
|
||||
typedef settingsstreampsp_t settingsplatformstream_t;
|
||||
|
||||
#define settingsInitPlatform settingsInitPSP
|
||||
#define settingsDisposePlatform settingsDisposePSP
|
||||
|
||||
#define settingsStreamOpenReadPlatform(stream) \
|
||||
settingsStreamOpenReadPSP(&(stream)->platform, &(stream)->found)
|
||||
#define settingsStreamOpenWritePlatform(stream) \
|
||||
settingsStreamOpenWritePSP(&(stream)->platform)
|
||||
#define settingsStreamClosePlatform(stream) \
|
||||
settingsStreamClosePSP(&(stream)->platform)
|
||||
#define settingsStreamReadBytesPlatform(stream, buf, len) \
|
||||
settingsStreamReadBytesPSP(&(stream)->platform, buf, len)
|
||||
#define settingsStreamWriteBytesPlatform(stream, buf, len) \
|
||||
settingsStreamWriteBytesPSP(&(stream)->platform, buf, len)
|
||||
#define settingsStreamSeekPlatform(stream, pos) \
|
||||
settingsStreamSeekPSP(&(stream)->platform, pos)
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "save/settingspsp.h"
|
||||
|
||||
errorret_t settingsInitPSP(void) {
|
||||
SceIoStat stat;
|
||||
if(sceIoGetstat(SAVE_PSP_ROOT, &stat) < 0) {
|
||||
errorThrow("No memory stick detected");
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsDisposePSP(void) {
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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/settingsfile.h"
|
||||
#include "save/savepsp.h"
|
||||
#include <pspiofilemgr.h>
|
||||
|
||||
#define SETTINGS_PSP_DIR_FORMAT "ms0:/PSP/SAVEDATA/%sCFG"
|
||||
#define SETTINGS_PSP_FILE_FORMAT SETTINGS_PSP_DIR_FORMAT "/settings.bin"
|
||||
#define SETTINGS_PSP_PATH_MAX 256
|
||||
|
||||
typedef struct {
|
||||
uint8_t reserved;
|
||||
} settingspsp_t;
|
||||
|
||||
/**
|
||||
* Initializes the settings system on PSP. Confirms the memory stick is
|
||||
* reachable (sceIoGetstat on SAVE_PSP_ROOT), same check as saveInitPSP().
|
||||
* Deliberately does not go through sceUtilitySavedata (see savepsp.h) -
|
||||
* that dialog is built for the "game save" browser/PARAM.SFO experience
|
||||
* and shows a visible native icon flash on every write, which is fine for
|
||||
* an explicit menu Save but not for a settings file that can be written
|
||||
* every time the player nudges a slider. Plain sceIo file I/O, in its own
|
||||
* directory outside the per-slot savedata folders, avoids that entirely.
|
||||
*
|
||||
* @return An error code if no memory stick is reachable.
|
||||
*/
|
||||
errorret_t settingsInitPSP(void);
|
||||
|
||||
/**
|
||||
* Disposes of the settings system on PSP.
|
||||
*
|
||||
* @return An error code if disposal fails.
|
||||
*/
|
||||
errorret_t settingsDisposePSP(void);
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "save/settingspsp.h"
|
||||
#include "save/settingsstreampsp.h"
|
||||
#include "util/string.h"
|
||||
|
||||
errorret_t settingsStreamOpenReadPSP(settingsstreampsp_t *p, bool_t *found) {
|
||||
char_t path[SETTINGS_PSP_PATH_MAX];
|
||||
stringFormat(
|
||||
path, sizeof(path), SETTINGS_PSP_FILE_FORMAT, SAVE_PSP_GAME_NAME
|
||||
);
|
||||
|
||||
p->fd = sceIoOpen(path, PSP_O_RDONLY, 0);
|
||||
*found = (p->fd >= 0);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamOpenWritePSP(settingsstreampsp_t *p) {
|
||||
char_t dir[SETTINGS_PSP_PATH_MAX];
|
||||
stringFormat(dir, sizeof(dir), SETTINGS_PSP_DIR_FORMAT, SAVE_PSP_GAME_NAME);
|
||||
sceIoMkdir(dir, 0777);
|
||||
|
||||
char_t path[SETTINGS_PSP_PATH_MAX];
|
||||
stringFormat(
|
||||
path, sizeof(path), SETTINGS_PSP_FILE_FORMAT, SAVE_PSP_GAME_NAME
|
||||
);
|
||||
|
||||
p->fd = sceIoOpen(path, PSP_O_WRONLY | PSP_O_CREAT | PSP_O_TRUNC, 0777);
|
||||
if(p->fd < 0) {
|
||||
errorThrow("Failed to open settings file for writing");
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void settingsStreamClosePSP(settingsstreampsp_t *p) {
|
||||
if(p->fd >= 0) {
|
||||
sceIoClose(p->fd);
|
||||
p->fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
errorret_t settingsStreamReadBytesPSP(
|
||||
settingsstreampsp_t *p, void *buf, const size_t len
|
||||
) {
|
||||
int32_t read = sceIoRead(p->fd, buf, len);
|
||||
if(read != (int32_t)len) {
|
||||
errorThrow("Unexpected end of settings file");
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamWriteBytesPSP(
|
||||
settingsstreampsp_t *p, const void *buf, const size_t len
|
||||
) {
|
||||
int32_t written = sceIoWrite(p->fd, buf, len);
|
||||
if(written != (int32_t)len) {
|
||||
errorThrow("Failed to write settings data");
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t settingsStreamSeekPSP(settingsstreampsp_t *p, const size_t pos) {
|
||||
if(sceIoLseek(p->fd, (SceOff)pos, PSP_SEEK_SET) < 0) {
|
||||
errorThrow("Failed to seek in settings file");
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
@@ -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 <pspiofilemgr.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct {
|
||||
SceUID fd;
|
||||
} settingsstreampsp_t;
|
||||
|
||||
/**
|
||||
* Opens the settings file for reading via plain sceIo (not the
|
||||
* sceUtilitySavedata dialog - see settingspsp.h).
|
||||
*
|
||||
* @param p Stream to initialize.
|
||||
* @param found Set to true if the file exists, false if it does not.
|
||||
* @return An error if the open fails for a reason other than missing file.
|
||||
*/
|
||||
errorret_t settingsStreamOpenReadPSP(settingsstreampsp_t *p, bool_t *found);
|
||||
|
||||
/**
|
||||
* Opens the settings file for writing, creating its directory and
|
||||
* truncating the file if needed.
|
||||
*
|
||||
* @param p Stream to initialize.
|
||||
* @return An error if the file cannot be opened for writing.
|
||||
*/
|
||||
errorret_t settingsStreamOpenWritePSP(settingsstreampsp_t *p);
|
||||
|
||||
/**
|
||||
* Closes the file descriptor held by the stream.
|
||||
*
|
||||
* @param p Stream to close.
|
||||
*/
|
||||
void settingsStreamClosePSP(settingsstreampsp_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 settingsStreamReadBytesPSP(
|
||||
settingsstreampsp_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 settingsStreamWriteBytesPSP(
|
||||
settingsstreampsp_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 settingsStreamSeekPSP(settingsstreampsp_t *p, const size_t pos);
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
#include "input/input.h"
|
||||
#include "save/save.h"
|
||||
#include "save/settings.h"
|
||||
|
||||
inputbuttondata_t INPUT_BUTTON_DATA[] = {
|
||||
{ .name = "triangle", {
|
||||
@@ -84,5 +84,5 @@ inputbuttondata_t INPUT_BUTTON_DATA[] = {
|
||||
};
|
||||
|
||||
float_t inputGetDeadzoneSDL2(const inputbutton_t button) {
|
||||
return saveGet(SAVE_ACTIVE_SLOT)->deadzone;
|
||||
return settingsGet()->deadzone;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user