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:
fgsfds committed 2023-09-13 20:36:42 +02:00
1 parent fff2fcae21
commit 53e881b75e
8 files changed
+174 -36

No files matched your search

+2 -1
View File
@@ -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);
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}