Index: binaries/system/readme.txt =================================================================== --- binaries/system/readme.txt +++ binaries/system/readme.txt @@ -48,7 +48,6 @@ Configuration: -conf=KEY:VALUE set a config value -nosound disable audio --noUserMod disable loading of the user mod -shadows enable shadows -vsync enable VSync, i.e. lock FPS to monitor refresh rate -xres=N set screen X resolution to 'N' Index: source/graphics/tests/test_MeshManager.h =================================================================== --- source/graphics/tests/test_MeshManager.h +++ source/graphics/tests/test_MeshManager.h @@ -60,13 +60,13 @@ g_VFS = CreateVfs(); - TS_ASSERT_OK(g_VFS->Mount(L"", MOD_PATH)); + TS_ASSERT_OK(g_VFS->Mount(L"", MOD_PATH, VFS_MOUNT_WRITABLE)); TS_ASSERT_OK(g_VFS->Mount(L"collada/", DataDir()/"tests"/"collada", VFS_MOUNT_MUST_EXIST)); // Mount _testcache onto virtual /cache - don't use the normal cache // directory because that's full of loads of cached files from the // proper game and takes a long time to load. - TS_ASSERT_OK(g_VFS->Mount(L"cache/", CACHE_PATH)); + TS_ASSERT_OK(g_VFS->Mount(L"cache/", CACHE_PATH, VFS_MOUNT_WRITABLE)); } void deinitVfs() Index: source/graphics/tests/test_TextureConverter.h =================================================================== --- source/graphics/tests/test_TextureConverter.h +++ source/graphics/tests/test_TextureConverter.h @@ -36,7 +36,7 @@ m_VFS = CreateVfs(); TS_ASSERT_OK(m_VFS->Mount(L"", DataDir()/"mods"/"_test.tex", VFS_MOUNT_MUST_EXIST)); - TS_ASSERT_OK(m_VFS->Mount(L"cache/", DataDir()/"_testcache")); + TS_ASSERT_OK(m_VFS->Mount(L"cache/", DataDir()/"_testcache", VFS_MOUNT_WRITABLE)); } void tearDown() Index: source/graphics/tests/test_TextureManager.h =================================================================== --- source/graphics/tests/test_TextureManager.h +++ source/graphics/tests/test_TextureManager.h @@ -37,7 +37,7 @@ m_VFS = CreateVfs(); TS_ASSERT_OK(m_VFS->Mount(L"", DataDir()/"mods"/"_test.tex", VFS_MOUNT_MUST_EXIST)); - TS_ASSERT_OK(m_VFS->Mount(L"cache/", DataDir()/"_testcache")); + TS_ASSERT_OK(m_VFS->Mount(L"cache/", DataDir()/"_testcache", VFS_MOUNT_WRITABLE)); h_mgr_init(); Index: source/gui/tests/test_GuiManager.h =================================================================== --- source/gui/tests/test_GuiManager.h +++ source/gui/tests/test_GuiManager.h @@ -35,7 +35,7 @@ { g_VFS = CreateVfs(); TS_ASSERT_OK(g_VFS->Mount(L"", DataDir()/"mods"/"_test.gui", VFS_MOUNT_MUST_EXIST)); - TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache")); + TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache", VFS_MOUNT_WRITABLE)); configDB = new CConfigDB; Index: source/lib/file/vfs/tests/test_vfs_populate.h =================================================================== --- /dev/null +++ source/lib/file/vfs/tests/test_vfs_populate.h @@ -0,0 +1,86 @@ +/* Copyright (C) 2020 Wildfire Games. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "lib/self_test.h" + +#include "lib/file/vfs/vfs_populate.h" +#include "lib/os_path.h" + +static OsPath CACHE_PATH_HIGH(DataDir()/"_testcache1"); +static OsPath CACHE_PATH_LOW(DataDir()/"_testcache2"); + +extern PIVFS g_VFS; + +class TestVfsPopulate : public CxxTest::TestSuite +{ + void initVfs() + { + // Initialise VFS: + + // Make sure the required directories doesn't exist when we start, + // in case the previous test aborted and left them full of junk + if(DirectoryExists(CACHE_PATH_HIGH)) + DeleteDirectory(CACHE_PATH_HIGH); + if(DirectoryExists(CACHE_PATH_LOW)) + DeleteDirectory(CACHE_PATH_LOW); + + g_VFS = CreateVfs(); + } + + void deinitVfs() + { + g_VFS.reset(); + DeleteDirectory(CACHE_PATH_LOW); + DeleteDirectory(CACHE_PATH_HIGH); + } + +public: + void setUp() + { + initVfs(); + } + + void tearDown() + { + deinitVfs(); + } + + void test_RealPriority() + { + g_VFS->Mount(L"cache", CACHE_PATH_LOW, 0, 0); + OsPath realPath; + g_VFS->GetDirectoryRealPath(L"cache", realPath); + TS_ASSERT_EQUALS(realPath, CACHE_PATH_LOW); + // Higher priority: overwritten. + g_VFS->Mount(L"cache", CACHE_PATH_HIGH, 0, 1); + g_VFS->GetDirectoryRealPath(L"cache", realPath); + TS_ASSERT_EQUALS(realPath, CACHE_PATH_HIGH); + // Same priority: nothing happens + g_VFS->Mount(L"cache", CACHE_PATH_LOW, 0, 1); + g_VFS->GetDirectoryRealPath(L"cache", realPath); + TS_ASSERT_EQUALS(realPath, CACHE_PATH_HIGH); + // Lower priority: nothing happens + g_VFS->Mount(L"cache", CACHE_PATH_LOW, 0, 0); + g_VFS->GetDirectoryRealPath(L"cache", realPath); + TS_ASSERT_EQUALS(realPath, CACHE_PATH_HIGH); + }; +}; Index: source/lib/file/vfs/tests/test_vfs_util.h =================================================================== --- source/lib/file/vfs/tests/test_vfs_util.h +++ source/lib/file/vfs/tests/test_vfs_util.h @@ -46,12 +46,12 @@ g_VFS = CreateVfs(); - TS_ASSERT_OK(g_VFS->Mount(L"", MOD_PATH)); + TS_ASSERT_OK(g_VFS->Mount(L"", MOD_PATH, VFS_MOUNT_WRITABLE)); // Mount _testcache onto virtual /cache - don't use the normal cache // directory because that's full of loads of cached files from the // proper game and takes a long time to load. - TS_ASSERT_OK(g_VFS->Mount(L"cache/", CACHE_PATH)); + TS_ASSERT_OK(g_VFS->Mount(L"cache/", CACHE_PATH, VFS_MOUNT_WRITABLE)); } void deinitVfs() Index: source/lib/file/vfs/vfs.h =================================================================== --- source/lib/file/vfs/vfs.h +++ source/lib/file/vfs/vfs.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -31,6 +31,9 @@ #include "lib/file/file_system.h" // CFileInfo #include "lib/file/vfs/vfs_path.h" +constexpr size_t VFS_MIN_PRIORITY = 0; +constexpr size_t VFS_MAX_PRIORITY = std::numeric_limits::max(); + namespace ERR { const Status VFS_DIR_NOT_FOUND = -110100; @@ -68,13 +71,9 @@ VFS_MOUNT_KEEP_DELETED = 8, /** - * mark a directory replaceable, so that when writing a file to this path - * new real directories will be created instead of reusing already existing - * ones mounted at a subpath of the VFS path. - * (the default behaviour is to write to the real directory associated - * with the VFS directory that was last mounted to this path (or subpath)) + * Mark a directory as writable. **/ - VFS_MOUNT_REPLACEABLE = 16 + VFS_MOUNT_WRITABLE = 16 }; // (member functions are thread-safe after the instance has been @@ -143,17 +142,6 @@ **/ virtual Status CreateFile(const VfsPath& pathname, const shared_ptr& fileContents, size_t size) = 0; - /** - * Replace a file with the given contents. - * - * @see CreateFile - * - * Used to replace a file if it is already present (even if the file is not - * in the attached vfs directory). Calls CreateFile if the file doesn't yet - * exist. - **/ - virtual Status ReplaceFile(const VfsPath& pathname, const shared_ptr& fileContents, size_t size) = 0; - /** * Read an entire file into memory. * Index: source/lib/file/vfs/vfs.cpp =================================================================== --- source/lib/file/vfs/vfs.cpp +++ source/lib/file/vfs/vfs.cpp @@ -129,7 +129,7 @@ std::lock_guard lock(vfs_mutex); VfsDirectory* directory; Status st; - st = vfs_Lookup(pathname, &m_rootDirectory, directory, 0, VFS_LOOKUP_ADD|VFS_LOOKUP_CREATE|VFS_LOOKUP_CREATE_ALWAYS); + st = vfs_Lookup(pathname, &m_rootDirectory, directory, 0, VFS_LOOKUP_ADD|VFS_LOOKUP_CREATE|VFS_LOOKUP_WRITE); if (st == ERR::FILE_ACCESS) return ERR::FILE_ACCESS; @@ -146,32 +146,6 @@ return INFO::OK; } - virtual Status ReplaceFile(const VfsPath& pathname, const shared_ptr& fileContents, size_t size) - { - std::unique_lock lock(vfs_mutex); - VfsDirectory* directory; - VfsFile* file; - Status st; - st = vfs_Lookup(pathname, &m_rootDirectory, directory, &file, VFS_LOOKUP_ADD|VFS_LOOKUP_CREATE); - - // There is no such file, create it. - if (st == ERR::VFS_FILE_NOT_FOUND) - { - lock.unlock(); - return CreateFile(pathname, fileContents, size); - } - - WARN_RETURN_STATUS_IF_ERR(st); - - RealDirectory realDirectory(file->Loader()->Path(), file->Priority(), directory->AssociatedDirectory()->Flags()); - RETURN_STATUS_IF_ERR(realDirectory.Store(pathname.Filename(), fileContents, size)); - - directory->AddFile(*file); - - m_trace->NotifyStore(pathname, size); - return INFO::OK; - } - virtual Status LoadFile(const VfsPath& pathname, shared_ptr& fileContents, size_t& size) { std::lock_guard lock(vfs_mutex); Index: source/lib/file/vfs/vfs_lookup.h =================================================================== --- source/lib/file/vfs/vfs_lookup.h +++ source/lib/file/vfs/vfs_lookup.h @@ -51,10 +51,17 @@ // cause nearly every directory to be populated. VFS_LOOKUP_SKIP_POPULATE = 4, - // even create directories if they are already present, this is - // useful to write new files to the directory that was attached - // last, if the directory wasn't mounted with VFS_MOUNT_REPLACEABLE - VFS_LOOKUP_CREATE_ALWAYS = 8 + // Do a lookup for writing. This changes the behaviour of the lookup. + // (it also fails to return if no WRITABLE directory is found on the requested path). + // Normally, the VFS will follow its tree structure without caring what the real directories are. + // When writing, after any WRITABLE directory is encountered, we will rewrite the real path of subdirectories of lower priority. + // Example: we try to write to something/maps/. + // The real directories of something/ and maps/ may be completely different. + // If something/ is marked as WRITABLE, then a write-lookup to 'something/maps/eg.xml' will + // overwrite the real directory of maps/ to point to "something/maps" on the user's disk. + // This avoids a lower-priority directory "hijacking" write paths to a higher directory by having subfolders + // that the higher directory does not have. + VFS_LOOKUP_WRITE = 8 }; /** Index: source/lib/file/vfs/vfs_lookup.cpp =================================================================== --- source/lib/file/vfs/vfs_lookup.cpp +++ source/lib/file/vfs/vfs_lookup.cpp @@ -78,46 +78,54 @@ const bool addMissingDirectories = (flags & VFS_LOOKUP_ADD) != 0; const bool createMissingDirectories = (flags & VFS_LOOKUP_CREATE) != 0; const bool skipPopulate = (flags & VFS_LOOKUP_SKIP_POPULATE) != 0; - const bool createAlways = (flags & VFS_LOOKUP_CREATE_ALWAYS) != 0; - ENSURE((flags & ~(VFS_LOOKUP_ADD|VFS_LOOKUP_CREATE|VFS_LOOKUP_SKIP_POPULATE|VFS_LOOKUP_CREATE_ALWAYS)) == 0); + const bool writeLookup = (flags & VFS_LOOKUP_WRITE) != 0; + ENSURE((flags & ~(VFS_LOOKUP_ADD|VFS_LOOKUP_CREATE|VFS_LOOKUP_SKIP_POPULATE|VFS_LOOKUP_WRITE)) == 0); directory = startDirectory; - if(pfile) + if (pfile) *pfile = 0; - if(!skipPopulate) + if (!skipPopulate) RETURN_STATUS_IF_ERR(vfs_Populate(directory)); // early-out for pathname == "" when mounting into VFS root - if(pathname.empty()) // (prevent iterator error in loop end condition) + if (pathname.empty()) // (prevent iterator error in loop end condition) { - if(pfile) // preserve a guarantee that if pfile then we either return an error or set *pfile + if (pfile) // preserve a guarantee that if pfile then we either return an error or set *pfile + return ERR::VFS_FILE_NOT_FOUND; + else if (writeLookup) return ERR::VFS_FILE_NOT_FOUND; else return INFO::OK; } + bool overwritingRealDir = false; + // for each directory component: size_t pos = 0; // (needed outside of loop) for(;;) { const size_t nextSlash = pathname.string().find_first_of('/', pos); - if(nextSlash == VfsPath::String::npos) + if (nextSlash == VfsPath::String::npos) break; const VfsPath subdirectoryName = pathname.string().substr(pos, nextSlash-pos); pos = nextSlash+1; + if (!overwritingRealDir) + overwritingRealDir |= writeLookup && ((directory->AssociatedDirectory()->Flags() & VFS_MOUNT_WRITABLE) != 0); + VfsDirectory* subdirectory = directory->GetSubdirectory(subdirectoryName); - if(!subdirectory) + if (!subdirectory) { - if(addMissingDirectories) + if (addMissingDirectories) subdirectory = directory->AddSubdirectory(subdirectoryName); else return ERR::VFS_DIR_NOT_FOUND; // NOWARN } - if(createMissingDirectories && (!subdirectory->AssociatedDirectory() - || (createAlways && (subdirectory->AssociatedDirectory()->Flags() & VFS_MOUNT_REPLACEABLE) != 0))) + if ((createMissingDirectories && !subdirectory->AssociatedDirectory()) || + (overwritingRealDir && subdirectory->AssociatedDirectory() && + directory->AssociatedDirectory()->Priority() > subdirectory->AssociatedDirectory()->Priority())) { OsPath currentPath; if(directory->AssociatedDirectory()) // (is NULL when mounting into root) @@ -126,7 +134,9 @@ RETURN_STATUS_IF_ERR(CreateDirectory(currentPath)); - PRealDirectory realDirectory(new RealDirectory(currentPath, 0, 0)); + PRealDirectory realDirectory(new RealDirectory(currentPath, + directory->AssociatedDirectory()->Priority(), + directory->AssociatedDirectory()->Flags())); RETURN_STATUS_IF_ERR(vfs_Attach(subdirectory, realDirectory)); } @@ -136,6 +146,9 @@ directory = subdirectory; } + if (writeLookup && (directory->AssociatedDirectory()->Flags() & VFS_MOUNT_WRITABLE) == 0) + return ERR::VFS_FILE_NOT_FOUND; + if(pfile) { ENSURE(!pathname.IsDirectory()); Index: source/lib/file/vfs/vfs_populate.cpp =================================================================== --- source/lib/file/vfs/vfs_populate.cpp +++ source/lib/file/vfs/vfs_populate.cpp @@ -168,7 +168,29 @@ Status vfs_Attach(VfsDirectory* directory, const PRealDirectory& realDirectory) { - RETURN_STATUS_IF_ERR(vfs_Populate(directory)); + PRealDirectory existingRealDir = directory->AssociatedDirectory(); + + // Don't allow replacing the real directory by a lower-priority one. + if (!existingRealDir || existingRealDir->Priority() < realDirectory->Priority()) + { + // This ordering is peculiar but useful, as it "defers" the population call. + // If there is already a real directory, we will replace it (and lose track of it), + // so we'll populate it right away, but the 'new' real directory can wait until we access it. + RETURN_STATUS_IF_ERR(vfs_Populate(directory)); + directory->SetAssociatedDirectory(realDirectory); + return INFO::OK; + } + // We are attaching a lower-priority real directory. + // Because of deferred population, we need to immediately populate this new directory. + bool shouldPop = directory->ShouldPopulate(); + // This sets "should populate" to true, so the vfs_Populate call below immediately populates. directory->SetAssociatedDirectory(realDirectory); + RETURN_STATUS_IF_ERR(vfs_Populate(directory)); + // Reset to the higher priority realDirectory, which resets ShouldPopulate to true. + directory->SetAssociatedDirectory(existingRealDir); + // Avoir un-necessary repopulation by clearing the flag. + if (!shouldPop) + directory->ShouldPopulate(); + return INFO::OK; } Index: source/main.cpp =================================================================== --- source/main.cpp +++ source/main.cpp @@ -636,7 +636,8 @@ Paths paths(args); g_VFS = CreateVfs(); - g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE); + // Mount with highest priority, we don't want mods overwriting this. + g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE|VFS_MOUNT_WRITABLE, VFS_MAX_PRIORITY); MountMods(paths, GetMods(args, INIT_MODS)); { Index: source/ps/ArchiveBuilder.cpp =================================================================== --- source/ps/ArchiveBuilder.cpp +++ source/ps/ArchiveBuilder.cpp @@ -35,10 +35,10 @@ DeleteDirectory(m_TempDir/"_archivecache"); // clean up in case the last run failed - m_VFS->Mount(L"cache/", m_TempDir/"_archivecache"/""); + m_VFS->Mount(L"cache/", m_TempDir/"_archivecache"/"", 0, VFS_MAX_PRIORITY); // Mount with highest priority so base mods do not overwrite files in this mod - m_VFS->Mount(L"", mod/"", VFS_MOUNT_MUST_EXIST | VFS_MOUNT_KEEP_DELETED, (size_t)-1); + m_VFS->Mount(L"", mod/"", VFS_MOUNT_MUST_EXIST | VFS_MOUNT_KEEP_DELETED, VFS_MAX_PRIORITY); // Collect the list of files before loading any base mods vfs::ForEachFile(m_VFS, L"", &CollectFileCB, (uintptr_t)static_cast(this), 0, vfs::DIR_RECURSIVE); Index: source/ps/GameSetup/GameSetup.cpp =================================================================== --- source/ps/GameSetup/GameSetup.cpp +++ source/ps/GameSetup/GameSetup.cpp @@ -371,17 +371,10 @@ const std::vector& GetMods(const CmdLineArgs& args, int flags) { const bool init_mods = (flags & INIT_MODS) == INIT_MODS; - const bool add_user = !InDevelopmentCopy() && !args.Has("noUserMod"); const bool add_public = (flags & INIT_MODS_PUBLIC) == INIT_MODS_PUBLIC; if (!init_mods) - { - // Add the user mod if it should be present - if (add_user && (g_modsLoaded.empty() || g_modsLoaded.back() != "user")) - g_modsLoaded.push_back("user"); - return g_modsLoaded; - } g_modsLoaded = args.GetMultiple("mod"); @@ -390,11 +383,6 @@ g_modsLoaded.insert(g_modsLoaded.begin(), "mod"); - // Add the user mod if not explicitly disabled or we have a dev copy so - // that saved files end up in version control and not in the user mod. - if (add_user) - g_modsLoaded.push_back("user"); - return g_modsLoaded; } @@ -402,29 +390,33 @@ { OsPath modPath = paths.RData()/"mods"; OsPath modUserPath = paths.UserData()/"mods"; + + size_t userFlags = VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE; + size_t baseFlags = userFlags|VFS_MOUNT_MUST_EXIST; + if (InDevelopmentCopy()) + { + userFlags |= VFS_MOUNT_WRITABLE; + baseFlags |= VFS_MOUNT_WRITABLE; + } + + size_t priority; for (size_t i = 0; i < mods.size(); ++i) { - size_t priority = (i+1)*2; // mods are higher priority than regular mountings, which default to priority 0 - size_t userFlags = VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE|VFS_MOUNT_REPLACEABLE; - size_t baseFlags = userFlags|VFS_MOUNT_MUST_EXIST; + priority = i + 1; // Mods are higher priority than regular mountings, which default to priority 0 OsPath modName(mods[i]); - if (InDevelopmentCopy()) - { - // We are running a dev copy, so only mount mods in the user mod path - // if the mod does not exist in the data path. - if (DirectoryExists(modPath / modName/"")) - g_VFS->Mount(L"", modPath / modName/"", baseFlags, priority); - else - g_VFS->Mount(L"", modUserPath / modName/"", userFlags, priority); - } - else - { + // Only mount mods from the user path if they don't exist in the 'rdata' path. + if (DirectoryExists(modPath / modName/"")) g_VFS->Mount(L"", modPath / modName/"", baseFlags, priority); - // Ensure that user modified files are loaded, if they are present - g_VFS->Mount(L"", modUserPath / modName/"", userFlags, priority+1); - } + else + g_VFS->Mount(L"", modUserPath / modName/"", userFlags, priority); } + + // Mount the user mod last. In dev copy, mount it with a low priority. Otherwise, make it writable. + if (InDevelopmentCopy()) + g_VFS->Mount(L"", modUserPath / "user", baseFlags, 0); + else + g_VFS->Mount(L"", modUserPath / "user", userFlags|VFS_MOUNT_WRITABLE, priority + 1); } static void InitVfs(const CmdLineArgs& args, int flags) @@ -451,23 +443,20 @@ g_VFS = CreateVfs(); const OsPath readonlyConfig = paths.RData()/"config"/""; - g_VFS->Mount(L"config/", readonlyConfig); - // Engine localization files. + // Mount these dirs with highest priority so that mods can't overwrite them. + g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE|VFS_MOUNT_WRITABLE, VFS_MAX_PRIORITY); // (adding XMBs to archive speeds up subsequent reads) + if(readonlyConfig != paths.Config()) + g_VFS->Mount(L"config/", readonlyConfig, 0, VFS_MAX_PRIORITY-1); + g_VFS->Mount(L"config/", paths.Config(), VFS_MOUNT_WRITABLE, VFS_MAX_PRIORITY); + g_VFS->Mount(L"screenshots/", paths.UserData()/"screenshots"/"", VFS_MOUNT_WRITABLE, VFS_MAX_PRIORITY); + g_VFS->Mount(L"saves/", paths.UserData()/"saves"/"", VFS_MOUNT_WATCH|VFS_MOUNT_WRITABLE, VFS_MAX_PRIORITY); + + // Engine localization files (regular priority, these can be overwritten). g_VFS->Mount(L"l10n/", paths.RData()/"l10n"/""); MountMods(paths, GetMods(args, flags)); - // We mount these dirs last as otherwise writing could result in files being placed in a mod's dir. - g_VFS->Mount(L"screenshots/", paths.UserData()/"screenshots"/""); - g_VFS->Mount(L"saves/", paths.UserData()/"saves"/"", VFS_MOUNT_WATCH); - // Mounting with highest priority, so that a mod supplied user.cfg is harmless - g_VFS->Mount(L"config/", readonlyConfig, 0, (size_t)-1); - if(readonlyConfig != paths.Config()) - g_VFS->Mount(L"config/", paths.Config(), 0, (size_t)-1); - - g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE); // (adding XMBs to archive speeds up subsequent reads) - // note: don't bother with g_VFS->TextRepresentation - directories // haven't yet been populated and are empty. } Index: source/ps/Mod.cpp =================================================================== --- source/ps/Mod.cpp +++ source/ps/Mod.cpp @@ -59,7 +59,8 @@ for (DirectoryNames::iterator iter = modDirs.begin(); iter != modDirs.end(); ++iter) { vfs->Clear(); - if (vfs->Mount(L"", modPath / *iter, VFS_MOUNT_MUST_EXIST) < 0) + // Mount with lowest priority, we don't want to overwrite anything + if (vfs->Mount(L"", modPath / *iter, VFS_MOUNT_MUST_EXIST, VFS_MIN_PRIORITY) < 0) continue; CVFSFile modinfo; @@ -85,7 +86,8 @@ continue; vfs->Clear(); - if (vfs->Mount(L"", modUserPath / *iter, VFS_MOUNT_MUST_EXIST) < 0) + // Mount with lowest priority, we don't want to overwrite anything + if (vfs->Mount(L"", modUserPath / *iter, VFS_MOUNT_MUST_EXIST, VFS_MIN_PRIORITY) < 0) continue; CVFSFile modinfo; @@ -114,8 +116,8 @@ for (const CStr& mod : g_modsLoaded) { - // Ignore user and mod mod as they are irrelevant for compatibility checks - if (mod == "mod" || mod == "user") + // Ignore mod mod as it is irrelevant for compatibility checks + if (mod == "mod") continue; CStr version; Index: source/simulation2/components/tests/test_Pathfinder.h =================================================================== --- source/simulation2/components/tests/test_Pathfinder.h +++ source/simulation2/components/tests/test_Pathfinder.h @@ -39,7 +39,7 @@ g_VFS = CreateVfs(); g_VFS->Mount(L"", DataDir()/"mods"/"mod", VFS_MOUNT_MUST_EXIST); g_VFS->Mount(L"", DataDir()/"mods"/"public", VFS_MOUNT_MUST_EXIST, 1); // ignore directory-not-found errors - TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache")); + TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache", VFS_MOUNT_WRITABLE, 2)); CXeromyces::Startup(); Index: source/simulation2/tests/test_CmpTemplateManager.h =================================================================== --- source/simulation2/tests/test_CmpTemplateManager.h +++ source/simulation2/tests/test_CmpTemplateManager.h @@ -38,7 +38,7 @@ { g_VFS = CreateVfs(); TS_ASSERT_OK(g_VFS->Mount(L"", DataDir()/"mods"/"_test.sim", VFS_MOUNT_MUST_EXIST)); - TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache")); + TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache", VFS_MOUNT_WRITABLE)); CXeromyces::Startup(); } @@ -223,7 +223,7 @@ g_VFS = CreateVfs(); TS_ASSERT_OK(g_VFS->Mount(L"", DataDir()/"mods"/"mod", VFS_MOUNT_MUST_EXIST)); TS_ASSERT_OK(g_VFS->Mount(L"", DataDir()/"mods"/"public", VFS_MOUNT_MUST_EXIST)); - TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache")); + TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache", VFS_MOUNT_WRITABLE)); CXeromyces::Startup(); } Index: source/simulation2/tests/test_ComponentManager.h =================================================================== --- source/simulation2/tests/test_ComponentManager.h +++ source/simulation2/tests/test_ComponentManager.h @@ -45,7 +45,7 @@ { g_VFS = CreateVfs(); TS_ASSERT_OK(g_VFS->Mount(L"", DataDir()/"mods"/"_test.sim", VFS_MOUNT_MUST_EXIST)); - TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache")); + TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache", VFS_MOUNT_WRITABLE)); CXeromyces::Startup(); } Index: source/simulation2/tests/test_Serializer.h =================================================================== --- source/simulation2/tests/test_Serializer.h +++ source/simulation2/tests/test_Serializer.h @@ -825,7 +825,7 @@ g_VFS = CreateVfs(); TS_ASSERT_OK(g_VFS->Mount(L"", DataDir()/"mods"/"public", VFS_MOUNT_MUST_EXIST)); - TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache")); + TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache", VFS_MOUNT_WRITABLE)); // Need some stuff for terrain movement costs: // (TODO: this ought to be independent of any graphics code) Index: source/simulation2/tests/test_Simulation2.h =================================================================== --- source/simulation2/tests/test_Simulation2.h +++ source/simulation2/tests/test_Simulation2.h @@ -43,8 +43,8 @@ void setUp() { g_VFS = CreateVfs(); - TS_ASSERT_OK(g_VFS->Mount(L"", DataDir()/"mods"/"_test.sim", VFS_MOUNT_MUST_EXIST)); - TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache")); + TS_ASSERT_OK(g_VFS->Mount(L"", DataDir()/"mods"/"_test.sim", VFS_MOUNT_MUST_EXIST|VFS_MOUNT_WRITABLE)); + TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache", VFS_MOUNT_WRITABLE)); CXeromyces::Startup(); }