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:
@@ -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
|
||||
);
|
||||
Reference in New Issue
Block a user