Index: ps/trunk/binaries/system/readme.txt =================================================================== --- ps/trunk/binaries/system/readme.txt +++ ps/trunk/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: ps/trunk/source/graphics/tests/test_MapGenerator.h =================================================================== --- ps/trunk/source/graphics/tests/test_MapGenerator.h +++ ps/trunk/source/graphics/tests/test_MapGenerator.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -25,8 +25,8 @@ void setUp() { 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 + 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 CXeromyces::Startup(); } Index: ps/trunk/source/graphics/tests/test_MeshManager.h =================================================================== --- ps/trunk/source/graphics/tests/test_MeshManager.h +++ ps/trunk/source/graphics/tests/test_MeshManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -29,8 +29,8 @@ #include "ps/CLogger.h" #include "ps/XML/RelaxNG.h" -static OsPath MOD_PATH(DataDir()/"mods"/"_test.mesh"); -static OsPath CACHE_PATH(DataDir()/"_testcache"); +static OsPath MOD_PATH(DataDir() / "mods" / "_test.mesh" / ""); +static OsPath CACHE_PATH(DataDir() / "_testcache" / ""); const OsPath srcDAE(L"collada/sphere.dae"); const OsPath srcPMD(L"collada/sphere.pmd"); @@ -61,12 +61,8 @@ g_VFS = CreateVfs(); TS_ASSERT_OK(g_VFS->Mount(L"", MOD_PATH)); - 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"collada/", DataDir() / "tests" / "collada" / "", VFS_MOUNT_MUST_EXIST)); + TS_ASSERT_OK(g_VFS->Mount(L"cache/", CACHE_PATH, 0, VFS_MAX_PRIORITY)); } void deinitVfs() Index: ps/trunk/source/graphics/tests/test_TextureConverter.h =================================================================== --- ps/trunk/source/graphics/tests/test_TextureConverter.h +++ ps/trunk/source/graphics/tests/test_TextureConverter.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -35,8 +35,8 @@ DeleteDirectory(DataDir()/"_testcache"); // clean up in case the last test run failed 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"", DataDir() / "mods" / "_test.tex" / "", VFS_MOUNT_MUST_EXIST)); + TS_ASSERT_OK(m_VFS->Mount(L"cache/", DataDir() / "_testcache" / "", 0, VFS_MAX_PRIORITY)); } void tearDown() Index: ps/trunk/source/graphics/tests/test_TextureManager.h =================================================================== --- ps/trunk/source/graphics/tests/test_TextureManager.h +++ ps/trunk/source/graphics/tests/test_TextureManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -36,8 +36,8 @@ DeleteDirectory(DataDir()/"_testcache"); // clean up in case the last test run failed 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"", DataDir() / "mods" / "_test.tex" / "", VFS_MOUNT_MUST_EXIST)); + TS_ASSERT_OK(m_VFS->Mount(L"cache/", DataDir() / "_testcache" / "", 0, VFS_MAX_PRIORITY)); h_mgr_init(); Index: ps/trunk/source/gui/tests/test_GuiManager.h =================================================================== --- ps/trunk/source/gui/tests/test_GuiManager.h +++ ps/trunk/source/gui/tests/test_GuiManager.h @@ -34,8 +34,8 @@ void setUp() { 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"", DataDir() / "mods" / "_test.gui" / "", VFS_MOUNT_MUST_EXIST)); + TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir() / "_testcache" / "", 0, VFS_MAX_PRIORITY)); configDB = new CConfigDB; Index: ps/trunk/source/lib/file/common/real_directory.cpp =================================================================== --- ps/trunk/source/lib/file/common/real_directory.cpp +++ ps/trunk/source/lib/file/common/real_directory.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2021 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,7 @@ RealDirectory::RealDirectory(const OsPath& path, size_t priority, size_t flags) : m_path(path), m_priority(priority), m_flags(flags) { + ENSURE(path.IsDirectory()); } Index: ps/trunk/source/lib/file/vfs/tests/test_vfs_real_path.h =================================================================== --- ps/trunk/source/lib/file/vfs/tests/test_vfs_real_path.h +++ ps/trunk/source/lib/file/vfs/tests/test_vfs_real_path.h @@ -0,0 +1,190 @@ +/* Copyright (C) 2021 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 TEST_FOLDER(DataDir()/"_test.temp"); + +extern PIVFS g_VFS; + +class TestVfsPopulate : public CxxTest::TestSuite +{ + void initVfs() + { + g_VFS = CreateVfs(); + } + + void deinitVfs() + { + if(DirectoryExists(TEST_FOLDER)) + DeleteDirectory(TEST_FOLDER); + + g_VFS.reset(); + } + + void createRealDir(const OsPath& path) + { + if(DirectoryExists(path)) + DeleteDirectory(path); + CreateDirectories(path, 0700, false); + } + +public: + void setUp() + { + initVfs(); + } + + void tearDown() + { + deinitVfs(); + } + + /** + * This is a regression test. Before priorities were used for real directories, things were... Rather undefined. + * The new spec is that the path is relative to the highest priority subdirectory in the path. + * The order remains undefined in case of equal priority. + * (see below for tests on that). + */ + void test_write_path_hijacking() + { + createRealDir(TEST_FOLDER / "cache" / "some_folder"); + createRealDir(TEST_FOLDER / "some_mod" / "cache" / "some_mod"); + + shared_ptr buf(new u8(1)); + + g_VFS->Mount(L"", TEST_FOLDER / "some_mod" / "", 0, 0); + + // Access the subfolder, creating subdirectories in the VFS. + g_VFS->CreateFile(L"cache/some_mod/peek.txt", buf, 0); + + g_VFS->Mount(L"cache/", TEST_FOLDER / "cache" / "", 0, 1); + + OsPath realPath; + g_VFS->GetDirectoryRealPath(L"cache/", realPath); + TS_ASSERT_EQUALS(realPath, TEST_FOLDER / "cache" / ""); + g_VFS->CreateFile(L"cache/test.txt", buf, 0); + g_VFS->GetRealPath(L"cache/test.txt", realPath); + TS_ASSERT_EQUALS(realPath, TEST_FOLDER / "cache" / "test.txt"); + + g_VFS->GetDirectoryRealPath(L"cache/some_mod/", realPath); + TS_ASSERT_EQUALS(realPath, TEST_FOLDER / "cache" / "some_mod" / ""); + g_VFS->CreateFile(L"cache/some_mod/test.txt", buf, 0); + g_VFS->GetRealPath(L"cache/some_mod/test.txt", realPath); + TS_ASSERT_EQUALS(realPath, TEST_FOLDER / "cache" / "some_mod" / "test.txt"); + }; + + void test_priority() + { + createRealDir(TEST_FOLDER / "mod_a"); + createRealDir(TEST_FOLDER / "mod_b"); + + // Check that the real directory points to the highest priority mod. + g_VFS->Mount(L"mod", TEST_FOLDER / "mod_a" / "", 0, 1); + g_VFS->Mount(L"mod", TEST_FOLDER / "mod_b" / "", 0, 0); + + // For consistency, populate everything. + g_VFS->TextRepresentation().c_str(); + + OsPath realPath; + g_VFS->GetDirectoryRealPath(L"mod", realPath); + TS_ASSERT_EQUALS(realPath, TEST_FOLDER / "mod_a" / ""); + }; + + void test_real_path_0() + { + createRealDir(TEST_FOLDER / "mod_0" / "folder_a"); + createRealDir(TEST_FOLDER / "mod_0" / "folder_a" / "subfolder"); + createRealDir(TEST_FOLDER / "mod_0" / "folder_b"); + createRealDir(TEST_FOLDER / "mod_1" / "folder_a"); + createRealDir(TEST_FOLDER / "folder_a"); + + g_VFS->Mount(L"folder_a/", TEST_FOLDER / "folder_a" / "", 0, 2); + g_VFS->Mount(L"", TEST_FOLDER / "mod_1" / "", 0, 1); + g_VFS->Mount(L"", TEST_FOLDER / "mod_0" / "", 0, 0); + + OsPath realPath; + g_VFS->GetDirectoryRealPath(L"folder_a/", realPath); + TS_ASSERT_EQUALS(realPath, TEST_FOLDER / "folder_a" / ""); + // Despite being lower priority, we still load non-conflicting files of mod_0 + g_VFS->GetDirectoryRealPath(L"folder_b/", realPath); + // However their real path is rewritten to mod_1. + TS_ASSERT_EQUALS(realPath, TEST_FOLDER / "mod_1" / "folder_b" / ""); + + // (including sub-subfolders) + g_VFS->GetDirectoryRealPath(L"folder_a/subfolder/", realPath); + // However their real path is rewritten. + TS_ASSERT_EQUALS(realPath, TEST_FOLDER / "folder_a" / "subfolder" / ""); + }; + + void test_real_path_1() + { + createRealDir(TEST_FOLDER / "mod_0" / "folder_a"); + createRealDir(TEST_FOLDER / "mod_1" / "folder_a"); + createRealDir(TEST_FOLDER / "folder_a"); + + // Equal priority, the order is undetermined. + OsPath realPath; + g_VFS->Mount(L"", TEST_FOLDER / "mod_0" / "", 0, 1); + g_VFS->Mount(L"folder_a/", TEST_FOLDER / "folder_a" / "", 0, 1); + g_VFS->Mount(L"", TEST_FOLDER / "mod_1" / "", 0, 0); + + g_VFS->GetDirectoryRealPath(L"folder_a/", realPath); + TS_ASSERT_EQUALS(realPath, TEST_FOLDER / "folder_a" / ""); + }; + + // Same as test_real_path_1, but invert the mounting order + // (may or may not result in different behaviour). + void test_real_path_2() + { + createRealDir(TEST_FOLDER / "mod_0" / "folder_a"); + createRealDir(TEST_FOLDER / "mod_1" / "folder_a"); + createRealDir(TEST_FOLDER / "folder_a"); + + // Equal priority, the order is undetermined. + OsPath realPath; + g_VFS->Mount(L"folder_a/", TEST_FOLDER / "folder_a" / "", 0, 1); + g_VFS->Mount(L"", TEST_FOLDER / "mod_1" / "", 0, 0); + g_VFS->Mount(L"", TEST_FOLDER / "mod_0" / "", 0, 1); + + g_VFS->GetDirectoryRealPath(L"folder_a/", realPath); + TS_ASSERT_EQUALS(realPath, TEST_FOLDER / "folder_a" / ""); + }; + + void test_real_path_subpath() + { + createRealDir(TEST_FOLDER / "mod_0" / "folder" / "subfolder"); + createRealDir(TEST_FOLDER / "mod_1" / "folder" / "subfolder"); + createRealDir(TEST_FOLDER / "other_folder"); + + g_VFS->Mount(L"", TEST_FOLDER / "mod_0" / "", 0, 0); + g_VFS->Mount(L"", TEST_FOLDER / "mod_1" / "", 0, 1); + g_VFS->Mount(L"folder/subfolder/", TEST_FOLDER / "other_folder" / "", 0, 2); + + OsPath realPath; + g_VFS->GetDirectoryRealPath(L"folder/subfolder/", realPath); + TS_ASSERT_EQUALS(realPath, TEST_FOLDER / "other_folder" / ""); + }; +}; Index: ps/trunk/source/lib/file/vfs/tests/test_vfs_util.h =================================================================== --- ps/trunk/source/lib/file/vfs/tests/test_vfs_util.h +++ ps/trunk/source/lib/file/vfs/tests/test_vfs_util.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -26,8 +26,8 @@ #include "lib/file/file_system.h" #include "lib/os_path.h" -static OsPath MOD_PATH(DataDir()/"mods"/"_test.vfs"); -static OsPath CACHE_PATH(DataDir()/"_testcache"); +static OsPath MOD_PATH(DataDir() / "mods" / "_test.vfs" / ""); +static OsPath CACHE_PATH(DataDir() / "_testcache" / ""); extern PIVFS g_VFS; @@ -47,11 +47,7 @@ g_VFS = CreateVfs(); TS_ASSERT_OK(g_VFS->Mount(L"", MOD_PATH)); - - // 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, 0, VFS_MAX_PRIORITY)); } void deinitVfs() Index: ps/trunk/source/lib/file/vfs/vfs.h =================================================================== --- ps/trunk/source/lib/file/vfs/vfs.h +++ ps/trunk/source/lib/file/vfs/vfs.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2021 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; @@ -65,16 +68,7 @@ * ".DELETED" suffix will still apply. * (the default behavior is to hide both the suffixed and unsuffixed files) **/ - 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)) - **/ - VFS_MOUNT_REPLACEABLE = 16 + VFS_MOUNT_KEEP_DELETED = 8 }; // (member functions are thread-safe after the instance has been @@ -95,6 +89,11 @@ * if files are encountered that already exist in the VFS (sub)directories, * the most recent / highest priority/precedence version is preferred. * + * Note that the 'real directory' associated with a VFS Path + * will be relative to the highest priority subdirectory in the path, + * and that in case of equal priority, the order is _undefined_, + * and will depend on the exact order of populate() calls. + * * if files with archive extensions are seen, their contents are added * as well. **/ @@ -144,17 +143,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. * * @param pathname @@ -170,18 +158,32 @@ virtual std::wstring TextRepresentation() const = 0; /** - * retrieve the real (POSIX) pathname underlying a VFS file. + * Retrieve the POSIX pathname a VFS file was loaded from. + * This is distinct from the current 'real' path, since that depends on the parent directory's real path, + * which may have been overwritten by a mod or another call to Mount(). * - * this is useful for passing paths to external libraries. + * This is used by the caching to split by mod, and you also ought to call this to delete a file. + * (note that deleting has other issues, see below). **/ - virtual Status GetRealPath(const VfsPath& pathname, OsPath& realPathname) = 0; + virtual Status GetOriginalPath(const VfsPath& filename, OsPath& loadedPathname) = 0; /** - * retrieve the real (POSIX) pathname underlying a VFS directory. - * - * this is useful for passing paths to external libraries. + * Retrieve the real (POSIX) pathname underlying a VFS file. + * This is useful for passing paths to external libraries. + * Note that this returns the real path relative to the highest priority VFS subpath. + * @param createMissingDirectories - if true, create subdirectories on the file system as required. + * (this defaults to true because, in general, this function is then used to create new files). + **/ + virtual Status GetRealPath(const VfsPath& pathname, OsPath& realPathname, bool createMissingDirectories = true) = 0; + + /** + * Retrieve the real (POSIX) pathname underlying a VFS directory. + * This is useful for passing paths to external libraries. + * Note that this returns the real path relative to the highest priority VFS subpath. + * @param createMissingDirectories - if true, create subdirectories on the file system as required. + * (this defaults to true because, in general, this function is then used to create new files). **/ - virtual Status GetDirectoryRealPath(const VfsPath& pathname, OsPath& realPathname) = 0; + virtual Status GetDirectoryRealPath(const VfsPath& pathname, OsPath& realPathname, bool createMissingDirectories = true) = 0; /** * retrieve the VFS pathname that corresponds to a real file. Index: ps/trunk/source/lib/file/vfs/vfs.cpp =================================================================== --- ps/trunk/source/lib/file/vfs/vfs.cpp +++ ps/trunk/source/lib/file/vfs/vfs.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -54,6 +54,8 @@ virtual Status Mount(const VfsPath& mountPoint, const OsPath& path, size_t flags /* = 0 */, size_t priority /* = 0 */) { + ENSURE(path.IsDirectory()); + std::lock_guard lock(vfs_mutex); if(!DirectoryExists(path)) { @@ -129,7 +131,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_REAL_PATH); if (st == ERR::FILE_ACCESS) return ERR::FILE_ACCESS; @@ -146,32 +148,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); @@ -203,7 +179,7 @@ return textRepresentation; } - virtual Status GetRealPath(const VfsPath& pathname, OsPath& realPathname) + virtual Status GetOriginalPath(const VfsPath& pathname, OsPath& realPathname) { std::lock_guard lock(vfs_mutex); VfsDirectory* directory; VfsFile* file; @@ -212,11 +188,24 @@ return INFO::OK; } - virtual Status GetDirectoryRealPath(const VfsPath& pathname, OsPath& realPathname) + virtual Status GetRealPath(const VfsPath& pathname, OsPath& realPathname, bool createMissingDirectories) + { + std::lock_guard lock(vfs_mutex); + VfsDirectory* directory; VfsFile* file; + size_t flags = VFS_LOOKUP_REAL_PATH | (createMissingDirectories ? VFS_LOOKUP_ADD : 0); + WARN_RETURN_STATUS_IF_ERR(vfs_Lookup(pathname, &m_rootDirectory, directory, &file, flags)); + ENSURE(directory->AssociatedDirectory()); + realPathname = directory->AssociatedDirectory()->Path() / pathname.Filename(); + return INFO::OK; + } + + virtual Status GetDirectoryRealPath(const VfsPath& pathname, OsPath& realPathname, bool createMissingDirectories) { std::lock_guard lock(vfs_mutex); VfsDirectory* directory; - WARN_RETURN_STATUS_IF_ERR(vfs_Lookup(pathname, &m_rootDirectory, directory, NULL)); + size_t flags = VFS_LOOKUP_REAL_PATH | (createMissingDirectories ? VFS_LOOKUP_ADD : 0); + WARN_RETURN_STATUS_IF_ERR(vfs_Lookup(pathname, &m_rootDirectory, directory, NULL, flags)); + ENSURE(directory->AssociatedDirectory()); realPathname = directory->AssociatedDirectory()->Path(); return INFO::OK; } Index: ps/trunk/source/lib/file/vfs/vfs_lookup.h =================================================================== --- ps/trunk/source/lib/file/vfs/vfs_lookup.h +++ ps/trunk/source/lib/file/vfs/vfs_lookup.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2013 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -37,24 +37,24 @@ enum VfsLookupFlags { - // add (if they do not already exist) subdirectory components + // Add (if they do not already exist) subdirectory components // encountered in the path[name]. + // If subdirectores do not exist on disk, they will be created. VFS_LOOKUP_ADD = 1, - // if VFS directories encountered are not already associated - // with a real directory, do so (creating the directories - // if they do not already exist). - VFS_LOOKUP_CREATE = 2, - - // don't populate the directories encountered. this makes sense + // Don't populate the directories encountered. This makes sense // when adding files from an archive, which would otherwise // cause nearly every directory to be populated. - VFS_LOOKUP_SKIP_POPULATE = 4, + VFS_LOOKUP_SKIP_POPULATE = 2, - // 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 + // Perform a 'real path' lookup. + // Because the VFS maps multiple 'disk paths' to a single tree of paths, + // the 'real directory' of a VFS directory at any given time may be almost anything, + // in particular not its real parent directory on disk. + // To make writing predictable, we'll return a path relative to the 'disk path' of the + // highest priority subdirectory found in the lookup path. + // See test_vfs_real_paths.h for examples of this behaviour. + VFS_LOOKUP_REAL_PATH = 4 }; /** Index: ps/trunk/source/lib/file/vfs/vfs_lookup.cpp =================================================================== --- ps/trunk/source/lib/file/vfs/vfs_lookup.cpp +++ ps/trunk/source/lib/file/vfs/vfs_lookup.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2013 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -47,14 +47,12 @@ return INFO::OK; } - // failed because the directory already exists. this can happen - // when the first vfs_Lookup has addMissingDirectories && - // !createMissingDirectories, and the directory is subsequently - // created. return 'success' to attach the existing directory.. + // Failed because the directory already exists. + // Return 'success' to attach the existing directory. if(errno == EEXIST) { - // but first ensure it's really a directory (otherwise, a - // file is "in the way" and needs to be deleted) + // But first ensure it's really a directory + // (otherwise, a file is "in the way" and needs to be deleted). struct stat s; const int ret = wstat(path, &s); ENSURE(ret == 0); // (wmkdir said it existed) @@ -76,22 +74,23 @@ { // extract and validate flags (ensure no unknown bits are set) 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 realPath = (flags & VFS_LOOKUP_REAL_PATH) != 0; + ENSURE((flags & ~(VFS_LOOKUP_ADD|VFS_LOOKUP_SKIP_POPULATE|VFS_LOOKUP_REAL_PATH)) == 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 + // Preserve a guarantee that if pfile then we either return an error or set *pfile, + // and if looking for a real path ensure an associated directory. + if (pfile || (realPath && !directory->AssociatedDirectory())) return ERR::VFS_FILE_NOT_FOUND; else return INFO::OK; @@ -102,46 +101,60 @@ 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; 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))) + // When looking for a real path, we need to keep the path of the highest priority subdirectory. + // If the current directory has an associated directory, and the subdir does not / is lower priority, + // we will overwrite it. + PRealDirectory realDir = directory->AssociatedDirectory(); + if (realPath && realDir && + (!subdirectory->AssociatedDirectory() || + realDir->Priority() > subdirectory->AssociatedDirectory()->Priority())) { - OsPath currentPath; - if(directory->AssociatedDirectory()) // (is NULL when mounting into root) - currentPath = directory->AssociatedDirectory()->Path(); - currentPath = currentPath / subdirectoryName; - - RETURN_STATUS_IF_ERR(CreateDirectory(currentPath)); + OsPath currentPath = directory->AssociatedDirectory()->Path(); + currentPath = currentPath / subdirectoryName / ""; - PRealDirectory realDirectory(new RealDirectory(currentPath, 0, 0)); + // Only actually create the directory if we're in LOOKUP_ADD mode. + if (addMissingDirectories) + RETURN_STATUS_IF_ERR(CreateDirectory(currentPath)); + else if (!DirectoryExists(currentPath)) + return ERR::VFS_DIR_NOT_FOUND; + + // Propagate priority and flags to the subdirectory. + // If it already existed, it will be replaced & the memory freed. + PRealDirectory realDirectory(new RealDirectory(currentPath, + realDir ? realDir->Priority() : 0, + realDir ? realDir->Flags() : 0) + ); RETURN_STATUS_IF_ERR(vfs_Attach(subdirectory, realDirectory)); } - if(!skipPopulate) + if (!skipPopulate) RETURN_STATUS_IF_ERR(vfs_Populate(subdirectory)); directory = subdirectory; } - if(pfile) + if (realPath && !directory->AssociatedDirectory()) + return ERR::VFS_DIR_NOT_FOUND; + + if (pfile) { ENSURE(!pathname.IsDirectory()); const VfsPath filename = pathname.string().substr(pos); *pfile = directory->GetFile(filename); - if(!*pfile) + if (!*pfile) return ERR::VFS_FILE_NOT_FOUND; // NOWARN } Index: ps/trunk/source/lib/file/vfs/vfs_populate.cpp =================================================================== --- ps/trunk/source/lib/file/vfs/vfs_populate.cpp +++ ps/trunk/source/lib/file/vfs/vfs_populate.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -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); + // Avoid un-necessary repopulation by clearing the flag. + if (!shouldPop) + directory->ShouldPopulate(); + return INFO::OK; } Index: ps/trunk/source/main.cpp =================================================================== --- ps/trunk/source/main.cpp +++ ps/trunk/source/main.cpp @@ -593,7 +593,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_MAX_PRIORITY); MountMods(paths, GetMods(args, INIT_MODS)); { Index: ps/trunk/source/network/tests/test_Net.h =================================================================== --- ps/trunk/source/network/tests/test_Net.h +++ ps/trunk/source/network/tests/test_Net.h @@ -40,8 +40,8 @@ void setUp() { 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"", DataDir() / "mods" / "public" / "", VFS_MOUNT_MUST_EXIST)); + TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir() / "_testcache" / "", 0, VFS_MAX_PRIORITY)); CXeromyces::Startup(); // Need some stuff for terrain movement costs: Index: ps/trunk/source/ps/ArchiveBuilder.cpp =================================================================== --- ps/trunk/source/ps/ArchiveBuilder.cpp +++ ps/trunk/source/ps/ArchiveBuilder.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -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: ps/trunk/source/ps/CacheLoader.cpp =================================================================== --- ps/trunk/source/ps/CacheLoader.cpp +++ ps/trunk/source/ps/CacheLoader.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -140,7 +140,7 @@ // Get the mod path OsPath path; - m_VFS->GetRealPath(sourcePath, path); + m_VFS->GetOriginalPath(sourcePath, path); return VfsPath("cache") / path_name_only(path.BeforeCommon(sourcePath).Parent().string().c_str()) / Index: ps/trunk/source/ps/GameSetup/GameSetup.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/GameSetup.cpp +++ ps/trunk/source/ps/GameSetup/GameSetup.cpp @@ -375,17 +375,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"); @@ -394,11 +387,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; } @@ -406,29 +394,24 @@ { 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; + 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); - } + // 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); else - { - 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); - } + 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. + g_VFS->Mount(L"", modUserPath / "user" / "", userFlags, InDevelopmentCopy() ? 0 : priority + 1); } static void InitVfs(const CmdLineArgs& args, int flags) @@ -455,23 +438,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_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(), 0, VFS_MAX_PRIORITY); + g_VFS->Mount(L"screenshots/", paths.UserData()/"screenshots"/"", 0, VFS_MAX_PRIORITY); + g_VFS->Mount(L"saves/", paths.UserData()/"saves"/"", VFS_MOUNT_WATCH, 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: ps/trunk/source/ps/Mod.cpp =================================================================== --- ps/trunk/source/ps/Mod.cpp +++ ps/trunk/source/ps/Mod.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -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: ps/trunk/source/ps/SavedGame.cpp =================================================================== --- ps/trunk/source/ps/SavedGame.cpp +++ ps/trunk/source/ps/SavedGame.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -284,8 +284,8 @@ const VfsPath filename = basename.ChangeExtension(L".0adsave"); OsPath realpath; - // Make sure it exists in VFS and find its real path - if (!VfsFileExists(filename) || g_VFS->GetRealPath(filename, realpath) != INFO::OK) + // Make sure it exists in VFS and find its path + if (!VfsFileExists(filename) || g_VFS->GetOriginalPath(filename, realpath) != INFO::OK) return false; // Error // Remove from VFS Index: ps/trunk/source/ps/tests/test_ConfigDB.h =================================================================== --- ps/trunk/source/ps/tests/test_ConfigDB.h +++ ps/trunk/source/ps/tests/test_ConfigDB.h @@ -30,7 +30,7 @@ void setUp() { g_VFS = CreateVfs(); - TS_ASSERT_OK(g_VFS->Mount(L"config", DataDir()/"_testconfig")); + TS_ASSERT_OK(g_VFS->Mount(L"config", DataDir() / "_testconfig" / "")); configDB = new CConfigDB; } Index: ps/trunk/source/ps/tests/test_Hotkeys.h =================================================================== --- ps/trunk/source/ps/tests/test_Hotkeys.h +++ ps/trunk/source/ps/tests/test_Hotkeys.h @@ -51,8 +51,8 @@ void setUp() { g_VFS = CreateVfs(); - TS_ASSERT_OK(g_VFS->Mount(L"config", DataDir()/"_testconfig")); - TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache")); + TS_ASSERT_OK(g_VFS->Mount(L"config", DataDir() / "_testconfig" / "")); + TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir() / "_testcache" / "")); configDB = new CConfigDB; Index: ps/trunk/source/scriptinterface/tests/test_ScriptConversions.h =================================================================== --- ps/trunk/source/scriptinterface/tests/test_ScriptConversions.h +++ ps/trunk/source/scriptinterface/tests/test_ScriptConversions.h @@ -100,7 +100,7 @@ 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"", DataDir() / "mods" / "_test.sim" / "", VFS_MOUNT_MUST_EXIST)); } void tearDown() Index: ps/trunk/source/simulation2/components/tests/test_Pathfinder.h =================================================================== --- ps/trunk/source/simulation2/components/tests/test_Pathfinder.h +++ ps/trunk/source/simulation2/components/tests/test_Pathfinder.h @@ -39,9 +39,9 @@ void setUp() { 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")); + 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" / "", 0, VFS_MAX_PRIORITY)); CXeromyces::Startup(); Index: ps/trunk/source/simulation2/components/tests/test_scripts.h =================================================================== --- ps/trunk/source/simulation2/components/tests/test_scripts.h +++ ps/trunk/source/simulation2/components/tests/test_scripts.h @@ -30,8 +30,8 @@ void setUp() { 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 + 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 CXeromyces::Startup(); } Index: ps/trunk/source/simulation2/tests/test_CmpTemplateManager.h =================================================================== --- ps/trunk/source/simulation2/tests/test_CmpTemplateManager.h +++ ps/trunk/source/simulation2/tests/test_CmpTemplateManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -37,8 +37,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)); + TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir() / "_testcache" / "", 0, VFS_MAX_PRIORITY)); CXeromyces::Startup(); } @@ -221,9 +221,9 @@ void setUp() { 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"", 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" / "", 0, VFS_MAX_PRIORITY)); CXeromyces::Startup(); } Index: ps/trunk/source/simulation2/tests/test_ComponentManager.h =================================================================== --- ps/trunk/source/simulation2/tests/test_ComponentManager.h +++ ps/trunk/source/simulation2/tests/test_ComponentManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -44,8 +44,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)); + TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir() / "_testcache" / "", 0, VFS_MAX_PRIORITY)); CXeromyces::Startup(); } Index: ps/trunk/source/simulation2/tests/test_Serializer.h =================================================================== --- ps/trunk/source/simulation2/tests/test_Serializer.h +++ ps/trunk/source/simulation2/tests/test_Serializer.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -875,8 +875,8 @@ CXeromyces::Startup(); 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"", DataDir() / "mods" / "public" / "", VFS_MOUNT_MUST_EXIST)); + TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir() / "_testcache" / "", 0, VFS_MAX_PRIORITY)); // Need some stuff for terrain movement costs: // (TODO: this ought to be independent of any graphics code) Index: ps/trunk/source/simulation2/tests/test_Simulation2.h =================================================================== --- ps/trunk/source/simulation2/tests/test_Simulation2.h +++ ps/trunk/source/simulation2/tests/test_Simulation2.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -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)); + TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir() / "_testcache" / "", 0, VFS_MAX_PRIORITY)); CXeromyces::Startup(); }