1
1
forked from 0ad/0ad

Make real directory paths on the VFS predictable (retry)

This is the same commit as 4bb31f084e, reverted in 5d1899785a. Noted
issues were fixed.

Partial revert/fix of 2f19cf86d3 and 2567fee329.

Before this diff, it was possible for a mod containing a cache/ or a
config/ folder to get written to incorrectly.
The issue is VFS can map multiple directories to one 'virtual' path, for
reading mods. However, writing data is problematic: which path to
choose?
The only viable solution is to use a path relative to the highest
priority directory encountered in the VFS path, or write paths could be
'hijacked' by lower-priority mods.

This fixes these issues by:
- Adding a new lookup mode ('Real-path') that explicitly picks the real
path relative to the highest-priority subdirectory in the VFS Path.
- Preventing overwriting a real directory with a lower priority one in
general.
- Revert c0c8132dd4's GetRealPath change, re-introducing the function as
GetOriginalPath.

This also cleans up some duplication that led to empty mod folders in
the user mod path, and cleans up loading the 'user' mod.
It also makes it explicit that a directory must be passed to Mount().

Note that the new 'realpath' lookup can still be somewhat complex with
many mount points at various hierarchy levels, but it is at least
predictable/deterministic without having to be careful about populating
order.

Fixes #2553

Differential Revision: https://code.wildfiregames.com/D3728
This was SVN commit r25107.
This commit is contained in:
wraitii 2021-03-23 12:46:59 +00:00
parent 5d1899785a
commit 87a2c3347f
30 changed files with 406 additions and 215 deletions

View File

@ -48,7 +48,6 @@ RL client:
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'

View File

@ -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 @@ public:
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();
}

View File

@ -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 @@ class TestMeshManager : public CxxTest::TestSuite
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()

View File

@ -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 @@ public:
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()

View File

@ -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 @@ public:
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();

View File

@ -34,8 +34,8 @@ public:
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;

View File

@ -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());
}

View File

@ -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<u8> 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" / "");
};
};

View File

@ -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 @@ class TestVfsUtil : public CxxTest::TestSuite
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()

View File

@ -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 @@ public:
virtual Status Mount(const VfsPath& mountPoint, const OsPath& path, size_t flags /* = 0 */, size_t priority /* = 0 */)
{
ENSURE(path.IsDirectory());
std::lock_guard<std::mutex> lock(vfs_mutex);
if(!DirectoryExists(path))
{
@ -129,7 +131,7 @@ public:
std::lock_guard<std::mutex> 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 @@ public:
return INFO::OK;
}
virtual Status ReplaceFile(const VfsPath& pathname, const shared_ptr<u8>& fileContents, size_t size)
{
std::unique_lock<std::mutex> 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<u8>& fileContents, size_t& size)
{
std::lock_guard<std::mutex> lock(vfs_mutex);
@ -203,7 +179,7 @@ public:
return textRepresentation;
}
virtual Status GetRealPath(const VfsPath& pathname, OsPath& realPathname)
virtual Status GetOriginalPath(const VfsPath& pathname, OsPath& realPathname)
{
std::lock_guard<std::mutex> lock(vfs_mutex);
VfsDirectory* directory; VfsFile* file;
@ -212,11 +188,24 @@ public:
return INFO::OK;
}
virtual Status GetDirectoryRealPath(const VfsPath& pathname, OsPath& realPathname)
virtual Status GetRealPath(const VfsPath& pathname, OsPath& realPathname, bool createMissingDirectories)
{
std::lock_guard<std::mutex> 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<std::mutex> 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;
}

View File

@ -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<size_t>::max();
namespace ERR
{
const Status VFS_DIR_NOT_FOUND = -110100;
@ -65,16 +68,7 @@ enum VfsMountFlags
* ".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 @@ struct IVFS
* 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.
**/
@ -143,17 +142,6 @@ struct IVFS
**/
virtual Status CreateFile(const VfsPath& pathname, const shared_ptr<u8>& 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<u8>& fileContents, size_t size) = 0;
/**
* Read an entire file into memory.
*
@ -170,18 +158,32 @@ struct IVFS
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 GetDirectoryRealPath(const VfsPath& pathname, OsPath& realPathname) = 0;
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, bool createMissingDirectories = true) = 0;
/**
* retrieve the VFS pathname that corresponds to a real file.

View File

@ -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 @@ static Status CreateDirectory(const OsPath& path)
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 @@ Status vfs_Lookup(const VfsPath& pathname, VfsDirectory* startDirectory, VfsDire
{
// 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 @@ Status vfs_Lookup(const VfsPath& pathname, VfsDirectory* startDirectory, VfsDire
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;
OsPath currentPath = directory->AssociatedDirectory()->Path();
currentPath = currentPath / subdirectoryName / "";
// 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;
PRealDirectory realDirectory(new RealDirectory(currentPath, 0, 0));
// 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
}

View File

@ -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 @@ class VfsDirectory;
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
};
/**

View File

@ -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_Populate(VfsDirectory* directory)
Status vfs_Attach(VfsDirectory* directory, const PRealDirectory& realDirectory)
{
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;
}

View File

@ -593,7 +593,8 @@ static void RunGameOrAtlas(int argc, const char* argv[])
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));
{

View File

@ -40,8 +40,8 @@ public:
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:

View File

@ -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 @@ CArchiveBuilder::CArchiveBuilder(const OsPath& mod, const OsPath& tempdir) :
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<void*>(this), 0, vfs::DIR_RECURSIVE);

View File

@ -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 @@ VfsPath CCacheLoader::LooseCachePath(const VfsPath& sourcePath, const MD5& initi
// 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()) /

View File

@ -375,17 +375,10 @@ ErrorReactionInternal psDisplayError(const wchar_t* UNUSED(text), size_t UNUSED(
const std::vector<CStr>& 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 @@ const std::vector<CStr>& GetMods(const CmdLineArgs& args, int flags)
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 @@ void MountMods(const Paths& paths, const std::vector<CStr>& mods)
{
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);
// 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"", modUserPath / modName/"", userFlags, 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 @@ static void InitVfs(const CmdLineArgs& args, int flags)
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.
}

View File

@ -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 @@ JS::Value Mod::GetAvailableMods(const ScriptInterface& scriptInterface)
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 @@ JS::Value Mod::GetAvailableMods(const ScriptInterface& scriptInterface)
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 @@ void Mod::CacheEnabledModVersions(const shared_ptr<ScriptContext>& scriptContext
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;

View File

@ -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 @@ bool SavedGames::DeleteSavedGame(const std::wstring& name)
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

View File

@ -30,7 +30,7 @@ public:
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;
}

View File

@ -51,8 +51,8 @@ public:
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;

View File

@ -100,7 +100,7 @@ public:
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()

View File

@ -39,9 +39,9 @@ public:
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();

View File

@ -30,8 +30,8 @@ public:
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();
}

View File

@ -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 @@ public:
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 @@ public:
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();
}

View File

@ -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 @@ public:
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();
}

View File

@ -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 @@ public:
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)

View File

@ -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 @@ public:
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();
}