1
0
forked from 0ad/0ad
4bb31f084e led to an issue in windows-specific code not detected by the
tests. I'll debug later.

This was SVN commit r25105.
This commit is contained in:
wraitii 2021-03-22 19:52:07 +00:00
parent 4bb31f084e
commit 5d1899785a
24 changed files with 187 additions and 375 deletions

View File

@ -48,6 +48,7 @@ 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) 2021 Wildfire Games.
/* Copyright (C) 2017 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -62,7 +62,11 @@ class TestMeshManager : public CxxTest::TestSuite
TS_ASSERT_OK(g_VFS->Mount(L"", MOD_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));
// 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));
}
void deinitVfs()

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2021 Wildfire Games.
/* Copyright (C) 2017 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -36,7 +36,7 @@ public:
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", 0, VFS_MAX_PRIORITY));
TS_ASSERT_OK(m_VFS->Mount(L"cache/", DataDir()/"_testcache"));
}
void tearDown()

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2021 Wildfire Games.
/* Copyright (C) 2017 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -37,7 +37,7 @@ public:
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", 0, VFS_MAX_PRIORITY));
TS_ASSERT_OK(m_VFS->Mount(L"cache/", DataDir()/"_testcache"));
h_mgr_init();

View File

@ -35,7 +35,7 @@ public:
{
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", 0, VFS_MAX_PRIORITY));
TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache"));
configDB = new CConfigDB;

View File

@ -1,190 +0,0 @@
/* 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) 2021 Wildfire Games.
/* Copyright (C) 2019 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -47,7 +47,11 @@ class TestVfsUtil : public CxxTest::TestSuite
g_VFS = CreateVfs();
TS_ASSERT_OK(g_VFS->Mount(L"", MOD_PATH));
TS_ASSERT_OK(g_VFS->Mount(L"cache/", CACHE_PATH, 0, VFS_MAX_PRIORITY));
// 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));
}
void deinitVfs()

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2021 Wildfire Games.
/* Copyright (C) 2017 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -129,7 +129,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_REAL_PATH);
st = vfs_Lookup(pathname, &m_rootDirectory, directory, 0, VFS_LOOKUP_ADD|VFS_LOOKUP_CREATE|VFS_LOOKUP_CREATE_ALWAYS);
if (st == ERR::FILE_ACCESS)
return ERR::FILE_ACCESS;
@ -146,6 +146,32 @@ 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);
@ -177,7 +203,7 @@ public:
return textRepresentation;
}
virtual Status GetOriginalPath(const VfsPath& pathname, OsPath& realPathname)
virtual Status GetRealPath(const VfsPath& pathname, OsPath& realPathname)
{
std::lock_guard<std::mutex> lock(vfs_mutex);
VfsDirectory* directory; VfsFile* file;
@ -186,24 +212,11 @@ public:
return INFO::OK;
}
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)
virtual Status GetDirectoryRealPath(const VfsPath& pathname, OsPath& realPathname)
{
std::lock_guard<std::mutex> lock(vfs_mutex);
VfsDirectory* directory;
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());
WARN_RETURN_STATUS_IF_ERR(vfs_Lookup(pathname, &m_rootDirectory, directory, NULL));
realPathname = directory->AssociatedDirectory()->Path();
return INFO::OK;
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2021 Wildfire Games.
/* Copyright (C) 2017 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -31,9 +31,6 @@
#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;
@ -68,7 +65,16 @@ enum VfsMountFlags
* ".DELETED" suffix will still apply.
* (the default behavior is to hide both the suffixed and unsuffixed files)
**/
VFS_MOUNT_KEEP_DELETED = 8
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
};
// (member functions are thread-safe after the instance has been
@ -89,11 +95,6 @@ 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.
**/
@ -142,6 +143,17 @@ 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.
*
@ -158,32 +170,18 @@ struct IVFS
virtual std::wstring TextRepresentation() const = 0;
/**
* 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().
* retrieve the real (POSIX) pathname underlying a VFS file.
*
* 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).
* this is useful for passing paths to external libraries.
**/
virtual Status GetOriginalPath(const VfsPath& filename, OsPath& loadedPathname) = 0;
virtual Status GetRealPath(const VfsPath& pathname, OsPath& realPathname) = 0;
/**
* 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).
* retrieve the real (POSIX) pathname underlying a VFS directory.
*
* this is useful for passing paths to external libraries.
**/
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;
virtual Status GetDirectoryRealPath(const VfsPath& pathname, OsPath& realPathname) = 0;
/**
* retrieve the VFS pathname that corresponds to a real file.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2021 Wildfire Games.
/* Copyright (C) 2013 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -47,12 +47,14 @@ static Status CreateDirectory(const OsPath& path)
return INFO::OK;
}
// Failed because the directory already exists.
// Return 'success' to attach the existing directory.
// 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..
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)
@ -74,23 +76,22 @@ 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 realPath = (flags & VFS_LOOKUP_REAL_PATH) != 0;
ENSURE((flags & ~(VFS_LOOKUP_ADD|VFS_LOOKUP_SKIP_POPULATE|VFS_LOOKUP_REAL_PATH)) == 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);
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)
{
// 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()))
if(pfile) // preserve a guarantee that if pfile then we either return an error or set *pfile
return ERR::VFS_FILE_NOT_FOUND;
else
return INFO::OK;
@ -101,60 +102,46 @@ 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
}
// 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()))
if(createMissingDirectories && (!subdirectory->AssociatedDirectory()
|| (createAlways && (subdirectory->AssociatedDirectory()->Flags() & VFS_MOUNT_REPLACEABLE) != 0)))
{
OsPath currentPath = directory->AssociatedDirectory()->Path();
OsPath currentPath;
if(directory->AssociatedDirectory()) // (is NULL when mounting into root)
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;
RETURN_STATUS_IF_ERR(CreateDirectory(currentPath));
// 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)
);
PRealDirectory realDirectory(new RealDirectory(currentPath, 0, 0));
RETURN_STATUS_IF_ERR(vfs_Attach(subdirectory, realDirectory));
}
if (!skipPopulate)
if(!skipPopulate)
RETURN_STATUS_IF_ERR(vfs_Populate(subdirectory));
directory = subdirectory;
}
if (realPath && !directory->AssociatedDirectory())
return ERR::VFS_DIR_NOT_FOUND;
if (pfile)
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) 2021 Wildfire Games.
/* Copyright (C) 2013 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,
// Don't populate the directories encountered. This makes sense
// 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
// when adding files from an archive, which would otherwise
// cause nearly every directory to be populated.
VFS_LOOKUP_SKIP_POPULATE = 2,
VFS_LOOKUP_SKIP_POPULATE = 4,
// 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
// 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
};
/**

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2021 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
@ -168,29 +168,7 @@ 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();
directory->SetAssociatedDirectory(realDirectory);
return INFO::OK;
}

View File

@ -593,8 +593,7 @@ static void RunGameOrAtlas(int argc, const char* argv[])
Paths paths(args);
g_VFS = CreateVfs();
// Mount with highest priority, we don't want mods overwriting this.
g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE, VFS_MAX_PRIORITY);
g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE);
MountMods(paths, GetMods(args, INIT_MODS));
{

View File

@ -41,7 +41,7 @@ public:
{
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", 0, VFS_MAX_PRIORITY));
TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache"));
CXeromyces::Startup();
// Need some stuff for terrain movement costs:

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2021 Wildfire Games.
/* Copyright (C) 2020 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"/"", 0, VFS_MAX_PRIORITY);
m_VFS->Mount(L"cache/", m_TempDir/"_archivecache"/"");
// 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, VFS_MAX_PRIORITY);
m_VFS->Mount(L"", mod/"", VFS_MOUNT_MUST_EXIST | VFS_MOUNT_KEEP_DELETED, (size_t)-1);
// 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) 2021 Wildfire Games.
/* Copyright (C) 2018 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->GetOriginalPath(sourcePath, path);
m_VFS->GetRealPath(sourcePath, path);
return VfsPath("cache") /
path_name_only(path.BeforeCommon(sourcePath).Parent().string().c_str()) /

View File

@ -375,10 +375,17 @@ 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");
@ -387,6 +394,11 @@ 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;
}
@ -394,24 +406,29 @@ 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)
{
priority = i + 1; // Mods are higher priority than regular mountings, which default to priority 0
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;
OsPath modName(mods[i]);
// 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);
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
g_VFS->Mount(L"", modUserPath / modName/"", userFlags, priority);
{
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);
}
}
// 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)
@ -438,20 +455,23 @@ static void InitVfs(const CmdLineArgs& args, int flags)
g_VFS = CreateVfs();
const OsPath readonlyConfig = paths.RData()/"config"/"";
g_VFS->Mount(L"config/", readonlyConfig);
// 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).
// Engine localization files.
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) 2021 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -59,8 +59,7 @@ JS::Value Mod::GetAvailableMods(const ScriptInterface& scriptInterface)
for (DirectoryNames::iterator iter = modDirs.begin(); iter != modDirs.end(); ++iter)
{
vfs->Clear();
// 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)
if (vfs->Mount(L"", modPath / *iter, VFS_MOUNT_MUST_EXIST) < 0)
continue;
CVFSFile modinfo;
@ -86,8 +85,7 @@ JS::Value Mod::GetAvailableMods(const ScriptInterface& scriptInterface)
continue;
vfs->Clear();
// 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)
if (vfs->Mount(L"", modUserPath / *iter, VFS_MOUNT_MUST_EXIST) < 0)
continue;
CVFSFile modinfo;
@ -116,8 +114,8 @@ void Mod::CacheEnabledModVersions(const shared_ptr<ScriptContext>& scriptContext
for (const CStr& mod : g_modsLoaded)
{
// Ignore mod mod as it is irrelevant for compatibility checks
if (mod == "mod")
// Ignore user and mod mod as they are irrelevant for compatibility checks
if (mod == "mod" || mod == "user")
continue;
CStr version;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2021 Wildfire Games.
/* Copyright (C) 2020 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 path
if (!VfsFileExists(filename) || g_VFS->GetOriginalPath(filename, realpath) != INFO::OK)
// Make sure it exists in VFS and find its real path
if (!VfsFileExists(filename) || g_VFS->GetRealPath(filename, realpath) != INFO::OK)
return false; // Error
// Remove from VFS

View File

@ -41,7 +41,7 @@ public:
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", 0, VFS_MAX_PRIORITY));
TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache"));
CXeromyces::Startup();

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2021 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -38,7 +38,7 @@ public:
{
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", 0, VFS_MAX_PRIORITY));
TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache"));
CXeromyces::Startup();
}
@ -223,7 +223,7 @@ public:
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", 0, VFS_MAX_PRIORITY));
TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache"));
CXeromyces::Startup();
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2021 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -45,7 +45,7 @@ public:
{
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", 0, VFS_MAX_PRIORITY));
TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache"));
CXeromyces::Startup();
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2021 Wildfire Games.
/* Copyright (C) 2020 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -876,7 +876,7 @@ public:
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", 0, VFS_MAX_PRIORITY));
TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache"));
// 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) 2021 Wildfire Games.
/* Copyright (C) 2019 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -44,7 +44,7 @@ public:
{
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", 0, VFS_MAX_PRIORITY));
TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache"));
CXeromyces::Startup();
}