diff --git a/src/modules/filesystem/physfs/Filesystem.cpp b/src/modules/filesystem/physfs/Filesystem.cpp index 17ae4e7ec..93dd67465 100644 --- a/src/modules/filesystem/physfs/Filesystem.cpp +++ b/src/modules/filesystem/physfs/Filesystem.cpp @@ -137,6 +137,11 @@ bool Filesystem::setSource(const char *source) return true; } +const char *Filesystem::getSource() const +{ + return game_source.c_str(); +} + bool Filesystem::setupWriteDirectory() { if (!initialized) @@ -160,7 +165,8 @@ bool Filesystem::setupWriteDirectory() if (!success) { - PHYSFS_setWriteDir(0); // Clear the write directory in case of error. + // Clear the write directory in case of error. + PHYSFS_setWriteDir(0); return false; } @@ -183,24 +189,39 @@ bool Filesystem::mount(const char *archive, const char *mountpoint, SearchOrder if (!initialized || !archive) return false; - // Not allowed for safety reasons. - if (strlen(archive) == 0 || strstr(archive, "..") || strcmp(archive, "/") == 0) + std::string realPath; + std::string sourceBase = getSourceBaseDirectory(); + + if (isFused() && sourceBase.compare(archive) == 0) + { + // Special case: if the game is fused and the archive is the source's + // base directory, mount it even though it's outside of the save dir. + realPath = sourceBase; + } + else + { + // Not allowed for safety reasons. + if (strlen(archive) == 0 || strstr(archive, "..") || strcmp(archive, "/") == 0) + return false; + + const char *realDir = PHYSFS_getRealDir(archive); + if (!realDir) + return false; + + realPath = realDir; + + // Always disallow mounting of files inside the game source, since it + // won't work anyway if the game source is a zipped .love file. + if (realPath.find(game_source) == 0) + return false; + + realPath += LOVE_PATH_SEPARATOR; + realPath += archive; + } + + if (realPath.length() == 0) return false; - const char *realDir = PHYSFS_getRealDir(archive); - if (!realDir) - return false; - - std::string realPath(realDir); - - // Always disallow mounting of files inside the game source, since it won't - // work anyway if the game source is a zipped .love file. - if (realPath.find(game_source) == 0) - return false; - - realPath += LOVE_PATH_SEPARATOR; - realPath += archive; - bool append = (searchorder == SEARCH_ORDER_LAST); return PHYSFS_mount(realPath.c_str(), mountpoint, append); @@ -211,17 +232,29 @@ bool Filesystem::unmount(const char *archive) if (!initialized || !archive) return false; - // Not allowed for safety reasons. - if (strlen(archive) == 0 || strstr(archive, "..") || strcmp(archive, "/") == 0) - return false; + std::string realPath; + std::string sourceBase = getSourceBaseDirectory(); - const char *realDir = PHYSFS_getRealDir(archive); - if (!realDir) - return false; + if (isFused() && sourceBase.compare(archive) == 0) + { + // Special case: if the game is fused and the archive is the source's + // base directory, unmount it even though it's outside of the save dir. + realPath = sourceBase; + } + else + { + // Not allowed for safety reasons. + if (strlen(archive) == 0 || strstr(archive, "..") || strcmp(archive, "/") == 0) + return false; - std::string realPath(realDir); - realPath += LOVE_PATH_SEPARATOR; - realPath += archive; + const char *realDir = PHYSFS_getRealDir(archive); + if (!realDir) + return false; + + realPath = realDir; + realPath += LOVE_PATH_SEPARATOR; + realPath += archive; + } const char *mountPoint = PHYSFS_getMountPoint(realPath.c_str()); if (!mountPoint) @@ -326,18 +359,40 @@ const char *Filesystem::getSaveDirectory() return save_path_full.c_str(); } +std::string Filesystem::getSourceBaseDirectory() const +{ + size_t source_len = game_source.length(); + + if (source_len == 0) + return ""; + + // FIXME: This doesn't take into account parent and current directory + // symbols (i.e. '..' and '.') +#ifdef LOVE_WINDOWS + // In windows, delimiters can be either '/' or '\'. + size_t base_end_pos = game_source.find_last_of("/\\", source_len - 2); +#else + size_t base_end_pos = game_source.find_last_of('/', source_len - 2); +#endif + + if (base_end_pos == std::string::npos) + return ""; + + // If the source is in the unix root (aka '/'), we want to keep the '/'. + if (base_end_pos == 0) + base_end_pos = 1; + + return game_source.substr(0, base_end_pos); +} + bool Filesystem::exists(const char *file) const { - if (PHYSFS_exists(file)) - return true; - return false; + return PHYSFS_exists(file); } bool Filesystem::isDirectory(const char *file) const { - if (PHYSFS_isDirectory(file)) - return true; - return false; + return PHYSFS_isDirectory(file); } bool Filesystem::isFile(const char *file) const diff --git a/src/modules/filesystem/physfs/Filesystem.h b/src/modules/filesystem/physfs/Filesystem.h index 4527c9a24..afa47b184 100644 --- a/src/modules/filesystem/physfs/Filesystem.h +++ b/src/modules/filesystem/physfs/Filesystem.h @@ -118,6 +118,12 @@ public: **/ bool setSource(const char *source); + /** + * Gets the path to the game source. + * Returns a 0-length string if the source has not been set. + **/ + const char *getSource() const; + bool mount(const char *archive, const char *mountpoint, SearchOrder searchorder = SEARCH_ORDER_FIRST); bool unmount(const char *archive); @@ -162,6 +168,13 @@ public: **/ const char *getSaveDirectory(); + /** + * Gets the full path to the directory containing the game source. + * For example if the game source is C:\Games\mygame.love, this will return + * C:\Games. + **/ + std::string getSourceBaseDirectory() const; + /** * Checks whether a file exists in the current search path * or not. diff --git a/src/modules/filesystem/physfs/wrap_Filesystem.cpp b/src/modules/filesystem/physfs/wrap_Filesystem.cpp index fd64ff57c..bdffb756e 100644 --- a/src/modules/filesystem/physfs/wrap_Filesystem.cpp +++ b/src/modules/filesystem/physfs/wrap_Filesystem.cpp @@ -111,6 +111,12 @@ int w_setSource(lua_State *L) return 0; } +int w_getSource(lua_State *L) +{ + lua_pushstring(L, instance->getSource()); + return 1; +} + int w_mount(lua_State *L) { const char *archive = luaL_checkstring(L, 1); @@ -249,6 +255,12 @@ int w_getSaveDirectory(lua_State *L) return 1; } +int w_getSourceBaseDirectory(lua_State *L) +{ + luax_pushstring(L, instance->getSourceBaseDirectory()); + return 1; +} + int w_exists(lua_State *L) { const char *arg = luaL_checkstring(L, 1); @@ -580,6 +592,7 @@ static const luaL_Reg functions[] = { "setIdentity", w_setIdentity }, { "getIdentity", w_getIdentity }, { "setSource", w_setSource }, + { "getSource", w_getSource }, { "mount", w_mount }, { "unmount", w_unmount }, { "newFile", w_newFile }, @@ -587,6 +600,7 @@ static const luaL_Reg functions[] = { "getUserDirectory", w_getUserDirectory }, { "getAppdataDirectory", w_getAppdataDirectory }, { "getSaveDirectory", w_getSaveDirectory }, + { "getSourceBaseDirectory", w_getSourceBaseDirectory }, { "exists", w_exists }, { "isDirectory", w_isDirectory }, { "isFile", w_isFile }, diff --git a/src/modules/filesystem/physfs/wrap_Filesystem.h b/src/modules/filesystem/physfs/wrap_Filesystem.h index c96818fe8..f6fd08adb 100644 --- a/src/modules/filesystem/physfs/wrap_Filesystem.h +++ b/src/modules/filesystem/physfs/wrap_Filesystem.h @@ -43,6 +43,7 @@ int w_isFused(lua_State *L); int w_setIdentity(lua_State *L); int w_getIdentity(lua_State *L); int w_setSource(lua_State *L); +int w_getSource(lua_State *L); int w_mount(lua_State *L); int w_unmount(lua_State *L); int w_newFile(lua_State *L); @@ -51,6 +52,7 @@ int w_getWorkingDirectory(lua_State *L); int w_getUserDirectory(lua_State *L); int w_getAppdataDirectory(lua_State *L); int w_getSaveDirectory(lua_State *L); +int w_getSourceBaseDirectory(lua_State *L); int w_exists(lua_State *L); int w_isDirectory(lua_State *L); int w_isFile(lua_State *L);