2020-05-25 06:17:10 +02:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
2020-05-25 23:54:51 +02:00
|
|
|
#include <dirent.h>
|
|
|
|
#include <ctype.h>
|
2020-05-25 06:17:10 +02:00
|
|
|
#ifdef _WIN32
|
|
|
|
#include <direct.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "cliopts.h"
|
|
|
|
|
2020-05-25 23:54:51 +02:00
|
|
|
/* these are not available on some platforms, so might as well */
|
|
|
|
|
|
|
|
char *sys_strlwr(char *src) {
|
|
|
|
for (unsigned char *p = (unsigned char *)src; *p; p++)
|
|
|
|
*p = tolower(*p);
|
|
|
|
return src;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *sys_strdup(const char *src) {
|
|
|
|
const unsigned len = strlen(src) + 1;
|
|
|
|
char *newstr = malloc(len);
|
|
|
|
if (newstr) memcpy(newstr, src, len);
|
|
|
|
return newstr;
|
|
|
|
}
|
|
|
|
|
|
|
|
int sys_strcasecmp(const char *s1, const char *s2) {
|
|
|
|
const unsigned char *p1 = (const unsigned char *) s1;
|
|
|
|
const unsigned char *p2 = (const unsigned char *) s2;
|
|
|
|
int result;
|
|
|
|
if (p1 == p2)
|
|
|
|
return 0;
|
|
|
|
while ((result = tolower(*p1) - tolower(*p2++)) == 0)
|
|
|
|
if (*p1++ == '\0')
|
|
|
|
break;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* file system stuff */
|
|
|
|
|
|
|
|
bool sys_file_exists(const char *name) {
|
|
|
|
struct stat st;
|
|
|
|
return (stat(name, &st) == 0 && S_ISREG(st.st_mode));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool sys_dir_exists(const char *name) {
|
2020-05-25 06:17:10 +02:00
|
|
|
struct stat st;
|
2020-05-25 23:54:51 +02:00
|
|
|
return (stat(name, &st) == 0 && S_ISDIR(st.st_mode));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool sys_dir_walk(const char *base, walk_fn_t walk, const bool recur) {
|
|
|
|
char fullpath[SYS_MAX_PATH];
|
|
|
|
DIR *dir;
|
|
|
|
struct dirent *ent;
|
|
|
|
|
|
|
|
if (!(dir = opendir(base))) {
|
|
|
|
fprintf(stderr, "sys_dir_walk(): could not open `%s`\n", base);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ret = true;
|
|
|
|
|
|
|
|
while ((ent = readdir(dir)) != NULL) {
|
|
|
|
if (ent->d_name[0] == 0 || ent->d_name[0] == '.') continue; // skip ./.. and hidden files
|
|
|
|
snprintf(fullpath, sizeof(fullpath), "%s/%s", base, ent->d_name);
|
|
|
|
if (sys_dir_exists(fullpath)) {
|
|
|
|
if (recur) {
|
|
|
|
if (!sys_dir_walk(fullpath, walk, recur)) {
|
|
|
|
ret = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!walk(fullpath)) {
|
|
|
|
ret = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
closedir(dir);
|
|
|
|
return ret;
|
2020-05-25 06:17:10 +02:00
|
|
|
}
|
|
|
|
|
2020-06-01 11:13:05 +02:00
|
|
|
void *sys_load_res(const char *name) {
|
|
|
|
char path[SYS_MAX_PATH] = { 0 };
|
|
|
|
snprintf(path, sizeof(path), "%s/%s", sys_data_path(), name);
|
|
|
|
|
|
|
|
FILE *f = fopen(path, "rb");
|
|
|
|
if (!f) return NULL;
|
|
|
|
|
|
|
|
fseek(f, 0, SEEK_END);
|
|
|
|
size_t size = ftell(f);
|
|
|
|
fseek(f, 0, SEEK_SET);
|
|
|
|
|
|
|
|
void *buf = malloc(size);
|
|
|
|
if (!buf) {
|
|
|
|
fclose(f);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
fread(buf, 1, size, f);
|
|
|
|
fclose(f);
|
|
|
|
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
2020-05-25 06:17:10 +02:00
|
|
|
bool sys_mkdir(const char *name) {
|
|
|
|
#ifdef _WIN32
|
|
|
|
return _mkdir(name) == 0;
|
|
|
|
#else
|
|
|
|
return mkdir(name, 0777) == 0;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
#if USE_SDL
|
|
|
|
|
|
|
|
// we can just ask SDL for most of this shit if we have it
|
|
|
|
#include <SDL2/SDL.h>
|
|
|
|
|
|
|
|
const char *sys_data_path(void) {
|
|
|
|
static char path[SYS_MAX_PATH] = { 0 };
|
|
|
|
|
|
|
|
if (!path[0]) {
|
|
|
|
// prefer the override, if it is set
|
|
|
|
// "!" expands to executable path
|
|
|
|
if (gCLIOpts.DataPath[0]) {
|
|
|
|
if (gCLIOpts.DataPath[0] == '!')
|
|
|
|
snprintf(path, sizeof(path), "%s%s", sys_exe_path(), gCLIOpts.DataPath + 1);
|
|
|
|
else
|
|
|
|
snprintf(path, sizeof(path), "%s", gCLIOpts.DataPath);
|
2020-05-25 23:54:51 +02:00
|
|
|
if (sys_dir_exists(path)) return path;
|
2020-05-25 06:17:10 +02:00
|
|
|
printf("Warning: Specified data path ('%s') doesn't exist\n", path);
|
|
|
|
}
|
|
|
|
|
|
|
|
// then the executable directory
|
|
|
|
snprintf(path, sizeof(path), "%s/" DATADIR, sys_exe_path());
|
2020-05-25 23:54:51 +02:00
|
|
|
if (sys_dir_exists(path)) return path;
|
2020-05-25 06:17:10 +02:00
|
|
|
|
|
|
|
// then the save path
|
|
|
|
snprintf(path, sizeof(path), "%s/" DATADIR, sys_save_path());
|
2020-05-25 23:54:51 +02:00
|
|
|
if (sys_dir_exists(path)) return path;
|
2020-05-25 06:17:10 +02:00
|
|
|
|
|
|
|
#if defined(__linux__) || defined(__unix__)
|
|
|
|
// on Linux/BSD try some common paths for read-only data
|
|
|
|
const char *try[] = {
|
|
|
|
"/usr/local/share/sm64pc/" DATADIR,
|
|
|
|
"/usr/share/sm64pc/" DATADIR,
|
|
|
|
"/opt/sm64pc/" DATADIR,
|
|
|
|
};
|
|
|
|
for (unsigned i = 0; i < sizeof(try) / sizeof(try[0]); ++i) {
|
2020-05-25 23:54:51 +02:00
|
|
|
if (sys_dir_exists(try[i])) {
|
2020-05-25 06:17:10 +02:00
|
|
|
strcpy(path, try[i]);
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// hope for the best
|
|
|
|
strcpy(path, "./" DATADIR);
|
|
|
|
}
|
|
|
|
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *sys_save_path(void) {
|
|
|
|
static char path[SYS_MAX_PATH] = { 0 };
|
|
|
|
|
|
|
|
if (!path[0]) {
|
|
|
|
// if the override is set, use that
|
|
|
|
// "!" expands to executable path
|
|
|
|
if (gCLIOpts.SavePath[0]) {
|
|
|
|
if (gCLIOpts.SavePath[0] == '!')
|
|
|
|
snprintf(path, sizeof(path), "%s%s", sys_exe_path(), gCLIOpts.SavePath + 1);
|
|
|
|
else
|
|
|
|
snprintf(path, sizeof(path), "%s", gCLIOpts.SavePath);
|
2020-05-25 23:54:51 +02:00
|
|
|
if (!sys_dir_exists(path) && !sys_mkdir(path)) {
|
2020-05-25 06:17:10 +02:00
|
|
|
printf("Warning: Specified save path ('%s') doesn't exist and can't be created\n", path);
|
|
|
|
path[0] = 0; // doesn't exist and no write access
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// didn't work? get it from SDL
|
|
|
|
if (!path[0]) {
|
|
|
|
char *sdlpath = SDL_GetPrefPath("", "sm64pc");
|
|
|
|
if (sdlpath) {
|
|
|
|
const unsigned int len = strlen(sdlpath);
|
|
|
|
strncpy(path, sdlpath, sizeof(path));
|
|
|
|
path[sizeof(path)-1] = 0;
|
|
|
|
SDL_free(sdlpath);
|
|
|
|
if (path[len-1] == '/' || path[len-1] == '\\')
|
|
|
|
path[len-1] = 0; // strip the trailing separator
|
2020-05-25 23:54:51 +02:00
|
|
|
if (!sys_dir_exists(path) && !sys_mkdir(path))
|
2020-05-25 06:17:10 +02:00
|
|
|
path[0] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if all else fails, just store near the EXE
|
|
|
|
if (!path[0])
|
|
|
|
strcpy(path, sys_exe_path());
|
|
|
|
|
|
|
|
printf("Save path set to '%s'\n", path);
|
|
|
|
}
|
|
|
|
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *sys_exe_path(void) {
|
|
|
|
static char path[SYS_MAX_PATH] = { 0 };
|
|
|
|
|
|
|
|
if (!path[0]) {
|
|
|
|
char *sdlpath = SDL_GetBasePath();
|
|
|
|
if (sdlpath) {
|
|
|
|
// use the SDL path if it exists
|
|
|
|
const unsigned int len = strlen(sdlpath);
|
|
|
|
strncpy(path, sdlpath, sizeof(path));
|
|
|
|
path[sizeof(path)-1] = 0;
|
|
|
|
SDL_free(sdlpath);
|
|
|
|
if (path[len-1] == '/' || path[len-1] == '\\')
|
|
|
|
path[len-1] = 0; // strip the trailing separator
|
|
|
|
} else {
|
|
|
|
// hope for the best
|
|
|
|
strcpy(path, ".");
|
|
|
|
}
|
|
|
|
printf("Executable path set to '%s'\n", path);
|
|
|
|
}
|
|
|
|
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
#warning "You might want to implement these functions for your platform"
|
|
|
|
|
|
|
|
const char *sys_data_path(void) {
|
|
|
|
return ".";
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *sys_save_path(void) {
|
|
|
|
return ".";
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *sys_exe_path(void) {
|
|
|
|
return ".";
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // platform switch
|