diff --git a/cmake/targets/wii.cmake b/cmake/targets/wii.cmake index 5b2ae662..fa6be9c4 100644 --- a/cmake/targets/wii.cmake +++ b/cmake/targets/wii.cmake @@ -5,7 +5,7 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC ) # Wii save storage method - see src/duskdolphin/save/savedeviceplatform.h. -set(DUSK_SAVE_WII_METHOD "NAND" CACHE STRING +set(DUSK_SAVE_WII_METHOD "SD" CACHE STRING "Wii save storage: NAND (internal storage via ISFS), CARD (GameCube-\ compatible memory card emulation), or SD (SD card via libfat)" ) diff --git a/src/dusk/asset/asset.h b/src/dusk/asset/asset.h index 15f3dfd0..b84d9830 100644 --- a/src/dusk/asset/asset.h +++ b/src/dusk/asset/asset.h @@ -26,6 +26,8 @@ #define ASSET_LOADING_COUNT_MAX 10 #define ASSET_ENTRY_COUNT_MAX 64 +#define ASSET_BASE_DIRECTORY_MAX 256 + typedef struct asset_s { // Compressed (DEFLATE) archive - expected to hold the bulk of a game's // binary assets. Looked up first by assetFileInit(). @@ -37,6 +39,17 @@ typedef struct asset_s { // assetFileInit() only if the name isn't found in `zip`. zip_t *zipStored; + // Directory dusk.dsk was actually found/opened in (on PSP, the directory + // EBOOT.PBP lives in, since dusk.dsk is embedded inside it rather than a + // loose file) - populated by each platform's own assetInitXxx() once it + // locates the file. Save devices that persist to a real filesystem path + // (Linux, PSP, Wii SD) use this as their base directory instead of a + // separate hardcoded location, so saves live next to wherever the game + // itself was actually launched from rather than some unrelated fixed + // path. Not meaningful for devices with no filesystem-path concept at + // all (GameCube/Wii memory card, Wii NAND's ISFS namespace). + char_t baseDirectory[ASSET_BASE_DIRECTORY_MAX]; + assetplatform_t platform; // Guards every libzip call against ASSET.zip/ASSET.zipStored (zip_fopen, diff --git a/src/duskdolphin/asset/assetdolphinfat.c b/src/duskdolphin/asset/assetdolphinfat.c index 577597dc..88ea98ac 100644 --- a/src/duskdolphin/asset/assetdolphinfat.c +++ b/src/duskdolphin/asset/assetdolphinfat.c @@ -41,6 +41,11 @@ errorret_t assetInitDolphinFAT(void) { *dolphinSearchPath, ASSET_FILE_NAME ); + // Save devices with a real filesystem path (Wii SD) reuse this as + // their base directory - see ASSET.baseDirectory's own comment. + stringCopy( + ASSET.baseDirectory, *dolphinSearchPath, ASSET_BASE_DIRECTORY_MAX + ); break; } diff --git a/src/duskdolphin/save/savedevicedolphinsd.c b/src/duskdolphin/save/savedevicedolphinsd.c index a8febbd2..32074889 100644 --- a/src/duskdolphin/save/savedevicedolphinsd.c +++ b/src/duskdolphin/save/savedevicedolphinsd.c @@ -7,12 +7,12 @@ #include "save/savedevice.h" #include "save/savedevicedolphinsd.h" +#include "asset/asset.h" #include "assert/assert.h" #include "util/memory.h" #include "util/string.h" #include #include -#include #include #define SAVE_DEVICE_DOLPHIN_SD_PATH_MAX 128 @@ -39,29 +39,15 @@ void saveDeviceDolphinSDCheckAvailability(savedevice_t *device) { "Device state incorrect?" ); - // Safe to call more than once - returns true if already mounted. + // Safe to call more than once - returns true if already mounted. No + // directory to create here - ASSET.baseDirectory is already known to + // exist, since dusk.dsk was just successfully opened from it. if(!fatInitDefault()) { device->state = SAVE_DEVICE_STATE_UNAVAILABLE; device->reasonKey = "save.dolphin.sd_fat_init_failed"; return saveDeviceFireCallback(device); } - // mkdir() only ever creates one level at a time - create the parent - // ("/dusk") before the save directory itself. - if(mkdir("/dusk", 0700) != 0 && errno != EEXIST) { - device->state = SAVE_DEVICE_STATE_UNAVAILABLE; - device->reasonKey = "save.dolphin.sd_mkdir_failed"; - return saveDeviceFireCallback(device); - } - - if( - mkdir(SAVE_DEVICE_DOLPHIN_SD_DIRECTORY, 0700) != 0 && errno != EEXIST - ) { - device->state = SAVE_DEVICE_STATE_UNAVAILABLE; - device->reasonKey = "save.dolphin.sd_mkdir_failed"; - return saveDeviceFireCallback(device); - } - device->state = SAVE_DEVICE_STATE_AVAILABLE; device->reasonKey = NULL; saveDeviceFireCallback(device); @@ -75,7 +61,7 @@ errorret_t saveDeviceDolphinSDGetDataPath( stringFormat( dest, destSize, "%s/%s", - SAVE_DEVICE_DOLPHIN_SD_DIRECTORY, SAVE_DEVICE_DOLPHIN_SD_DATA_FILENAME + ASSET.baseDirectory, SAVE_DEVICE_DOLPHIN_SD_DATA_FILENAME ); errorOk(); @@ -96,7 +82,9 @@ errorret_t saveDeviceDolphinSDDataWrite( stringFormat(tempPath, sizeof(tempPath), "%s.tmp", finalPath); FILE *file = fopen(tempPath, "wb"); - if(file == NULL) errorThrow("Failed to open save data for writing: %s", tempPath); + if(file == NULL) { + errorThrow("Failed to open save data for writing: %s", tempPath); + } size_t written = fwrite(buffer, 1, size, file); fclose(file); diff --git a/src/duskdolphin/save/savedevicedolphinsd.h b/src/duskdolphin/save/savedevicedolphinsd.h index a684be8b..bc6622eb 100644 --- a/src/duskdolphin/save/savedevicedolphinsd.h +++ b/src/duskdolphin/save/savedevicedolphinsd.h @@ -8,12 +8,10 @@ #pragma once #include "error/error.h" -// Root directory on whichever device libfat's fatInitDefault() picks as the -// default (SD slot on Wii, SD Gecko/Slot2 on GameCube) - same mechanism -// already used for Dolphin asset loading, see assetdolphinfat.c. -#ifndef SAVE_DEVICE_DOLPHIN_SD_DIRECTORY - #define SAVE_DEVICE_DOLPHIN_SD_DIRECTORY "/dusk/save" -#endif +// Data file lives directly in ASSET.baseDirectory (the same directory +// dusk.dsk was found in - see its own comment) rather than a separate +// fixed directory, so it doesn't need creating: that directory is already +// known to exist, since dusk.dsk was just successfully opened from it. #ifndef SAVE_DEVICE_DOLPHIN_SD_DATA_FILENAME #define SAVE_DEVICE_DOLPHIN_SD_DATA_FILENAME "save.dat" #endif @@ -42,8 +40,8 @@ errorret_t saveDeviceDolphinSDUpdate(savedevice_t *device); /** * Requests the device to check its availability, this will call the callback - * whence completed. Initializes libfat and confirms the save directory - * exists (creating it on first run). + * whence completed. Initializes libfat - the save directory itself is + * ASSET.baseDirectory, already known to exist. * * @param device The save device platform to check availability. */ diff --git a/src/dusklinux/asset/assetlinux.c b/src/dusklinux/asset/assetlinux.c index 1f761d14..2e6a5a11 100644 --- a/src/dusklinux/asset/assetlinux.c +++ b/src/dusklinux/asset/assetlinux.c @@ -83,6 +83,13 @@ errorret_t assetInitLinux(void) { continue; } opened = true; + + // Save devices with a real filesystem path (Linux) reuse this as + // their base directory - see ASSET.baseDirectory's own comment. + stringCopy(ASSET.baseDirectory, searchPath, ASSET_BASE_DIRECTORY_MAX); + char_t *lastSlash = stringFindLastChar(ASSET.baseDirectory, '/'); + if(lastSlash != NULL) *lastSlash = '\0'; + break;// Found! } while(*(++path) != NULL); diff --git a/src/dusklinux/save/savedevicelinux.c b/src/dusklinux/save/savedevicelinux.c index 453e299e..1aafd1d0 100644 --- a/src/dusklinux/save/savedevicelinux.c +++ b/src/dusklinux/save/savedevicelinux.c @@ -10,10 +10,10 @@ #include "save/saveslot.h" #include "save/savesettings.h" #include "save/savejson.h" +#include "asset/asset.h" #include "assert/assert.h" #include "util/mkdirp.h" #include "util/string.h" -#include errorret_t saveDeviceLinuxInit(savedevice_t *device) { assertNotNull(device, "device cannot be null"); @@ -37,17 +37,15 @@ void saveDeviceLinuxCheckAvailability(savedevice_t *device) { "Device state incorrect?" ); - // Resolve the save directory. + // Resolve the save directory - can't actually fail (just a + // stringFormat), unlike when this derived from $HOME, but this function + // returns void (via saveDeviceFireCallback below) rather than + // errorret_t, so there's no return-early errorChain to use here. char_t directory[SAVE_DEVICE_LINUX_PATH_MAX]; errorret_t directoryResult = saveDeviceLinuxGetDirectory( directory, sizeof(directory) ); - if(errorIsNotOk(directoryResult)) { - errorCatch(errorPrint(directoryResult)); - device->state = SAVE_DEVICE_STATE_UNAVAILABLE; - device->reasonKey = "save.linux.no_home"; - return saveDeviceFireCallback(device); - } + if(errorIsNotOk(directoryResult)) errorCatch(errorPrint(directoryResult)); // Mkdirp the save directory. int mkdirpResult = mkdirp(directory, 0700); @@ -66,10 +64,10 @@ void saveDeviceLinuxCheckAvailability(savedevice_t *device) { errorret_t saveDeviceLinuxGetDirectory(char_t *dest, const size_t destSize) { assertNotNull(dest, "dest cannot be null"); - const char_t *home = getenv("HOME"); - if(home == NULL) errorThrow("HOME environment variable is not set"); - - stringFormat(dest, destSize, "%s/%s", home, SAVE_DEVICE_LINUX_DIRECTORY_NAME); + stringFormat( + dest, destSize, "%s/%s", + ASSET.baseDirectory, SAVE_DEVICE_LINUX_SAVES_SUBDIR_NAME + ); errorOk(); } diff --git a/src/dusklinux/save/savedevicelinux.h b/src/dusklinux/save/savedevicelinux.h index c1920ec3..99ccbf89 100644 --- a/src/dusklinux/save/savedevicelinux.h +++ b/src/dusklinux/save/savedevicelinux.h @@ -8,7 +8,11 @@ #pragma once #include "error/error.h" -#define SAVE_DEVICE_LINUX_DIRECTORY_NAME ".dusk/saves" +// Subdirectory of ASSET.baseDirectory (the directory dusk.dsk was found +// in - see its own comment) saves are kept in - a subdirectory, unlike +// the other platforms' single combined-blob file, since Linux keeps +// settings and each save slot as their own separate file. +#define SAVE_DEVICE_LINUX_SAVES_SUBDIR_NAME "saves" #define SAVE_DEVICE_LINUX_SETTINGS_FILENAME "settings.json" #define SAVE_DEVICE_LINUX_SLOT_FILENAME_FORMAT "slot%u.json" #define SAVE_DEVICE_LINUX_PATH_MAX FILENAME_MAX diff --git a/src/duskpsp/asset/assetpbp.c b/src/duskpsp/asset/assetpbp.c index b55c5ee9..dc7dd424 100644 --- a/src/duskpsp/asset/assetpbp.c +++ b/src/duskpsp/asset/assetpbp.c @@ -9,12 +9,21 @@ #include "asset/assetdsk.h" #include "assert/assert.h" #include "util/memory.h" +#include "util/string.h" errorret_t assetInitPBP(const char_t *pbpPath) { assertNotNull(pbpPath, "PBP path cannot be null."); assertStrLenMin(pbpPath, 1, "PBP path cannot be empty."); assertStrLenMax(pbpPath, ASSET_PBP_PATH_MAX, "PBP path is too long."); + // Save devices with a real filesystem path (PSP) reuse this as their + // base directory - see ASSET.baseDirectory's own comment. dusk.dsk + // itself is embedded inside this PBP rather than a loose file, so this + // is the directory the PBP (and thus the game) actually lives in. + stringCopy(ASSET.baseDirectory, pbpPath, ASSET_BASE_DIRECTORY_MAX); + char_t *lastSlash = stringFindLastChar(ASSET.baseDirectory, '/'); + if(lastSlash != NULL) *lastSlash = '\0'; + FILE *pbpFile = fopen(pbpPath, "rb"); if(pbpFile == NULL) { errorThrow("Failed to open PBP file: %s", pbpPath); diff --git a/src/duskpsp/save/savedeviceplatform.h b/src/duskpsp/save/savedeviceplatform.h index 4942b780..a992c259 100644 --- a/src/duskpsp/save/savedeviceplatform.h +++ b/src/duskpsp/save/savedeviceplatform.h @@ -23,9 +23,10 @@ #define saveDeviceDataWritePlatform saveDevicePSPDataWrite #define saveDeviceDataReadPlatform saveDevicePSPDataRead -#ifndef SAVE_DEVICE_PSP_DIRECTORY_NAME - #define SAVE_DEVICE_PSP_DIRECTORY_NAME "ms0:/DUSK" -#endif +// Data file lives directly in ASSET.baseDirectory (the directory EBOOT.PBP +// was launched from - see its own comment) rather than a separate fixed +// directory, so it doesn't need creating: that directory is already known +// to exist, since the running EBOOT.PBP lives there. #ifndef SAVE_DEVICE_PSP_DATA_FILENAME #define SAVE_DEVICE_PSP_DATA_FILENAME "save.dat" #endif diff --git a/src/duskpsp/save/savedevicepsp.c b/src/duskpsp/save/savedevicepsp.c index 19f94d38..8eadee92 100644 --- a/src/duskpsp/save/savedevicepsp.c +++ b/src/duskpsp/save/savedevicepsp.c @@ -7,6 +7,7 @@ #include "save/savedevice.h" #include "save/savedevicepsp.h" +#include "asset/asset.h" #include "assert/assert.h" #include "util/memory.h" #include "util/string.h" @@ -64,7 +65,7 @@ errorret_t saveDevicePSPGetDataPath( stringFormat( dest, destSize, "%s/%s", - SAVE_DEVICE_PSP_DIRECTORY_NAME, SAVE_DEVICE_PSP_DATA_FILENAME + ASSET.baseDirectory, SAVE_DEVICE_PSP_DATA_FILENAME ); errorOk(); @@ -78,9 +79,6 @@ errorret_t saveDevicePSPDataWrite( assertNotNull(device, "device cannot be null"); assertNotNull(buffer, "buffer cannot be null"); - // Best-effort create - a fresh memory stick won't have this directory yet. - sceIoMkdir(SAVE_DEVICE_PSP_DIRECTORY_NAME, 0777); - char_t finalPath[SAVE_DEVICE_PSP_PATH_MAX]; errorChain(saveDevicePSPGetDataPath(finalPath, sizeof(finalPath)));