mirror of
https://github.com/perfect-dark-pc-port/perfect_dark.git
synced 2026-10-24 00:02:43 +02:00
210 lines
4.8 KiB
C
210 lines
4.8 KiB
C
#include <stdlib.h>
|
|
#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"
|
|
|
|
#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
|
|
}
|
|
|
|
s32 fsPathIsAbsolute(const char *path)
|
|
{
|
|
return (path[0] == '/' || (isalpha(path[0]) && path[1] == ':'));
|
|
}
|
|
|
|
s32 fsPathIsCwdRelative(const char *path)
|
|
{
|
|
// ., .., ./, ../
|
|
return (path[0] == '.' && (path[1] == '.' || path[1] == '/' || path[1] == '\\' || path[1] == '\0'));
|
|
}
|
|
|
|
const char *fsFullPath(const char *relPath)
|
|
{
|
|
static char pathBuf[FS_MAXPATH + 1];
|
|
|
|
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, 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] || fsPathIsAbsolute(relPath) || fsPathIsCwdRelative(relPath)) {
|
|
// user explicitly wants working directory or this is an absolute path or we have no dataDir set up yet
|
|
return relPath;
|
|
}
|
|
|
|
// path relative to data dir
|
|
snprintf(pathBuf, FS_MAXPATH, "%s/%s", dataDir, relPath);
|
|
return pathBuf;
|
|
}
|
|
|
|
s32 fsInit(void)
|
|
{
|
|
sysGetExecutablePath(exeDir, FS_MAXPATH);
|
|
|
|
// if this is set, default to exe path for everything
|
|
const s32 portable = sysArgCheck("--portable");
|
|
if (portable) {
|
|
strcpy(homeDir, exeDir);
|
|
} else {
|
|
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;
|
|
}
|
|
|
|
void *fsFileLoad(const char *name, u32 *outSize)
|
|
{
|
|
const char *fullName = fsFullPath(name);
|
|
|
|
FILE *f = fopen(fullName, "rb");
|
|
if (!f) {
|
|
sysLogPrintf(LOG_ERROR, "fsFileLoad: could not find file: %s", fullName);
|
|
return NULL;
|
|
}
|
|
|
|
fseek(f, 0, SEEK_END);
|
|
const s32 size = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
|
|
if (size < 0) {
|
|
sysLogPrintf(LOG_ERROR, "fsFileLoad: empty file or invalid size (%d): %s", size, fullName);
|
|
fclose(f);
|
|
return NULL;
|
|
}
|
|
|
|
void *buf = NULL;
|
|
if (size) {
|
|
buf = sysMemZeroAlloc(size + 1); // sick hack for a free null terminator
|
|
if (!buf) {
|
|
sysLogPrintf(LOG_ERROR, "fsFileLoad: could not alloc %d bytes for file: %s", size, fullName);
|
|
fclose(f);
|
|
return NULL;
|
|
}
|
|
fread(buf, 1, size, f);
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
if (outSize) {
|
|
*outSize = size;
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
s32 fsFileSize(const char *name)
|
|
{
|
|
const char *fullName = fsFullPath(name);
|
|
struct stat st;
|
|
if (stat(fullName, &st) < 0) {
|
|
return -1;
|
|
} else {
|
|
return st.st_size;
|
|
}
|
|
}
|
|
|
|
FILE *fsFileOpenWrite(const char *name)
|
|
{
|
|
return fopen(fsFullPath(name), "wb");
|
|
}
|
|
|
|
FILE *fsFileOpenRead(const char *name)
|
|
{
|
|
return fopen(fsFullPath(name), "rb");
|
|
}
|
|
|
|
void fsFileClose(FILE *f)
|
|
{
|
|
fclose(f);
|
|
}
|