diff --git a/Makefile b/Makefile index b4b82edf..49d95ee3 100644 --- a/Makefile +++ b/Makefile @@ -785,7 +785,7 @@ ifneq ($(SDL1_USED)$(SDL2_USED),00) endif ifeq ($(WINDOWS_BUILD),1) - BACKEND_LDFLAGS += `$(SDLCONFIG) --static-libs` -lsetupapi -luser32 -limm32 -lole32 -loleaut32 -lshell32 -lwinmm -lversion + BACKEND_LDFLAGS += `$(SDLCONFIG) --static-libs` -lsetupapi -luser32 -limm32 -lole32 -loleaut32 -lshell32 -lshlwapi -lwinmm -lversion else BACKEND_LDFLAGS += `$(SDLCONFIG) --libs` endif diff --git a/src/game/player_palette.c b/src/game/player_palette.c index a0b6fda6..83547b30 100644 --- a/src/game/player_palette.c +++ b/src/game/player_palette.c @@ -3,7 +3,6 @@ #include "pc/ini.h" #include "pc/mods/mods.h" #include "pc/mods/mods_utils.h" -#include "pc/os/os.h" #include "player_palette.h" const struct PlayerPalette DEFAULT_MARIO_PALETTE = @@ -64,14 +63,14 @@ void player_palettes_read(const char* palettesPath, bool appendPalettes) { } // open directory - os_dirent* dir = NULL; + struct dirent* dir = NULL; - OS_DIR* d = os_opendir(lpath); + DIR* d = opendir(lpath); if (!d) { return; } // iterate char path[SYS_MAX_PATH] = { 0 }; - while ((dir = os_readdir(d)) != NULL) { + while ((dir = readdir(d)) != NULL) { // sanity check / fill path[] if (!directory_sanity_check(dir, lpath, path)) { continue; } snprintf(path, SYS_MAX_PATH, "%s", os_get_dir_name(dir)); @@ -113,7 +112,7 @@ void player_palettes_read(const char* palettesPath, bool appendPalettes) { if (gPresetPaletteCount >= MAX_PRESET_PALETTES) { break; } } - os_closedir(d); + closedir(d); // this should mean we are in the exe path's palette dir if (appendPalettes) { @@ -128,7 +127,7 @@ void player_palettes_read(const char* palettesPath, bool appendPalettes) { } } } - + // copy remaining palettes for (int i = 0; i < gPresetPaletteCount; i++) { bool isCharacterPalette = false; diff --git a/src/pc/crash_handler.c b/src/pc/crash_handler.c index bd119d78..498ecf28 100644 --- a/src/pc/crash_handler.c +++ b/src/pc/crash_handler.c @@ -40,7 +40,7 @@ static CrashHandlerText sCrashHandlerText[128 + 256 + 4]; #define PTR long long unsigned int)(uintptr_t #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) -#define MEMNEW(typ, cnt) calloc(sizeof(typ), cnt) +#define MEMNEW(typ, cnt) calloc(cnt, sizeof(typ)) #define STRING(str, size, fmt, ...) char str[size]; snprintf(str, size, fmt, __VA_ARGS__); #define BACK_TRACE_SIZE 15 @@ -457,15 +457,9 @@ static void crash_handler(const int signalNum, siginfo_t *info, UNUSED ucontext_ // Load symbols char filename[256] = { 0 }; - if (GetModuleFileName(NULL, filename, sizeof(filename))) { - int index = strlen(filename); - while (--index > 0) { - if (filename[index] == '\\') { - filename[index] = '\0'; - break; - } - } - strncat(filename, "\\coop.map", 255); + const char *exe_path = sys_exe_path(); + if (NULL != exe_path) { + snprintf(filename, 256, "%s/%s", exe_path, "coop.map"); } else { snprintf(filename, 256, "%s", "coop.map"); } diff --git a/src/pc/djui/djui_language.c b/src/pc/djui/djui_language.c index 428d4cd9..e4fe6f59 100644 --- a/src/pc/djui/djui_language.c +++ b/src/pc/djui/djui_language.c @@ -17,12 +17,9 @@ bool djui_language_init(char* lang) { } // construct path - char exePath[SYS_MAX_PATH] = ""; - path_get_folder((char*)path_to_executable(), exePath); - char path[SYS_MAX_PATH] = ""; if (!lang || lang[0] == '\0') { lang = "English"; } - snprintf(path, SYS_MAX_PATH, "%s/lang/%s.ini", exePath, lang); + snprintf(path, SYS_MAX_PATH, "%s/lang/%s.ini", sys_exe_path(), lang); // load sLang = ini_load(path); diff --git a/src/pc/djui/djui_panel_language.c b/src/pc/djui/djui_panel_language.c index 28dd32f2..9dfaa0dc 100644 --- a/src/pc/djui/djui_panel_language.c +++ b/src/pc/djui/djui_panel_language.c @@ -8,7 +8,6 @@ #include "pc/debuglog.h" #include "pc/utils/misc.h" #include "pc/configfile.h" -#include "pc/os/os.h" #include "pc/lua/smlua_hooks.h" #include "game/bettercamera.h" @@ -86,9 +85,9 @@ void djui_panel_language_create(struct DjuiBase* caller) { snprintf(lpath, SYS_MAX_PATH, "%s/lang", sys_exe_path()); // open directory - os_dirent* dir = NULL; + struct dirent* dir = NULL; - OS_DIR* d = os_opendir(lpath); + DIR* d = opendir(lpath); if (!d) { LOG_ERROR("Could not open directory '%s'", lpath); @@ -111,10 +110,10 @@ void djui_panel_language_create(struct DjuiBase* caller) { // iterate char path[SYS_MAX_PATH] = { 0 }; - while ((dir = os_readdir(d)) != NULL) { + while ((dir = readdir(d)) != NULL) { // sanity check / fill path[] //if (!directory_sanity_check(dir, lpath, path)) { continue; } - snprintf(path, SYS_MAX_PATH, "%s", os_get_dir_name(dir)); + snprintf(path, SYS_MAX_PATH, "%s", dir->d_name); // strip the name before the . char* c = path; @@ -130,7 +129,7 @@ void djui_panel_language_create(struct DjuiBase* caller) { if (!strcmp(path, "English")) { chkEnglish = checkbox; } } - os_closedir(d); + closedir(d); if (!foundMatch && chkEnglish) { chkEnglish->value = &sTrue; diff --git a/src/pc/fs/fs.c b/src/pc/fs/fs.c index 04325586..34de1d60 100644 --- a/src/pc/fs/fs.c +++ b/src/pc/fs/fs.c @@ -51,6 +51,11 @@ bool fs_init(const char *writepath) { printf("FS: writepath set to `%s`\n", fs_writepath); #endif + // we shall not progress any further if the path is inaccessible + if ('\0' == fs_writepath[0]) { + sys_fatal("Could not access the User Preferences directory."); + } + fs_mount(fs_writepath); return true; @@ -244,11 +249,13 @@ const char *fs_get_write_path(const char *vpath) { return path; } -const char *fs_convert_path(char *buf, const size_t bufsiz, const char *path) { +const char *fs_convert_path(char *buf, const size_t bufsiz, const char *path) { + if (NULL == path) { return ""; } + // ! means "executable directory" if (path[0] == '!') { - if (snprintf(buf, bufsiz, "%s%s", sys_exe_path(), path + 1) < 0) { - return NULL; + if (snprintf(buf, bufsiz, "%s%s", sys_exe_path(), path + 1) < 0) { + return ""; } } else { strncpy(buf, path, bufsiz); @@ -262,8 +269,33 @@ const char *fs_convert_path(char *buf, const size_t bufsiz, const char *path) { return buf; } +bool fs_sys_filename_is_portable(char const *filename) { + char c; + while (0 != (c = *(filename++))) { + + if (c < ' ' || c > '~') { + // character outside of printable range + return false; + } + + switch (c) { + // characters unallowed in filenames + case '/': case '\\': case '<': case '>': + case ':': case '"': case '|': case '?': case '*': + return false; + } + } + + return true; +} + /* these operate on the real file system */ +bool fs_sys_path_exists(const char *name) { + struct stat st; + return (stat(name, &st) == 0); +} + bool fs_sys_file_exists(const char *name) { struct stat st; return (stat(name, &st) == 0 && S_ISREG(st.st_mode)); @@ -286,7 +318,7 @@ bool fs_sys_dir_is_empty(const char *name) { bool ret = true; while ((ent = readdir(dir)) != NULL) { // skip "." and ".." - if (ent->d_name[0] == '.' && (ent->d_name[1] == '\0' || + if (ent->d_name[0] == '.' && (ent->d_name[1] == '\0' || (ent->d_name[1] == '.' && ent->d_name[2] == '\0'))) { continue; } @@ -340,36 +372,17 @@ bool fs_sys_walk(const char *base, walk_fn_t walk, void *user, const bool recur) } bool fs_sys_mkdir(const char *name) { - #ifdef _WIN32 +#ifdef _WIN32 return _mkdir(name) == 0; - #else +#else return mkdir(name, 0777) == 0; - #endif +#endif } -bool fs_sys_copy_file(const char *oldname, const char *newname) { - uint8_t buf[2048]; - - FILE *fin = fopen(oldname, "rb"); - if (!fin) return false; - - FILE *fout = fopen(newname, "wb"); - if (!fout) { - fclose(fin); - return false; - } - - bool ret = true; - size_t rx; - while ((rx = fread(buf, 1, sizeof(buf), fin)) > 0) { - if (!fwrite(buf, rx, 1, fout)) { - ret = false; - break; - } - } - - fclose(fout); - fclose(fin); - - return ret; +bool fs_sys_rmdir(const char *name) { +#ifdef _WIN32 + return _rmdir(name) == 0; +#else + return rmdir(name) == 0; +#endif } diff --git a/src/pc/fs/fs.h b/src/pc/fs/fs.h index fc299f1f..afc3c826 100644 --- a/src/pc/fs/fs.h +++ b/src/pc/fs/fs.h @@ -97,13 +97,16 @@ const char *fs_get_write_path(const char *vpath); // expands special chars in paths and changes backslashes to forward slashes const char *fs_convert_path(char *buf, const size_t bufsiz, const char *path); +bool fs_sys_filename_is_portable(char const *filename); + /* these operate on the real filesystem and are used by fs_packtype_dir */ bool fs_sys_walk(const char *base, walk_fn_t walk, void *user, const bool recur); +bool fs_sys_path_exists(const char *name); bool fs_sys_file_exists(const char *name); bool fs_sys_dir_exists(const char *name); bool fs_sys_dir_is_empty(const char *name); bool fs_sys_mkdir(const char *name); // creates with 0777 by default -bool fs_sys_copy_file(const char *oldname, const char *newname); +bool fs_sys_rmdir(const char *name); // removes an empty directory #endif // _SM64_FS_H_ diff --git a/src/pc/mods/mod.c b/src/pc/mods/mod.c index 86a8044a..8ca436d4 100644 --- a/src/pc/mods/mod.c +++ b/src/pc/mods/mod.c @@ -454,18 +454,18 @@ bool mod_load(struct Mods* mods, char* basePath, char* modName) { return true; } - bool isDirectory = is_directory(fullPath); + bool isDirectory = fs_sys_dir_exists(fullPath); // make sure mod is valid if (str_ends_with(modName, ".lua")) { valid = true; - } else if (is_directory(fullPath)) { + } else if (fs_sys_dir_exists(fullPath)) { char tmpPath[SYS_MAX_PATH] = { 0 }; if (!concat_path(tmpPath, fullPath, "main.lua")) { LOG_ERROR("Failed to concat path '%s' + '%s'", fullPath, "main.lua"); return true; } - valid = path_exists(tmpPath); + valid = fs_sys_path_exists(tmpPath); } if (!valid) { diff --git a/src/pc/mods/mod_storage.cpp b/src/pc/mods/mod_storage.cpp index 92a98aac..58309946 100644 --- a/src/pc/mods/mod_storage.cpp +++ b/src/pc/mods/mod_storage.cpp @@ -115,7 +115,7 @@ C_FIELD const char* mod_storage_load(const char* key) { char filename[SYS_MAX_PATH] = { 0 }; mod_storage_get_filename(filename); - if (!path_exists(filename)) { return NULL; } + if (!fs_sys_path_exists(filename)) { return NULL; } mINI::INIFile file(filename); mINI::INIStructure ini; @@ -152,7 +152,7 @@ C_FIELD bool mod_storage_remove(const char* key) { char filename[SYS_MAX_PATH] = { 0 }; mod_storage_get_filename(filename); - if (!path_exists(filename)) { return false; } + if (!fs_sys_path_exists(filename)) { return false; } mINI::INIFile file(filename); mINI::INIStructure ini; @@ -172,7 +172,7 @@ C_FIELD bool mod_storage_clear(void) { char filename[SYS_MAX_PATH] = { 0 }; mod_storage_get_filename(filename); - if (!path_exists(filename)) { return false; } + if (!fs_sys_path_exists(filename)) { return false; } mINI::INIFile file(filename); mINI::INIStructure ini; diff --git a/src/pc/mods/mods.c b/src/pc/mods/mods.c index e2f6834e..0953ebc4 100644 --- a/src/pc/mods/mods.c +++ b/src/pc/mods/mods.c @@ -218,7 +218,7 @@ static void mods_load(struct Mods* mods, char* modsBasePath, UNUSED bool isUserM normalize_path(modsBasePath); // check for existence - if (!is_directory(modsBasePath)) { + if (!fs_sys_dir_exists(modsBasePath)) { LOG_ERROR("Could not find directory '%s'", modsBasePath); } @@ -275,10 +275,8 @@ void mods_refresh_local(void) { // load mods if (hasUserPath) { mods_load(&gLocalMods, userModPath, true); } - const char* exePath = path_to_executable(); char defaultModsPath[SYS_MAX_PATH] = { 0 }; - path_get_folder((char*)exePath, defaultModsPath); - strncat(defaultModsPath, MOD_DIRECTORY, SYS_MAX_PATH-1); + snprintf(defaultModsPath, SYS_MAX_PATH, "%s/%s", sys_exe_path(), MOD_DIRECTORY); mods_load(&gLocalMods, defaultModsPath, false); // sort diff --git a/src/pc/mods/mods_utils.c b/src/pc/mods/mods_utils.c index 9a79bb06..f78d36c7 100644 --- a/src/pc/mods/mods_utils.c +++ b/src/pc/mods/mods_utils.c @@ -93,7 +93,7 @@ void mods_delete_folder(char* path) { if (!strcmp(dir->d_name, "..")) { continue; } if (!concat_path(fullPath, path, dir->d_name)) { continue; } - if (is_directory(fullPath)) { + if (fs_sys_dir_exists(fullPath)) { mods_delete_folder(fullPath); } else if (fs_sys_file_exists(fullPath)) { if (unlink(fullPath) == -1) { @@ -185,75 +185,6 @@ char* extract_lua_field(char* fieldName, char* buffer) { ////////////////////////////////////////////////////////////////////////////////////////// -const char* path_to_executable(void) { - static char exePath[SYS_MAX_PATH] = { 0 }; - if (exePath[0] != '\0') { return exePath; } - -#if defined(_WIN32) || defined(_WIN64) - HMODULE hModule = GetModuleHandle(NULL); - if (hModule == NULL) { - LOG_ERROR("unable to retrieve absolute windows path!"); - return NULL; - } - GetModuleFileName(hModule, exePath, SYS_MAX_PATH-1); -#elif defined(OSX_BUILD) - u32 bufsize = SYS_MAX_PATH-1; - if (_NSGetExecutablePath(exePath, &bufsize) != 0) { - LOG_ERROR("unable to retrieve absolute mac path!"); - return NULL; - } -#else - char procPath[SYS_MAX_PATH] = { 0 }; - snprintf(procPath, SYS_MAX_PATH-1, "/proc/%d/exe", getpid()); - s32 rc = readlink(procPath, exePath, SYS_MAX_PATH-1); - if (rc <= 0) { - LOG_ERROR("unable to retrieve absolute linux path!"); - return NULL; - } -#endif - return exePath; -} - -bool path_is_portable_filename(char* string) { - char* s = string; - while (*s != '\0') { - char c = *s; - - if (c < ' ' || c > '~') { - // outside of printable range - return false; - } - - switch (c) { - // unallowed in filenames - case '/': - case '\\': - case '<': - case '>': - case ':': - case '"': - case '|': - case '?': - case '*': - return false; - } - - s++; - } - - return true; -} - -bool path_exists(char* path) { - struct stat sb = { 0 }; - return (stat(path, &sb) == 0); -} - -bool is_directory(char* path) { - struct stat sb = { 0 }; - return (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)); -} - void normalize_path(char* path) { // replace slashes char* p = path; @@ -298,7 +229,7 @@ void path_get_folder(char* path, char* outpath) { bool directory_sanity_check(struct dirent* dir, char* dirPath, char* outPath) { // skip non-portable filenames - if (!path_is_portable_filename(dir->d_name)) { return false; } + if (!fs_sys_filename_is_portable(dir->d_name)) { return false; } // skip anything that contains \ or / if (strchr(dir->d_name, '/') != NULL) { return false; } @@ -315,7 +246,7 @@ bool directory_sanity_check(struct dirent* dir, char* dirPath, char* outPath) { normalize_path(outPath); // sanity check - if (!path_exists(outPath)) { + if (!fs_sys_path_exists(outPath)) { LOG_ERROR("Path doesn't exist: '%s'", outPath); return false; } diff --git a/src/pc/mods/mods_utils.h b/src/pc/mods/mods_utils.h index 062e77a6..d7d9d24b 100644 --- a/src/pc/mods/mods_utils.h +++ b/src/pc/mods/mods_utils.h @@ -16,10 +16,6 @@ bool str_ends_with(const char* string, const char* suffix); char* extract_lua_field(char* fieldName, char* buffer); -const char* path_to_executable(void); -bool path_is_portable_filename(char* string); -bool path_exists(char* path); -bool is_directory(char* path); void normalize_path(char* path); bool concat_path(char* destination, char* path, char* fname); char* path_basename(char* path); diff --git a/src/pc/os/os.h b/src/pc/os/os.h deleted file mode 100644 index 9d6a2792..00000000 --- a/src/pc/os/os.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once -#if defined(_WIN32) || defined(WIN32) || defined(_WIN64) || defined(WIN64) -#include "os_win.h" -#else -#include "os_other.h" -#endif \ No newline at end of file diff --git a/src/pc/os/os_other.h b/src/pc/os/os_other.h deleted file mode 100644 index e1cf232b..00000000 --- a/src/pc/os/os_other.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include - -#define os_dirent struct dirent -#define OS_DIR DIR -#define os_opendir(_x) opendir(_x) -#define os_readdir(_x) readdir(_x) -#define os_closedir(_x) closedir(_x) -#define os_get_dir_name(_x) _x->d_name diff --git a/src/pc/os/os_win.c b/src/pc/os/os_win.c deleted file mode 100644 index 37f65cdb..00000000 --- a/src/pc/os/os_win.c +++ /dev/null @@ -1,20 +0,0 @@ -#if defined(_WIN32) || defined(WIN32) || defined(_WIN64) || defined(WIN64) - -#include -#include -#include "os_win.h" -#include "pc/platform.h" - -OS_DIR* os_opendir(const char* path) { - wchar_t wpath[SYS_MAX_PATH] = { 0 }; - MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, SYS_MAX_PATH); - return _wopendir(wpath); -} - -const char* os_get_dir_name(os_dirent* dir) { - static char path[SYS_MAX_PATH] = { 0 }; - snprintf(path, SYS_MAX_PATH, "%ls", dir->d_name); - return path; -} - -#endif \ No newline at end of file diff --git a/src/pc/os/os_win.h b/src/pc/os/os_win.h deleted file mode 100644 index a09558d7..00000000 --- a/src/pc/os/os_win.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include -#include - -#define os_dirent struct _wdirent -#define OS_DIR _WDIR - -#define os_closedir(_x) _wclosedir(_x) -#define os_readdir(_x) _wreaddir(_x) - -OS_DIR* os_opendir(const char* path); -const char* os_get_dir_name(os_dirent* dir); diff --git a/src/pc/platform.c b/src/pc/platform.c index 2725159d..b59bf8b7 100644 --- a/src/pc/platform.c +++ b/src/pc/platform.c @@ -5,6 +5,12 @@ #include #include +#if defined(_WIN32) || defined(_WIN64) + #include + #include + #include +#endif + #include "cliopts.h" #include "fs/fs.h" #include "configfile.h" @@ -76,57 +82,133 @@ void sys_fatal(const char *fmt, ...) { sys_fatal_impl(msg); } -#ifdef HAVE_SDL2 +#if defined(_WIN32) || defined(_WIN64) + +static BOOL sys_windows_short_path(LPSTR destPath, SIZE_T destSize, LPWSTR wideLongPath) +{ + WCHAR wideShortPath[SYS_MAX_PATH]; + + // Convert the Long Path in Wide Format to the alternate short form. + // It will still point to already existing directory. + if (0 == GetShortPathNameW(wideLongPath, wideShortPath, SYS_MAX_PATH)) { return FALSE; } + + // Short Path can be safely represented by the US-ASCII Charset. + return (WideCharToMultiByte(CP_ACP, 0, wideShortPath, (-1), destPath, destSize, NULL, NULL) > 0); +} + +const char *sys_user_path(void) +{ + static char shortPath[SYS_MAX_PATH] = { 0 }; + if ('\0' != shortPath[0]) { return shortPath; } + + WCHAR widePath[SYS_MAX_PATH]; + + // "%USERPROFILE%\AppData\Roaming" + WCHAR *wcsAppDataPath = NULL; + HRESULT res = SHGetKnownFolderPath( + &(FOLDERID_RoamingAppData), + (KF_FLAG_CREATE | KF_FLAG_DONT_UNEXPAND), + NULL, &(wcsAppDataPath)); + + if (S_OK != res) + { + if (NULL != wcsAppDataPath) { CoTaskMemFree(wcsAppDataPath); } + return NULL; + } + + LPCWSTR subdirs[] = { L"sm64ex-coop", L"sm64coopdx", NULL }; + + for (int i = 0; NULL != subdirs[i]; i++) + { + if (_snwprintf(widePath, SYS_MAX_PATH, L"%s\\%s", wcsAppDataPath, subdirs[i]) <= 0) { return NULL; } + + // Directory already exists. + if (FALSE != PathIsDirectoryW(widePath)) + { + // Directory is not empty, so choose this name. + if (FALSE == PathIsDirectoryEmptyW(widePath)) { break; } + } + + // 'widePath' will hold the last checked subdir name. + } + + // System resource can be safely released now. + if (NULL != wcsAppDataPath) { CoTaskMemFree(wcsAppDataPath); } + + // Always try to create the directory pointed to by User Path, + // but ignore errors if the destination already exists. + if (FALSE == CreateDirectoryW(widePath, NULL)) + { + if (ERROR_ALREADY_EXISTS != GetLastError()) { return NULL; } + } + + return sys_windows_short_path(shortPath, SYS_MAX_PATH, widePath) ? shortPath : NULL; +} + +const char *sys_exe_path(void) +{ + static char shortPath[SYS_MAX_PATH] = { 0 }; + if ('\0' != shortPath[0]) { return shortPath; } + + WCHAR widePath[SYS_MAX_PATH]; + if (0 == GetModuleFileNameW(NULL, widePath, SYS_MAX_PATH)) { return NULL; } + + WCHAR *lastBackslash = wcsrchr(widePath, L'\\'); + if (NULL != lastBackslash) { *lastBackslash = L'\0'; } + else { return NULL; } + + return sys_windows_short_path(shortPath, SYS_MAX_PATH, widePath) ? shortPath : NULL; +} + +static void sys_fatal_impl(const char *msg) { + MessageBoxA(NULL, msg, "Fatal error", MB_ICONERROR); + fprintf(stderr, "FATAL ERROR:\n%s\n", msg); + fflush(stderr); + exit(1); +} + +#elif defined(HAVE_SDL2) // we can just ask SDL for most of this shit if we have it #include -static const char *sys_old_user_path(void) { - static char path[SYS_MAX_PATH] = { 0 }; - - // get the new pref path from SDL - char *sdlPath = SDL_GetPrefPath("", "sm64ex-coop"); - if (sdlPath) { - const unsigned int len = strlen(sdlPath); - snprintf(path, sizeof(path), "%s", sdlPath); - path[sizeof(path)-1] = 0; - - SDL_free(sdlPath); - - // strip the trailing separator - if (path[len-1] == '/' || path[len-1] == '\\') { path[len-1] = 0; } - } - - return path; -} - const char *sys_user_path(void) { static char path[SYS_MAX_PATH] = { 0 }; + if ('\0' != path[0]) { return path; } - // get the new pref path from SDL - char *sdlPath = SDL_GetPrefPath("", "sm64coopdx"); - if (sdlPath) { - // redirect to the old user path if the current one is empty (likely just created from SDL_GetPrefPath) - if (fs_sys_dir_is_empty(sdlPath)) { + char const *subdirs[] = { "sm64ex-coop", "sm64coopdx", NULL }; + + char *sdlPath = NULL; + for (int i = 0; NULL != subdirs[i]; i++) + { + if (sdlPath) { + // Previous dir likely just created with SDL_GetPrefPath. + fs_sys_rmdir(sdlPath); SDL_free(sdlPath); - return sys_old_user_path(); } - const unsigned int len = strlen(sdlPath); - snprintf(path, sizeof(path), "%s", sdlPath); - path[sizeof(path)-1] = 0; + sdlPath = SDL_GetPrefPath("", subdirs[i]); - SDL_free(sdlPath); - - // strip the trailing separator - if (path[len-1] == '/' || path[len-1] == '\\') { path[len-1] = 0; } + // Choose this directory if it already exists and is not empty. + if (sdlPath && !fs_sys_dir_is_empty(sdlPath)) { break; } } + if (NULL == sdlPath) { return NULL; } + + strncpy(path, sdlPath, SYS_MAX_PATH - 1); + SDL_free(sdlPath); + + // strip the trailing separator + const unsigned int len = strlen(path); + if (path[len-1] == '/' || path[len-1] == '\\') { path[len-1] = 0; } + return path; } const char *sys_exe_path(void) { static char path[SYS_MAX_PATH] = { 0 }; + if ('\0' != path[0]) { return path; } + char *sdlPath = SDL_GetBasePath(); if (sdlPath && sdlPath[0]) { // use the SDL path if it exists