diff --git a/CHANGES b/CHANGES index 11e7601df..dfcbfceb8 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,7 @@ Other fixes: - Scripting: Fix receiving packets for client sockets - Scripting: Fix empty receive calls returning unknown error on Windows Misc: + - Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826) - GB Serialize: Add missing savestate support for MBC6 and NT (newer) - GBA: Improve detection of valid ELF ROMs - Qt: Include wayland QPA in AppImage (fixes mgba.io/i/2796) diff --git a/CMakeLists.txt b/CMakeLists.txt index ac498d0d2..aebbfecae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -338,6 +338,8 @@ find_function(popcount32) find_function(futimens) find_function(futimes) +find_function(realpath) + if(ANDROID AND ANDROID_NDK_MAJOR GREATER 13) find_function(localtime_r) set(HAVE_STRTOF_L ON) diff --git a/include/mgba-util/vfs.h b/include/mgba-util/vfs.h index 334f84419..eee68b99e 100644 --- a/include/mgba-util/vfs.h +++ b/include/mgba-util/vfs.h @@ -100,6 +100,9 @@ struct VFile* VFileFromFILE(FILE* file); void separatePath(const char* path, char* dirname, char* basename, char* extension); +bool isAbsolute(const char* path); +void makeAbsolute(const char* path, const char* base, char* out); + struct VFile* VDirFindFirst(struct VDir* dir, bool (*filter)(struct VFile*)); struct VFile* VDirFindNextAvailable(struct VDir*, const char* basename, const char* infix, const char* suffix, int mode); diff --git a/src/core/directories.c b/src/core/directories.c index 63c66e564..c35773f1e 100644 --- a/src/core/directories.c +++ b/src/core/directories.c @@ -116,10 +116,15 @@ struct VFile* mDirectorySetOpenSuffix(struct mDirectorySet* dirs, struct VDir* d } void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptions* opts) { + char abspath[PATH_MAX + 1]; + char configDir[PATH_MAX + 1]; + mCoreConfigDirectory(configDir, sizeof(configDir)); + if (opts->savegamePath) { - struct VDir* dir = VDirOpen(opts->savegamePath); - if (!dir && VDirCreate(opts->savegamePath)) { - dir = VDirOpen(opts->savegamePath); + makeAbsolute(opts->savegamePath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->save && dirs->save != dirs->base) { @@ -130,9 +135,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio } if (opts->savestatePath) { - struct VDir* dir = VDirOpen(opts->savestatePath); - if (!dir && VDirCreate(opts->savestatePath)) { - dir = VDirOpen(opts->savestatePath); + makeAbsolute(opts->savestatePath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->state && dirs->state != dirs->base) { @@ -143,9 +149,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio } if (opts->screenshotPath) { - struct VDir* dir = VDirOpen(opts->screenshotPath); - if (!dir && VDirCreate(opts->screenshotPath)) { - dir = VDirOpen(opts->screenshotPath); + makeAbsolute(opts->screenshotPath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->screenshot && dirs->screenshot != dirs->base) { @@ -156,9 +163,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio } if (opts->patchPath) { - struct VDir* dir = VDirOpen(opts->patchPath); - if (!dir && VDirCreate(opts->patchPath)) { - dir = VDirOpen(opts->patchPath); + makeAbsolute(opts->patchPath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->patch && dirs->patch != dirs->base) { @@ -169,9 +177,10 @@ void mDirectorySetMapOptions(struct mDirectorySet* dirs, const struct mCoreOptio } if (opts->cheatsPath) { - struct VDir* dir = VDirOpen(opts->cheatsPath); - if (!dir && VDirCreate(opts->cheatsPath)) { - dir = VDirOpen(opts->cheatsPath); + makeAbsolute(opts->cheatsPath, configDir, abspath); + struct VDir* dir = VDirOpen(abspath); + if (!dir && VDirCreate(abspath)) { + dir = VDirOpen(abspath); } if (dir) { if (dirs->cheats && dirs->cheats != dirs->base) { diff --git a/src/util/vfs.c b/src/util/vfs.c index 28d366c53..d99f5020f 100644 --- a/src/util/vfs.c +++ b/src/util/vfs.c @@ -13,6 +13,10 @@ #ifdef __3DS__ #include #endif +#ifdef _WIN32 +#include +#include +#endif struct VFile* VFileOpen(const char* path, int flags) { #ifdef USE_VFS_FILE @@ -207,6 +211,41 @@ void separatePath(const char* path, char* dirname, char* basename, char* extensi } } +bool isAbsolute(const char* path) { + // XXX: Is this robust? +#ifdef _WIN32 + WCHAR wpath[PATH_MAX]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, PATH_MAX); + return !PathIsRelativeW(wpath); +#else + return path[0] == '/'; +#endif +} + +void makeAbsolute(const char* path, const char* base, char* out) { + if (isAbsolute(path)) { + strncpy(out, path, PATH_MAX); + return; + } + + char buf[PATH_MAX]; + snprintf(buf, sizeof(buf), "%s" PATH_SEP "%s", base, path); +#ifdef _WIN32 + WCHAR wbuf[PATH_MAX]; + WCHAR wout[PATH_MAX]; + MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, PATH_MAX); + if (GetFullPathNameW(wbuf, PATH_MAX, wout, NULL)) { + WideCharToMultiByte(CP_UTF8, 0, wout, -1, out, PATH_MAX, 0, 0); + return; + } +#elif defined(HAVE_REALPATH) + if (realpath(buf, out)) { + return; + } +#endif + strncpy(out, buf, PATH_MAX); +} + struct VFile* VDirFindFirst(struct VDir* dir, bool (*filter)(struct VFile*)) { dir->rewind(dir); struct VDirEntry* dirent = dir->listNext(dir);