diff --git a/port/include/config.h b/port/include/config.h index c248b9c2b..c48550345 100644 --- a/port/include/config.h +++ b/port/include/config.h @@ -2,7 +2,8 @@ #include -#define CONFIG_FNAME "./pd.ini" +#define CONFIG_FNAME "pd.ini" +#define CONFIG_PATH "$S/" CONFIG_FNAME void configInit(void); diff --git a/port/include/system.h b/port/include/system.h index cc9d4277e..337593dde 100644 --- a/port/include/system.h +++ b/port/include/system.h @@ -26,6 +26,7 @@ void sysFatalError(const char *fmt, ...) __attribute__((noreturn)); void sysLogPrintf(s32 level, const char *fmt, ...); void sysGetExecutablePath(char *outPath, const u32 outLen); +void sysGetHomePath(char *outPath, const u32 outLen); void *sysMemAlloc(const u32 size); void *sysMemZeroAlloc(const u32 size); diff --git a/port/src/config.c b/port/src/config.c index 293485ead..4e6f52161 100644 --- a/port/src/config.c +++ b/port/src/config.c @@ -304,7 +304,7 @@ s32 configLoad(const char *fname) void configInit(void) { - if (fsFileSize(CONFIG_FNAME) > 0) { - configLoad(CONFIG_FNAME); + if (fsFileSize(CONFIG_PATH) > 0) { + configLoad(CONFIG_PATH); } } diff --git a/port/src/fs.c b/port/src/fs.c index e5a582012..79d284155 100644 --- a/port/src/fs.c +++ b/port/src/fs.c @@ -2,34 +2,70 @@ #include #include #include +#include +#include #include #include #include "config.h" #include "system.h" +#include "platform.h" #include "fs.h" -static char baseDir[FS_MAXPATH + 1]; -static char exeDir[FS_MAXPATH + 1]; +#define DEFAULT_DATADIR_NAME "data" + +static char dataDir[FS_MAXPATH + 1]; // replaces $D +static char saveDir[FS_MAXPATH + 1]; // replaces $S +static char homeDir[FS_MAXPATH + 1]; // replaces $H +static char exeDir[FS_MAXPATH + 1]; // replaces $E + +static s32 fsPathIsWritable(const char *path) { +#ifdef PLATFORM_WIN32 + // on windows access() on directories will only check if the directory exists, so + char tmp[FS_MAXPATH + 1] = { 0 }; + snprintf(tmp, sizeof(tmp), "%s/.tmp", path); + FILE *f = fopen(tmp, "wb"); + if (f) { + fclose(f); + remove(tmp); + return 1; + } + return 0; +#else + return (access(path, W_OK) == 0); +#endif +} const char *fsFullPath(const char *relPath) { static char pathBuf[FS_MAXPATH + 1]; - if (relPath[0] == '!') { - // path relative to the exe - const u32 len = strlen(exeDir); - if (len > 0) { - memcpy(pathBuf, exeDir, len); - strncpy(pathBuf + len, relPath + 1, FS_MAXPATH - len); - return pathBuf; + if (relPath[0] == '$') { + // expandable placeholder $X; will be replaced with the corresponding path, if any + const char *expStr = NULL; + switch (relPath[1]) { + case 'E': expStr = exeDir; break; + case 'H': expStr = homeDir; break; + case 'D': expStr = dataDir; break; + case 'S': expStr = saveDir; break; + default: break; } - } else if (relPath[0] == '.') { - // user explicitly wants working directory + if (expStr) { + const u32 len = strlen(expStr); + if (len > 0) { + memcpy(pathBuf, expStr, len); + strncpy(pathBuf + len, relPath + 2, FS_MAXPATH - len); + return pathBuf; + } + } + // couldn't expand anything, return as is + return relPath; + } else if (!dataDir[0] || relPath[0] == '.' || relPath[0] == '/' || (isalpha(relPath[0]) && relPath[1] == ':')) { + // user explicitly wants working directory or this is an absolute path or we have no dataDir set up yet return relPath; } - // path relative to basedir - snprintf(pathBuf, FS_MAXPATH, "%s/%s", baseDir, relPath); + // path relative to data dir + snprintf(pathBuf, FS_MAXPATH, "%s/%s", dataDir, relPath); return pathBuf; } @@ -37,14 +73,60 @@ s32 fsInit(void) { sysGetExecutablePath(exeDir, FS_MAXPATH); - // get path to data dir and expand it if needed - const char *path = configGetString("Game.BaseDir", "./data"); - if (path[0] == '!') { - strncpy(baseDir, fsFullPath(path), FS_MAXPATH); + // if this is set, default to exe path for everything + const s32 portable = sysArgCheck("--portable"); + if (portable) { + strcpy(homeDir, exeDir); } else { - strncpy(baseDir, path, FS_MAXPATH); + sysGetHomePath(homeDir, FS_MAXPATH); } + // get path to data dir and expand it if needed + const char *path = sysArgGetString("--datadir"); + if (!path) { + // check if there's a data directory in working directory or homeDir, otherwise default to exe directory + path = "$E/" DEFAULT_DATADIR_NAME; + if (!portable) { + if (fsFileSize("./" DEFAULT_DATADIR_NAME) >= 0) { + path = "./" DEFAULT_DATADIR_NAME; + } else if (fsFileSize("$H/" DEFAULT_DATADIR_NAME)) { + path = "$H/" DEFAULT_DATADIR_NAME; + } + } + } + + strncpy(dataDir, fsFullPath(path), FS_MAXPATH); + + // get path to save dir and expand it if needed + path = sysArgGetString("--savedir"); + if (!path) { + if (portable) { + path = "$E"; + } else { +#ifdef PLATFORM_LINUX + // check if there's a config in the working directory, otherwise default to homeDir + if (fsFileSize("./" CONFIG_FNAME) >= 0) { + path = "."; + } else { + path = "$H"; + } +#else + // check if working directory is writable, otherwise default to homeDir + if (fsPathIsWritable("./")) { + path = "."; + } else { + sysLogPrintf(LOG_WARNING, "cannot write to working directory, will use %s for saves instead", homeDir); + path = "$H"; + } +#endif + } + } + + strncpy(saveDir, fsFullPath(path), FS_MAXPATH); + + sysLogPrintf(LOG_NOTE, "data dir: %s", dataDir); + sysLogPrintf(LOG_NOTE, "save dir: %s", saveDir); + return 0; } diff --git a/port/src/libultra.c b/port/src/libultra.c index bb642a5cf..aa1f7522a 100644 --- a/port/src/libultra.c +++ b/port/src/libultra.c @@ -13,9 +13,10 @@ #include "fs.h" #define EEPROM_SIZE (EEP16K_MAXBLOCKS * 8) -#define EEPROM_FNAME "./eeprom.bin" -#define OS_COUNTER_RATE 46875000ULL +#define EEPROM_FNAME "eeprom.bin" +#define EEPROM_PATH "$S/" EEPROM_FNAME +#define OS_COUNTER_RATE 46875000ULL #define OS_COUNTER_NUM (OS_COUNTER_RATE / 1000ULL) #define OS_COUNTER_DEN (1000000ULL / 1000ULL) @@ -260,7 +261,7 @@ static inline void osEeepromLoad(const char *fname) fread(eeprom, 1, EEPROM_SIZE, fp); fsFileClose(fp); } else { - sysLogPrintf(LOG_NOTE, "could not read EEPROM from `%s`: %s", fname, strerror(errno)); + sysLogPrintf(LOG_NOTE, "could not read EEPROM from `%s`: %s", fsFullPath(fname), strerror(errno)); } } } @@ -272,7 +273,7 @@ static inline void osEeepromSave(const char *fname) fwrite(eeprom, 1, EEPROM_SIZE, fp); fsFileClose(fp); } else { - sysLogPrintf(LOG_ERROR, "could not save EEPROM to `%s`: %s", fname, strerror(errno)); + sysLogPrintf(LOG_ERROR, "could not save EEPROM to `%s`: %s", fsFullPath(fname), strerror(errno)); } } @@ -283,7 +284,7 @@ s32 osEepromProbe(OSMesgQueue *mq) s32 osEepromLongRead(OSMesgQueue *mq, u8 address, u8 *buffer, int nbytes) { - osEeepromLoad(EEPROM_FNAME); + osEeepromLoad(EEPROM_PATH); memcpy(buffer, eeprom + address * 8, nbytes); @@ -292,11 +293,11 @@ s32 osEepromLongRead(OSMesgQueue *mq, u8 address, u8 *buffer, int nbytes) s32 osEepromLongWrite(OSMesgQueue *mq, u8 address, u8 *buffer, int nbytes) { - osEeepromLoad(EEPROM_FNAME); + osEeepromLoad(EEPROM_PATH); memcpy(eeprom + address * 8, buffer, nbytes); - osEeepromSave(EEPROM_FNAME); + osEeepromSave(EEPROM_PATH); return 0; } diff --git a/port/src/main.c b/port/src/main.c index 0791d8580..47cc173d3 100644 --- a/port/src/main.c +++ b/port/src/main.c @@ -59,7 +59,7 @@ static void cleanup(void) { sysLogPrintf(LOG_NOTE, "shutdown"); inputSaveConfig(); - configSave(CONFIG_FNAME); + configSave(CONFIG_PATH); crashShutdown(); // TODO: actually shut down all subsystems } diff --git a/port/src/romdata.c b/port/src/romdata.c index 41f9957e0..10367d36f 100644 --- a/port/src/romdata.c +++ b/port/src/romdata.c @@ -152,7 +152,7 @@ static inline void romdataLoadRom(void) g_RomFile = fsFileLoad(romName, &g_RomFileSize); if (!g_RomFile) { - sysFatalError("Could not open ROM file %s.\nEnsure that it is in the data directory.", romName); + sysFatalError("Could not open ROM file %s.\nEnsure that it is in the %s directory.", romName, fsFullPath("")); } if (g_RomFileSize != ROMDATA_ROM_SIZE) { diff --git a/port/src/system.c b/port/src/system.c index 73cd1727c..6cfcafdcd 100644 --- a/port/src/system.c +++ b/port/src/system.c @@ -7,13 +7,14 @@ #include #include #include +#include "platform.h" #include "system.h" -#define LOG_FNAME "./pd.log" +#define LOG_FNAME "pd.log" #define USEC_IN_SEC 1000000ULL static u64 startTick = 0; -static s32 logToFile = 0; +static char logPath[2048]; static s32 sysArgc; static const char **sysArgv; @@ -25,10 +26,17 @@ void sysInit(s32 argc, const char **argv) sysArgc = argc; sysArgv = argv; - logToFile = sysArgCheck("--log"); - if (logToFile) { - // clear log - FILE *f = fopen(LOG_FNAME, "wb"); + if (sysArgCheck("--log")) { + // figure out where the log is and clear it + // try working dir first + snprintf(logPath, sizeof(logPath), "./%s", LOG_FNAME); + FILE *f = fopen(logPath, "wb"); + if (!f) { + // try home dir + sysGetHomePath(logPath, sizeof(logPath) - 1); + strncat(logPath, "/" LOG_FNAME, sizeof(logPath) - 1); + f = fopen(logPath, "wb"); + } if (f) { fclose(f); } @@ -98,8 +106,8 @@ void sysLogPrintf(s32 level, const char *fmt, ...) vsnprintf(logmsg, sizeof(logmsg), fmt, ap); va_end(ap); - if (logToFile) { - FILE *f = fopen(LOG_FNAME, "ab"); + if (logPath[0]) { + FILE *f = fopen(logPath, "ab"); if (f) { fprintf(f, "%s%s\n", prefix[level], logmsg); fclose(f); @@ -141,6 +149,41 @@ void sysGetExecutablePath(char *outPath, const u32 outLen) { // try asking SDL char *sdlPath = SDL_GetBasePath(); + + if (sdlPath && *sdlPath) { + // -1 to trim trailing slash + const u32 len = strlen(sdlPath) - 1; + if (len < outLen) { + memcpy(outPath, sdlPath, len); + outPath[len] = '\0'; + } + } else if (sysArgc && sysArgv[0] && sysArgv[0][0]) { + // get exe path from argv[0] + strncpy(outPath, sysArgv[0], outLen - 1); + outPath[outLen - 1] = '\0'; + } else if (outLen > 1) { + // give up, use working directory instead + outPath[0] = '.'; + outPath[1] = '\0'; + } + +#ifdef PLATFORM_WIN32 + // replace all backslashes with forward slashes, windows supports both + for (u32 i = 0; i < outLen && outPath[i]; ++i) { + if (outPath[i] == '\\') { + outPath[i] = '/'; + } + } +#endif + + SDL_free(sdlPath); +} + +void sysGetHomePath(char *outPath, const u32 outLen) +{ + // try asking SDL + char *sdlPath = SDL_GetPrefPath("", "perfectdark"); + if (sdlPath && *sdlPath) { // -1 to trim trailing slash const u32 len = strlen(sdlPath) - 1; @@ -153,6 +196,16 @@ void sysGetExecutablePath(char *outPath, const u32 outLen) outPath[0] = '.'; outPath[1] = '\0'; } + +#ifdef PLATFORM_WIN32 + // replace all backslashes with forward slashes, windows supports both + for (u32 i = 0; i < outLen && outPath[i]; ++i) { + if (outPath[i] == '\\') { + outPath[i] = '/'; + } + } +#endif + SDL_free(sdlPath); }