Files
perfect_dark/port/src/fs.c
T

294 lines
6.9 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 "utils.h"
#include "fs.h"
#ifdef PLATFORM_WIN32
#include <direct.h>
#endif
#define DEFAULT_BASEDIR_NAME "data"
static char baseDir[FS_MAXPATH + 1]; // replaces $B
static char modDir[FS_MAXPATH + 1]; // replaces $M
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 'M': expStr = modDir; break;
case 'B': expStr = baseDir; 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 (!baseDir[0] || fsPathIsAbsolute(relPath) || fsPathIsCwdRelative(relPath)) {
// user explicitly wants working directory or this is an absolute path or we have no baseDir set up yet
return relPath;
}
// path relative to mod or base dir; this will be a read request, so check where the file actually is
if (modDir[0]) {
snprintf(pathBuf, FS_MAXPATH, "%s/%s", modDir, relPath);
if (fsFileSize(pathBuf) >= 0) {
return pathBuf;
}
}
// fall back to basedir
snprintf(pathBuf, FS_MAXPATH, "%s/%s", baseDir, 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 base dir and expand it if needed
const char *path = sysArgGetString("--basedir");
if (!path) {
// check if there's a `data` directory in working directory or homeDir, otherwise default to exe directory
path = "$E/" DEFAULT_BASEDIR_NAME;
if (!portable) {
if (fsFileSize("./" DEFAULT_BASEDIR_NAME) >= 0) {
path = "./" DEFAULT_BASEDIR_NAME;
} else if (fsFileSize("$H/" DEFAULT_BASEDIR_NAME)) {
path = "$H/" DEFAULT_BASEDIR_NAME;
}
}
}
strncpy(baseDir, fsFullPath(path), FS_MAXPATH);
// get path to mod dir and expand it if needed
// mod directory is overlaid on top of base directory
path = sysArgGetString("--moddir");
if (path) {
if (fsPathIsAbsolute(path) || fsPathIsCwdRelative(path) || path[0] == '$') {
// path is explicit; check as-is
if (fsFileSize(path) >= 0) {
strncpy(modDir, fsFullPath(path), FS_MAXPATH);
}
} else {
// path is relative to workdir; try to find it
const char *priority[] = { ".", "$E", "$H" };
for (s32 i = 0; i < 2 + (portable != 0); ++i) {
char *tmp = strFmt("%s/%s", priority[i], path);
if (fsFileSize(tmp) >= 0) {
strncpy(modDir, fsFullPath(tmp), FS_MAXPATH);
break;
}
}
}
if (!modDir[0]) {
sysLogPrintf(LOG_WARNING, "could not find specified moddir `%s`", path);
}
}
// get path to save dir and expand it if needed
path = sysArgGetString("--savedir");
if (!path) {
if (portable) {
path = "$E";
} else {
#if defined(PLATFORM_LINUX) || defined(PLATFORM_OSX)
// 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);
if (modDir[0]) {
sysLogPrintf(LOG_NOTE, " mod dir: %s", modDir);
}
sysLogPrintf(LOG_NOTE, "base dir: %s", baseDir);
sysLogPrintf(LOG_NOTE, "save dir: %s", saveDir);
return 0;
}
const char *fsGetModDir(void)
{
return modDir[0] ? modDir : NULL;
}
s32 fsFileLoadTo(const char *name, void *dst, u32 dstSize)
{
const char *fullName = fsFullPath(name);
FILE *f = fopen(fullName, "rb");
if (!f) {
return -1;
}
fseek(f, 0, SEEK_END);
const s32 size = ftell(f);
fseek(f, 0, SEEK_SET);
if (size < 0) {
sysLogPrintf(LOG_ERROR, "fsFileLoadTo: empty file or invalid size (%d): %s", size, fullName);
fclose(f);
return -1;
}
if ((u32)size > dstSize) {
sysLogPrintf(LOG_ERROR, "fsFileLoadTo: file too big for buffer (%u > %u): %s", size, dstSize, fullName);
fclose(f);
return -1;
}
fread(dst, 1, size, f);
fclose(f);
return size;
}
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 fsFileFree(FILE *f)
{
fclose(f);
}
s32 fsCreateDir(const char *path) {
#ifdef PLATFORM_WIN32
return _mkdir(fsFullPath(path));
#else
return mkdir(fsFullPath(path), S_IRWXU | S_IRWXG);
#endif
}