forked from Ports/perfect_dark
port: support loading/saving from/to a directory in HOME or APPDATA
~/.local/share/perfectdark on Linux AppData/Roaming/perfectdark on Windows on Windows if the game has write access to the working directory it will save there by default, otherwise it will save to AppData on Linux the game will save to the working directory if there is already a config there, otherwise it will save to ~ additionally the ROM/data files will be loaded from wherever they're found, working directory taking priority
This commit is contained in:
1 parent
fff2fcae21
commit
53e881b75e
8 files changed
+174
-36
No files matched your search
@@ -2,7 +2,8 @@
|
||||
|
||||
#include <PR/ultratypes.h>
|
||||
|
||||
#define CONFIG_FNAME "./pd.ini"
|
||||
#define CONFIG_FNAME "pd.ini"
|
||||
#define CONFIG_PATH "$S/" CONFIG_FNAME
|
||||
|
||||
void configInit(void);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
+98
-16
@@ -2,34 +2,70 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <ctype.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <PR/ultratypes.h>
|
||||
#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 (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;
|
||||
}
|
||||
if (expStr) {
|
||||
const u32 len = strlen(expStr);
|
||||
if (len > 0) {
|
||||
memcpy(pathBuf, exeDir, len);
|
||||
strncpy(pathBuf + len, relPath + 1, FS_MAXPATH - len);
|
||||
memcpy(pathBuf, expStr, len);
|
||||
strncpy(pathBuf + len, relPath + 2, FS_MAXPATH - len);
|
||||
return pathBuf;
|
||||
}
|
||||
} else if (relPath[0] == '.') {
|
||||
// user explicitly wants working directory
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
+8
-7
@@ -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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
+61
-8
@@ -7,13 +7,14 @@
|
||||
#include <sys/time.h>
|
||||
#include <SDL.h>
|
||||
#include <PR/ultratypes.h>
|
||||
#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);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user