summaryrefslogtreecommitdiffstats
path: root/src/core/file_sys/vfs
diff options
context:
space:
mode:
authorFearlessTobi <thm.frey@gmail.com>2024-01-16 06:23:01 +0100
committerLiam <byteslice@airmail.cc>2024-01-25 22:40:42 +0100
commit0f9288e38d80c6c63a545934557501fae40d3d83 (patch)
tree0643100d2471a1545dbfb447319b6ea26fdd6b63 /src/core/file_sys/vfs
parentfs: Move fsp_srv subclasses to separate files (diff)
downloadyuzu-0f9288e38d80c6c63a545934557501fae40d3d83.tar
yuzu-0f9288e38d80c6c63a545934557501fae40d3d83.tar.gz
yuzu-0f9288e38d80c6c63a545934557501fae40d3d83.tar.bz2
yuzu-0f9288e38d80c6c63a545934557501fae40d3d83.tar.lz
yuzu-0f9288e38d80c6c63a545934557501fae40d3d83.tar.xz
yuzu-0f9288e38d80c6c63a545934557501fae40d3d83.tar.zst
yuzu-0f9288e38d80c6c63a545934557501fae40d3d83.zip
Diffstat (limited to 'src/core/file_sys/vfs')
-rw-r--r--src/core/file_sys/vfs/vfs.cpp552
-rw-r--r--src/core/file_sys/vfs/vfs.h327
-rw-r--r--src/core/file_sys/vfs/vfs_cached.cpp63
-rw-r--r--src/core/file_sys/vfs/vfs_cached.h31
-rw-r--r--src/core/file_sys/vfs/vfs_concat.cpp192
-rw-r--r--src/core/file_sys/vfs/vfs_concat.h57
-rw-r--r--src/core/file_sys/vfs/vfs_layered.cpp132
-rw-r--r--src/core/file_sys/vfs/vfs_layered.h46
-rw-r--r--src/core/file_sys/vfs/vfs_offset.cpp98
-rw-r--r--src/core/file_sys/vfs/vfs_offset.h50
-rw-r--r--src/core/file_sys/vfs/vfs_real.cpp528
-rw-r--r--src/core/file_sys/vfs/vfs_real.h145
-rw-r--r--src/core/file_sys/vfs/vfs_static.h80
-rw-r--r--src/core/file_sys/vfs/vfs_types.h29
-rw-r--r--src/core/file_sys/vfs/vfs_vector.cpp133
-rw-r--r--src/core/file_sys/vfs/vfs_vector.h131
16 files changed, 2594 insertions, 0 deletions
diff --git a/src/core/file_sys/vfs/vfs.cpp b/src/core/file_sys/vfs/vfs.cpp
new file mode 100644
index 000000000..b88a5f91d
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs.cpp
@@ -0,0 +1,552 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <numeric>
+#include <string>
+#include "common/fs/path_util.h"
+#include "core/file_sys/mode.h"
+#include "core/file_sys/vfs/vfs.h"
+
+namespace FileSys {
+
+VfsFilesystem::VfsFilesystem(VirtualDir root_) : root(std::move(root_)) {}
+
+VfsFilesystem::~VfsFilesystem() = default;
+
+std::string VfsFilesystem::GetName() const {
+ return root->GetName();
+}
+
+bool VfsFilesystem::IsReadable() const {
+ return root->IsReadable();
+}
+
+bool VfsFilesystem::IsWritable() const {
+ return root->IsWritable();
+}
+
+VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const {
+ const auto path = Common::FS::SanitizePath(path_);
+ if (root->GetFileRelative(path) != nullptr)
+ return VfsEntryType::File;
+ if (root->GetDirectoryRelative(path) != nullptr)
+ return VfsEntryType::Directory;
+
+ return VfsEntryType::None;
+}
+
+VirtualFile VfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
+ const auto path = Common::FS::SanitizePath(path_);
+ return root->GetFileRelative(path);
+}
+
+VirtualFile VfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
+ const auto path = Common::FS::SanitizePath(path_);
+ return root->CreateFileRelative(path);
+}
+
+VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) {
+ const auto old_path = Common::FS::SanitizePath(old_path_);
+ const auto new_path = Common::FS::SanitizePath(new_path_);
+
+ // VfsDirectory impls are only required to implement copy across the current directory.
+ if (Common::FS::GetParentPath(old_path) == Common::FS::GetParentPath(new_path)) {
+ if (!root->Copy(Common::FS::GetFilename(old_path), Common::FS::GetFilename(new_path)))
+ return nullptr;
+ return OpenFile(new_path, Mode::ReadWrite);
+ }
+
+ // Do it using RawCopy. Non-default impls are encouraged to optimize this.
+ const auto old_file = OpenFile(old_path, Mode::Read);
+ if (old_file == nullptr)
+ return nullptr;
+ auto new_file = OpenFile(new_path, Mode::Read);
+ if (new_file != nullptr)
+ return nullptr;
+ new_file = CreateFile(new_path, Mode::Write);
+ if (new_file == nullptr)
+ return nullptr;
+ if (!VfsRawCopy(old_file, new_file))
+ return nullptr;
+ return new_file;
+}
+
+VirtualFile VfsFilesystem::MoveFile(std::string_view old_path, std::string_view new_path) {
+ const auto sanitized_old_path = Common::FS::SanitizePath(old_path);
+ const auto sanitized_new_path = Common::FS::SanitizePath(new_path);
+
+ // Again, non-default impls are highly encouraged to provide a more optimized version of this.
+ auto out = CopyFile(sanitized_old_path, sanitized_new_path);
+ if (out == nullptr)
+ return nullptr;
+ if (DeleteFile(sanitized_old_path))
+ return out;
+ return nullptr;
+}
+
+bool VfsFilesystem::DeleteFile(std::string_view path_) {
+ const auto path = Common::FS::SanitizePath(path_);
+ auto parent = OpenDirectory(Common::FS::GetParentPath(path), Mode::Write);
+ if (parent == nullptr)
+ return false;
+ return parent->DeleteFile(Common::FS::GetFilename(path));
+}
+
+VirtualDir VfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) {
+ const auto path = Common::FS::SanitizePath(path_);
+ return root->GetDirectoryRelative(path);
+}
+
+VirtualDir VfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
+ const auto path = Common::FS::SanitizePath(path_);
+ return root->CreateDirectoryRelative(path);
+}
+
+VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_view new_path_) {
+ const auto old_path = Common::FS::SanitizePath(old_path_);
+ const auto new_path = Common::FS::SanitizePath(new_path_);
+
+ // Non-default impls are highly encouraged to provide a more optimized version of this.
+ auto old_dir = OpenDirectory(old_path, Mode::Read);
+ if (old_dir == nullptr)
+ return nullptr;
+ auto new_dir = OpenDirectory(new_path, Mode::Read);
+ if (new_dir != nullptr)
+ return nullptr;
+ new_dir = CreateDirectory(new_path, Mode::Write);
+ if (new_dir == nullptr)
+ return nullptr;
+
+ for (const auto& file : old_dir->GetFiles()) {
+ const auto x = CopyFile(old_path + '/' + file->GetName(), new_path + '/' + file->GetName());
+ if (x == nullptr)
+ return nullptr;
+ }
+
+ for (const auto& dir : old_dir->GetSubdirectories()) {
+ const auto x =
+ CopyDirectory(old_path + '/' + dir->GetName(), new_path + '/' + dir->GetName());
+ if (x == nullptr)
+ return nullptr;
+ }
+
+ return new_dir;
+}
+
+VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path, std::string_view new_path) {
+ const auto sanitized_old_path = Common::FS::SanitizePath(old_path);
+ const auto sanitized_new_path = Common::FS::SanitizePath(new_path);
+
+ // Non-default impls are highly encouraged to provide a more optimized version of this.
+ auto out = CopyDirectory(sanitized_old_path, sanitized_new_path);
+ if (out == nullptr)
+ return nullptr;
+ if (DeleteDirectory(sanitized_old_path))
+ return out;
+ return nullptr;
+}
+
+bool VfsFilesystem::DeleteDirectory(std::string_view path_) {
+ const auto path = Common::FS::SanitizePath(path_);
+ auto parent = OpenDirectory(Common::FS::GetParentPath(path), Mode::Write);
+ if (parent == nullptr)
+ return false;
+ return parent->DeleteSubdirectoryRecursive(Common::FS::GetFilename(path));
+}
+
+VfsFile::~VfsFile() = default;
+
+std::string VfsFile::GetExtension() const {
+ return std::string(Common::FS::GetExtensionFromFilename(GetName()));
+}
+
+VfsDirectory::~VfsDirectory() = default;
+
+std::optional<u8> VfsFile::ReadByte(std::size_t offset) const {
+ u8 out{};
+ const std::size_t size = Read(&out, sizeof(u8), offset);
+ if (size == 1) {
+ return out;
+ }
+
+ return std::nullopt;
+}
+
+std::vector<u8> VfsFile::ReadBytes(std::size_t size, std::size_t offset) const {
+ std::vector<u8> out(size);
+ std::size_t read_size = Read(out.data(), size, offset);
+ out.resize(read_size);
+ return out;
+}
+
+std::vector<u8> VfsFile::ReadAllBytes() const {
+ return ReadBytes(GetSize());
+}
+
+bool VfsFile::WriteByte(u8 data, std::size_t offset) {
+ return Write(&data, 1, offset) == 1;
+}
+
+std::size_t VfsFile::WriteBytes(const std::vector<u8>& data, std::size_t offset) {
+ return Write(data.data(), data.size(), offset);
+}
+
+std::string VfsFile::GetFullPath() const {
+ if (GetContainingDirectory() == nullptr)
+ return '/' + GetName();
+
+ return GetContainingDirectory()->GetFullPath() + '/' + GetName();
+}
+
+VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const {
+ auto vec = Common::FS::SplitPathComponents(path);
+ if (vec.empty()) {
+ return nullptr;
+ }
+
+ if (vec.size() == 1) {
+ return GetFile(vec[0]);
+ }
+
+ auto dir = GetSubdirectory(vec[0]);
+ for (std::size_t component = 1; component < vec.size() - 1; ++component) {
+ if (dir == nullptr) {
+ return nullptr;
+ }
+
+ dir = dir->GetSubdirectory(vec[component]);
+ }
+
+ if (dir == nullptr) {
+ return nullptr;
+ }
+
+ return dir->GetFile(vec.back());
+}
+
+VirtualFile VfsDirectory::GetFileAbsolute(std::string_view path) const {
+ if (IsRoot()) {
+ return GetFileRelative(path);
+ }
+
+ return GetParentDirectory()->GetFileAbsolute(path);
+}
+
+VirtualDir VfsDirectory::GetDirectoryRelative(std::string_view path) const {
+ auto vec = Common::FS::SplitPathComponents(path);
+ if (vec.empty()) {
+ // TODO(DarkLordZach): Return this directory if path is '/' or similar. Can't currently
+ // because of const-ness
+ return nullptr;
+ }
+
+ auto dir = GetSubdirectory(vec[0]);
+ for (std::size_t component = 1; component < vec.size(); ++component) {
+ if (dir == nullptr) {
+ return nullptr;
+ }
+
+ dir = dir->GetSubdirectory(vec[component]);
+ }
+
+ return dir;
+}
+
+VirtualDir VfsDirectory::GetDirectoryAbsolute(std::string_view path) const {
+ if (IsRoot()) {
+ return GetDirectoryRelative(path);
+ }
+
+ return GetParentDirectory()->GetDirectoryAbsolute(path);
+}
+
+VirtualFile VfsDirectory::GetFile(std::string_view name) const {
+ const auto& files = GetFiles();
+ const auto iter = std::find_if(files.begin(), files.end(),
+ [&name](const auto& file1) { return name == file1->GetName(); });
+ return iter == files.end() ? nullptr : *iter;
+}
+
+FileTimeStampRaw VfsDirectory::GetFileTimeStamp([[maybe_unused]] std::string_view path) const {
+ return {};
+}
+
+VirtualDir VfsDirectory::GetSubdirectory(std::string_view name) const {
+ const auto& subs = GetSubdirectories();
+ const auto iter = std::find_if(subs.begin(), subs.end(),
+ [&name](const auto& file1) { return name == file1->GetName(); });
+ return iter == subs.end() ? nullptr : *iter;
+}
+
+bool VfsDirectory::IsRoot() const {
+ return GetParentDirectory() == nullptr;
+}
+
+std::size_t VfsDirectory::GetSize() const {
+ const auto& files = GetFiles();
+ const auto sum_sizes = [](const auto& range) {
+ return std::accumulate(range.begin(), range.end(), 0ULL,
+ [](const auto& f1, const auto& f2) { return f1 + f2->GetSize(); });
+ };
+
+ const auto file_total = sum_sizes(files);
+ const auto& sub_dir = GetSubdirectories();
+ const auto subdir_total = sum_sizes(sub_dir);
+
+ return file_total + subdir_total;
+}
+
+VirtualFile VfsDirectory::CreateFileRelative(std::string_view path) {
+ auto vec = Common::FS::SplitPathComponents(path);
+ if (vec.empty()) {
+ return nullptr;
+ }
+
+ if (vec.size() == 1) {
+ return CreateFile(vec[0]);
+ }
+
+ auto dir = GetSubdirectory(vec[0]);
+ if (dir == nullptr) {
+ dir = CreateSubdirectory(vec[0]);
+ if (dir == nullptr) {
+ return nullptr;
+ }
+ }
+
+ return dir->CreateFileRelative(Common::FS::GetPathWithoutTop(path));
+}
+
+VirtualFile VfsDirectory::CreateFileAbsolute(std::string_view path) {
+ if (IsRoot()) {
+ return CreateFileRelative(path);
+ }
+
+ return GetParentDirectory()->CreateFileAbsolute(path);
+}
+
+VirtualDir VfsDirectory::CreateDirectoryRelative(std::string_view path) {
+ auto vec = Common::FS::SplitPathComponents(path);
+ if (vec.empty()) {
+ return nullptr;
+ }
+
+ if (vec.size() == 1) {
+ return CreateSubdirectory(vec[0]);
+ }
+
+ auto dir = GetSubdirectory(vec[0]);
+ if (dir == nullptr) {
+ dir = CreateSubdirectory(vec[0]);
+ if (dir == nullptr) {
+ return nullptr;
+ }
+ }
+
+ return dir->CreateDirectoryRelative(Common::FS::GetPathWithoutTop(path));
+}
+
+VirtualDir VfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
+ if (IsRoot()) {
+ return CreateDirectoryRelative(path);
+ }
+
+ return GetParentDirectory()->CreateDirectoryAbsolute(path);
+}
+
+bool VfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
+ auto dir = GetSubdirectory(name);
+ if (dir == nullptr) {
+ return false;
+ }
+
+ bool success = true;
+ for (const auto& file : dir->GetFiles()) {
+ if (!DeleteFile(file->GetName())) {
+ success = false;
+ }
+ }
+
+ for (const auto& sdir : dir->GetSubdirectories()) {
+ if (!dir->DeleteSubdirectoryRecursive(sdir->GetName())) {
+ success = false;
+ }
+ }
+
+ return success;
+}
+
+bool VfsDirectory::CleanSubdirectoryRecursive(std::string_view name) {
+ auto dir = GetSubdirectory(name);
+ if (dir == nullptr) {
+ return false;
+ }
+
+ bool success = true;
+ for (const auto& file : dir->GetFiles()) {
+ if (!dir->DeleteFile(file->GetName())) {
+ success = false;
+ }
+ }
+
+ for (const auto& sdir : dir->GetSubdirectories()) {
+ if (!dir->DeleteSubdirectoryRecursive(sdir->GetName())) {
+ success = false;
+ }
+ }
+
+ return success;
+}
+
+bool VfsDirectory::Copy(std::string_view src, std::string_view dest) {
+ const auto f1 = GetFile(src);
+ auto f2 = CreateFile(dest);
+ if (f1 == nullptr || f2 == nullptr) {
+ return false;
+ }
+
+ if (!f2->Resize(f1->GetSize())) {
+ DeleteFile(dest);
+ return false;
+ }
+
+ return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize();
+}
+
+std::map<std::string, VfsEntryType, std::less<>> VfsDirectory::GetEntries() const {
+ std::map<std::string, VfsEntryType, std::less<>> out;
+ for (const auto& dir : GetSubdirectories())
+ out.emplace(dir->GetName(), VfsEntryType::Directory);
+ for (const auto& file : GetFiles())
+ out.emplace(file->GetName(), VfsEntryType::File);
+ return out;
+}
+
+std::string VfsDirectory::GetFullPath() const {
+ if (IsRoot())
+ return GetName();
+
+ return GetParentDirectory()->GetFullPath() + '/' + GetName();
+}
+
+bool ReadOnlyVfsDirectory::IsWritable() const {
+ return false;
+}
+
+bool ReadOnlyVfsDirectory::IsReadable() const {
+ return true;
+}
+
+VirtualDir ReadOnlyVfsDirectory::CreateSubdirectory(std::string_view name) {
+ return nullptr;
+}
+
+VirtualFile ReadOnlyVfsDirectory::CreateFile(std::string_view name) {
+ return nullptr;
+}
+
+VirtualFile ReadOnlyVfsDirectory::CreateFileAbsolute(std::string_view path) {
+ return nullptr;
+}
+
+VirtualFile ReadOnlyVfsDirectory::CreateFileRelative(std::string_view path) {
+ return nullptr;
+}
+
+VirtualDir ReadOnlyVfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
+ return nullptr;
+}
+
+VirtualDir ReadOnlyVfsDirectory::CreateDirectoryRelative(std::string_view path) {
+ return nullptr;
+}
+
+bool ReadOnlyVfsDirectory::DeleteSubdirectory(std::string_view name) {
+ return false;
+}
+
+bool ReadOnlyVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
+ return false;
+}
+
+bool ReadOnlyVfsDirectory::CleanSubdirectoryRecursive(std::string_view name) {
+ return false;
+}
+
+bool ReadOnlyVfsDirectory::DeleteFile(std::string_view name) {
+ return false;
+}
+
+bool ReadOnlyVfsDirectory::Rename(std::string_view name) {
+ return false;
+}
+
+bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t block_size) {
+ if (file1->GetSize() != file2->GetSize())
+ return false;
+
+ std::vector<u8> f1_v(block_size);
+ std::vector<u8> f2_v(block_size);
+ for (std::size_t i = 0; i < file1->GetSize(); i += block_size) {
+ auto f1_vs = file1->Read(f1_v.data(), block_size, i);
+ auto f2_vs = file2->Read(f2_v.data(), block_size, i);
+
+ if (f1_vs != f2_vs)
+ return false;
+ auto iters = std::mismatch(f1_v.begin(), f1_v.end(), f2_v.begin(), f2_v.end());
+ if (iters.first != f1_v.end() && iters.second != f2_v.end())
+ return false;
+ }
+
+ return true;
+}
+
+bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t block_size) {
+ if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
+ return false;
+ if (!dest->Resize(src->GetSize()))
+ return false;
+
+ std::vector<u8> temp(std::min(block_size, src->GetSize()));
+ for (std::size_t i = 0; i < src->GetSize(); i += block_size) {
+ const auto read = std::min(block_size, src->GetSize() - i);
+
+ if (src->Read(temp.data(), read, i) != read) {
+ return false;
+ }
+
+ if (dest->Write(temp.data(), read, i) != read) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, std::size_t block_size) {
+ if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
+ return false;
+
+ for (const auto& file : src->GetFiles()) {
+ const auto out = dest->CreateFile(file->GetName());
+ if (!VfsRawCopy(file, out, block_size))
+ return false;
+ }
+
+ for (const auto& dir : src->GetSubdirectories()) {
+ const auto out = dest->CreateSubdirectory(dir->GetName());
+ if (!VfsRawCopyD(dir, out, block_size))
+ return false;
+ }
+
+ return true;
+}
+
+VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) {
+ const auto res = rel->GetDirectoryRelative(path);
+ if (res == nullptr)
+ return rel->CreateDirectoryRelative(path);
+ return res;
+}
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs.h b/src/core/file_sys/vfs/vfs.h
new file mode 100644
index 000000000..6830244e3
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs.h
@@ -0,0 +1,327 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <optional>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/file_sys/vfs/vfs_types.h"
+
+namespace FileSys {
+
+enum class Mode : u32;
+
+// An enumeration representing what can be at the end of a path in a VfsFilesystem
+enum class VfsEntryType {
+ None,
+ File,
+ Directory,
+};
+
+// A class representing an abstract filesystem. A default implementation given the root VirtualDir
+// is provided for convenience, but if the Vfs implementation has any additional state or
+// functionality, they will need to override.
+class VfsFilesystem {
+public:
+ YUZU_NON_COPYABLE(VfsFilesystem);
+ YUZU_NON_MOVEABLE(VfsFilesystem);
+
+ explicit VfsFilesystem(VirtualDir root);
+ virtual ~VfsFilesystem();
+
+ // Gets the friendly name for the filesystem.
+ virtual std::string GetName() const;
+
+ // Return whether or not the user has read permissions on this filesystem.
+ virtual bool IsReadable() const;
+ // Return whether or not the user has write permission on this filesystem.
+ virtual bool IsWritable() const;
+
+ // Determine if the entry at path is non-existent, a file, or a directory.
+ virtual VfsEntryType GetEntryType(std::string_view path) const;
+
+ // Opens the file with path relative to root. If it doesn't exist, returns nullptr.
+ virtual VirtualFile OpenFile(std::string_view path, Mode perms);
+ // Creates a new, empty file at path
+ virtual VirtualFile CreateFile(std::string_view path, Mode perms);
+ // Copies the file from old_path to new_path, returning the new file on success and nullptr on
+ // failure.
+ virtual VirtualFile CopyFile(std::string_view old_path, std::string_view new_path);
+ // Moves the file from old_path to new_path, returning the moved file on success and nullptr on
+ // failure.
+ virtual VirtualFile MoveFile(std::string_view old_path, std::string_view new_path);
+ // Deletes the file with path relative to root, returning true on success.
+ virtual bool DeleteFile(std::string_view path);
+
+ // Opens the directory with path relative to root. If it doesn't exist, returns nullptr.
+ virtual VirtualDir OpenDirectory(std::string_view path, Mode perms);
+ // Creates a new, empty directory at path
+ virtual VirtualDir CreateDirectory(std::string_view path, Mode perms);
+ // Copies the directory from old_path to new_path, returning the new directory on success and
+ // nullptr on failure.
+ virtual VirtualDir CopyDirectory(std::string_view old_path, std::string_view new_path);
+ // Moves the directory from old_path to new_path, returning the moved directory on success and
+ // nullptr on failure.
+ virtual VirtualDir MoveDirectory(std::string_view old_path, std::string_view new_path);
+ // Deletes the directory with path relative to root, returning true on success.
+ virtual bool DeleteDirectory(std::string_view path);
+
+protected:
+ // Root directory in default implementation.
+ VirtualDir root;
+};
+
+// A class representing a file in an abstract filesystem.
+class VfsFile {
+public:
+ YUZU_NON_COPYABLE(VfsFile);
+ YUZU_NON_MOVEABLE(VfsFile);
+
+ VfsFile() = default;
+ virtual ~VfsFile();
+
+ // Retrieves the file name.
+ virtual std::string GetName() const = 0;
+ // Retrieves the extension of the file name.
+ virtual std::string GetExtension() const;
+ // Retrieves the size of the file.
+ virtual std::size_t GetSize() const = 0;
+ // Resizes the file to new_size. Returns whether or not the operation was successful.
+ virtual bool Resize(std::size_t new_size) = 0;
+ // Gets a pointer to the directory containing this file, returning nullptr if there is none.
+ virtual VirtualDir GetContainingDirectory() const = 0;
+
+ // Returns whether or not the file can be written to.
+ virtual bool IsWritable() const = 0;
+ // Returns whether or not the file can be read from.
+ virtual bool IsReadable() const = 0;
+
+ // The primary method of reading from the file. Reads length bytes into data starting at offset
+ // into file. Returns number of bytes successfully read.
+ virtual std::size_t Read(u8* data, std::size_t length, std::size_t offset = 0) const = 0;
+ // The primary method of writing to the file. Writes length bytes from data starting at offset
+ // into file. Returns number of bytes successfully written.
+ virtual std::size_t Write(const u8* data, std::size_t length, std::size_t offset = 0) = 0;
+
+ // Reads exactly one byte at the offset provided, returning std::nullopt on error.
+ virtual std::optional<u8> ReadByte(std::size_t offset = 0) const;
+ // Reads size bytes starting at offset in file into a vector.
+ virtual std::vector<u8> ReadBytes(std::size_t size, std::size_t offset = 0) const;
+ // Reads all the bytes from the file into a vector. Equivalent to 'file->Read(file->GetSize(),
+ // 0)'
+ virtual std::vector<u8> ReadAllBytes() const;
+
+ // Reads an array of type T, size number_elements starting at offset.
+ // Returns the number of bytes (sizeof(T)*number_elements) read successfully.
+ template <typename T>
+ std::size_t ReadArray(T* data, std::size_t number_elements, std::size_t offset = 0) const {
+ static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
+
+ return Read(reinterpret_cast<u8*>(data), number_elements * sizeof(T), offset);
+ }
+
+ // Reads size bytes into the memory starting at data starting at offset into the file.
+ // Returns the number of bytes read successfully.
+ template <typename T>
+ std::size_t ReadBytes(T* data, std::size_t size, std::size_t offset = 0) const {
+ static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
+ return Read(reinterpret_cast<u8*>(data), size, offset);
+ }
+
+ // Reads one object of type T starting at offset in file.
+ // Returns the number of bytes read successfully (sizeof(T)).
+ template <typename T>
+ std::size_t ReadObject(T* data, std::size_t offset = 0) const {
+ static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
+ return Read(reinterpret_cast<u8*>(data), sizeof(T), offset);
+ }
+
+ // Writes exactly one byte to offset in file and returns whether or not the byte was written
+ // successfully.
+ virtual bool WriteByte(u8 data, std::size_t offset = 0);
+ // Writes a vector of bytes to offset in file and returns the number of bytes successfully
+ // written.
+ virtual std::size_t WriteBytes(const std::vector<u8>& data, std::size_t offset = 0);
+
+ // Writes an array of type T, size number_elements to offset in file.
+ // Returns the number of bytes (sizeof(T)*number_elements) written successfully.
+ template <typename T>
+ std::size_t WriteArray(const T* data, std::size_t number_elements, std::size_t offset = 0) {
+ static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
+ return Write(reinterpret_cast<const u8*>(data), number_elements * sizeof(T), offset);
+ }
+
+ // Writes size bytes starting at memory location data to offset in file.
+ // Returns the number of bytes written successfully.
+ template <typename T>
+ std::size_t WriteBytes(const T* data, std::size_t size, std::size_t offset = 0) {
+ static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
+ return Write(reinterpret_cast<const u8*>(data), size, offset);
+ }
+
+ // Writes one object of type T to offset in file.
+ // Returns the number of bytes written successfully (sizeof(T)).
+ template <typename T>
+ std::size_t WriteObject(const T& data, std::size_t offset = 0) {
+ static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
+ return Write(reinterpret_cast<const u8*>(&data), sizeof(T), offset);
+ }
+
+ // Renames the file to name. Returns whether or not the operation was successful.
+ virtual bool Rename(std::string_view name) = 0;
+
+ // Returns the full path of this file as a string, recursively
+ virtual std::string GetFullPath() const;
+};
+
+// A class representing a directory in an abstract filesystem.
+class VfsDirectory {
+public:
+ YUZU_NON_COPYABLE(VfsDirectory);
+ YUZU_NON_MOVEABLE(VfsDirectory);
+
+ VfsDirectory() = default;
+ virtual ~VfsDirectory();
+
+ // Retrieves the file located at path as if the current directory was root. Returns nullptr if
+ // not found.
+ virtual VirtualFile GetFileRelative(std::string_view path) const;
+ // Calls GetFileRelative(path) on the root of the current directory.
+ virtual VirtualFile GetFileAbsolute(std::string_view path) const;
+
+ // Retrieves the directory located at path as if the current directory was root. Returns nullptr
+ // if not found.
+ virtual VirtualDir GetDirectoryRelative(std::string_view path) const;
+ // Calls GetDirectoryRelative(path) on the root of the current directory.
+ virtual VirtualDir GetDirectoryAbsolute(std::string_view path) const;
+
+ // Returns a vector containing all of the files in this directory.
+ virtual std::vector<VirtualFile> GetFiles() const = 0;
+ // Returns the file with filename matching name. Returns nullptr if directory doesn't have a
+ // file with name.
+ virtual VirtualFile GetFile(std::string_view name) const;
+
+ // Returns a struct containing the file's timestamp.
+ virtual FileTimeStampRaw GetFileTimeStamp(std::string_view path) const;
+
+ // Returns a vector containing all of the subdirectories in this directory.
+ virtual std::vector<VirtualDir> GetSubdirectories() const = 0;
+ // Returns the directory with name matching name. Returns nullptr if directory doesn't have a
+ // directory with name.
+ virtual VirtualDir GetSubdirectory(std::string_view name) const;
+
+ // Returns whether or not the directory can be written to.
+ virtual bool IsWritable() const = 0;
+ // Returns whether of not the directory can be read from.
+ virtual bool IsReadable() const = 0;
+
+ // Returns whether or not the directory is the root of the current file tree.
+ virtual bool IsRoot() const;
+
+ // Returns the name of the directory.
+ virtual std::string GetName() const = 0;
+ // Returns the total size of all files and subdirectories in this directory.
+ virtual std::size_t GetSize() const;
+ // Returns the parent directory of this directory. Returns nullptr if this directory is root or
+ // has no parent.
+ virtual VirtualDir GetParentDirectory() const = 0;
+
+ // Creates a new subdirectory with name name. Returns a pointer to the new directory or nullptr
+ // if the operation failed.
+ virtual VirtualDir CreateSubdirectory(std::string_view name) = 0;
+ // Creates a new file with name name. Returns a pointer to the new file or nullptr if the
+ // operation failed.
+ virtual VirtualFile CreateFile(std::string_view name) = 0;
+
+ // Creates a new file at the path relative to this directory. Also creates directories if
+ // they do not exist and is supported by this implementation. Returns nullptr on any failure.
+ virtual VirtualFile CreateFileRelative(std::string_view path);
+
+ // Creates a new file at the path relative to root of this directory. Also creates directories
+ // if they do not exist and is supported by this implementation. Returns nullptr on any failure.
+ virtual VirtualFile CreateFileAbsolute(std::string_view path);
+
+ // Creates a new directory at the path relative to this directory. Also creates directories if
+ // they do not exist and is supported by this implementation. Returns nullptr on any failure.
+ virtual VirtualDir CreateDirectoryRelative(std::string_view path);
+
+ // Creates a new directory at the path relative to root of this directory. Also creates
+ // directories if they do not exist and is supported by this implementation. Returns nullptr on
+ // any failure.
+ virtual VirtualDir CreateDirectoryAbsolute(std::string_view path);
+
+ // Deletes the subdirectory with the given name and returns true on success.
+ virtual bool DeleteSubdirectory(std::string_view name) = 0;
+
+ // Deletes all subdirectories and files within the provided directory and then deletes
+ // the directory itself. Returns true on success.
+ virtual bool DeleteSubdirectoryRecursive(std::string_view name);
+
+ // Deletes all subdirectories and files within the provided directory.
+ // Unlike DeleteSubdirectoryRecursive, this does not delete the provided directory.
+ virtual bool CleanSubdirectoryRecursive(std::string_view name);
+
+ // Returns whether or not the file with name name was deleted successfully.
+ virtual bool DeleteFile(std::string_view name) = 0;
+
+ // Returns whether or not this directory was renamed to name.
+ virtual bool Rename(std::string_view name) = 0;
+
+ // Returns whether or not the file with name src was successfully copied to a new file with name
+ // dest.
+ virtual bool Copy(std::string_view src, std::string_view dest);
+
+ // Gets all of the entries directly in the directory (files and dirs), returning a map between
+ // item name -> type.
+ virtual std::map<std::string, VfsEntryType, std::less<>> GetEntries() const;
+
+ // Returns the full path of this directory as a string, recursively
+ virtual std::string GetFullPath() const;
+};
+
+// A convenience partial-implementation of VfsDirectory that stubs out methods that should only work
+// if writable. This is to avoid redundant empty methods everywhere.
+class ReadOnlyVfsDirectory : public VfsDirectory {
+public:
+ bool IsWritable() const override;
+ bool IsReadable() const override;
+ VirtualDir CreateSubdirectory(std::string_view name) override;
+ VirtualFile CreateFile(std::string_view name) override;
+ VirtualFile CreateFileAbsolute(std::string_view path) override;
+ VirtualFile CreateFileRelative(std::string_view path) override;
+ VirtualDir CreateDirectoryAbsolute(std::string_view path) override;
+ VirtualDir CreateDirectoryRelative(std::string_view path) override;
+ bool DeleteSubdirectory(std::string_view name) override;
+ bool DeleteSubdirectoryRecursive(std::string_view name) override;
+ bool CleanSubdirectoryRecursive(std::string_view name) override;
+ bool DeleteFile(std::string_view name) override;
+ bool Rename(std::string_view name) override;
+};
+
+// Compare the two files, byte-for-byte, in increments specified by block_size
+bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2,
+ std::size_t block_size = 0x1000);
+
+// A method that copies the raw data between two different implementations of VirtualFile. If you
+// are using the same implementation, it is probably better to use the Copy method in the parent
+// directory of src/dest.
+bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t block_size = 0x1000);
+
+// A method that performs a similar function to VfsRawCopy above, but instead copies entire
+// directories. It suffers the same performance penalties as above and an implementation-specific
+// Copy should always be preferred.
+bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, std::size_t block_size = 0x1000);
+
+// Checks if the directory at path relative to rel exists. If it does, returns that. If it does not
+// it attempts to create it and returns the new dir or nullptr on failure.
+VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path);
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_cached.cpp b/src/core/file_sys/vfs/vfs_cached.cpp
new file mode 100644
index 000000000..01cd0f1e0
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_cached.cpp
@@ -0,0 +1,63 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/file_sys/vfs/vfs_cached.h"
+#include "core/file_sys/vfs/vfs_types.h"
+
+namespace FileSys {
+
+CachedVfsDirectory::CachedVfsDirectory(VirtualDir&& source_dir)
+ : name(source_dir->GetName()), parent(source_dir->GetParentDirectory()) {
+ for (auto& dir : source_dir->GetSubdirectories()) {
+ dirs.emplace(dir->GetName(), std::make_shared<CachedVfsDirectory>(std::move(dir)));
+ }
+ for (auto& file : source_dir->GetFiles()) {
+ files.emplace(file->GetName(), std::move(file));
+ }
+}
+
+CachedVfsDirectory::~CachedVfsDirectory() = default;
+
+VirtualFile CachedVfsDirectory::GetFile(std::string_view file_name) const {
+ auto it = files.find(file_name);
+ if (it != files.end()) {
+ return it->second;
+ }
+
+ return nullptr;
+}
+
+VirtualDir CachedVfsDirectory::GetSubdirectory(std::string_view dir_name) const {
+ auto it = dirs.find(dir_name);
+ if (it != dirs.end()) {
+ return it->second;
+ }
+
+ return nullptr;
+}
+
+std::vector<VirtualFile> CachedVfsDirectory::GetFiles() const {
+ std::vector<VirtualFile> out;
+ for (auto& [file_name, file] : files) {
+ out.push_back(file);
+ }
+ return out;
+}
+
+std::vector<VirtualDir> CachedVfsDirectory::GetSubdirectories() const {
+ std::vector<VirtualDir> out;
+ for (auto& [dir_name, dir] : dirs) {
+ out.push_back(dir);
+ }
+ return out;
+}
+
+std::string CachedVfsDirectory::GetName() const {
+ return name;
+}
+
+VirtualDir CachedVfsDirectory::GetParentDirectory() const {
+ return parent;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_cached.h b/src/core/file_sys/vfs/vfs_cached.h
new file mode 100644
index 000000000..47dff7224
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_cached.h
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <string_view>
+#include <vector>
+#include "core/file_sys/vfs/vfs.h"
+
+namespace FileSys {
+
+class CachedVfsDirectory : public ReadOnlyVfsDirectory {
+public:
+ CachedVfsDirectory(VirtualDir&& source_directory);
+
+ ~CachedVfsDirectory() override;
+ VirtualFile GetFile(std::string_view file_name) const override;
+ VirtualDir GetSubdirectory(std::string_view dir_name) const override;
+ std::vector<VirtualFile> GetFiles() const override;
+ std::vector<VirtualDir> GetSubdirectories() const override;
+ std::string GetName() const override;
+ VirtualDir GetParentDirectory() const override;
+
+private:
+ std::string name;
+ VirtualDir parent;
+ std::map<std::string, VirtualDir, std::less<>> dirs;
+ std::map<std::string, VirtualFile, std::less<>> files;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_concat.cpp b/src/core/file_sys/vfs/vfs_concat.cpp
new file mode 100644
index 000000000..b5cc9a9e9
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_concat.cpp
@@ -0,0 +1,192 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <utility>
+
+#include "common/assert.h"
+#include "core/file_sys/vfs/vfs_concat.h"
+#include "core/file_sys/vfs/vfs_static.h"
+
+namespace FileSys {
+
+ConcatenatedVfsFile::ConcatenatedVfsFile(std::string&& name_, ConcatenationMap&& concatenation_map_)
+ : concatenation_map(std::move(concatenation_map_)), name(std::move(name_)) {
+ DEBUG_ASSERT(this->VerifyContinuity());
+}
+
+bool ConcatenatedVfsFile::VerifyContinuity() const {
+ u64 last_offset = 0;
+ for (auto& entry : concatenation_map) {
+ if (entry.offset != last_offset) {
+ return false;
+ }
+
+ last_offset = entry.offset + entry.file->GetSize();
+ }
+
+ return true;
+}
+
+ConcatenatedVfsFile::~ConcatenatedVfsFile() = default;
+
+VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(std::string&& name,
+ std::vector<VirtualFile>&& files) {
+ // Fold trivial cases.
+ if (files.empty()) {
+ return nullptr;
+ }
+ if (files.size() == 1) {
+ return files.front();
+ }
+
+ // Make the concatenation map from the input.
+ std::vector<ConcatenationEntry> concatenation_map;
+ concatenation_map.reserve(files.size());
+ u64 last_offset = 0;
+
+ for (auto& file : files) {
+ const auto size = file->GetSize();
+
+ concatenation_map.emplace_back(ConcatenationEntry{
+ .offset = last_offset,
+ .file = std::move(file),
+ });
+
+ last_offset += size;
+ }
+
+ return VirtualFile(new ConcatenatedVfsFile(std::move(name), std::move(concatenation_map)));
+}
+
+VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(
+ u8 filler_byte, std::string&& name, std::vector<std::pair<u64, VirtualFile>>&& files) {
+ // Fold trivial cases.
+ if (files.empty()) {
+ return nullptr;
+ }
+ if (files.size() == 1) {
+ return files.begin()->second;
+ }
+
+ // Make the concatenation map from the input.
+ std::vector<ConcatenationEntry> concatenation_map;
+
+ concatenation_map.reserve(files.size());
+ u64 last_offset = 0;
+
+ // Iteration of a multimap is ordered, so offset will be strictly non-decreasing.
+ for (auto& [offset, file] : files) {
+ const auto size = file->GetSize();
+
+ if (offset > last_offset) {
+ concatenation_map.emplace_back(ConcatenationEntry{
+ .offset = last_offset,
+ .file = std::make_shared<StaticVfsFile>(filler_byte, offset - last_offset),
+ });
+ }
+
+ concatenation_map.emplace_back(ConcatenationEntry{
+ .offset = offset,
+ .file = std::move(file),
+ });
+
+ last_offset = offset + size;
+ }
+
+ return VirtualFile(new ConcatenatedVfsFile(std::move(name), std::move(concatenation_map)));
+}
+
+std::string ConcatenatedVfsFile::GetName() const {
+ if (concatenation_map.empty()) {
+ return "";
+ }
+ if (!name.empty()) {
+ return name;
+ }
+ return concatenation_map.front().file->GetName();
+}
+
+std::size_t ConcatenatedVfsFile::GetSize() const {
+ if (concatenation_map.empty()) {
+ return 0;
+ }
+ return concatenation_map.back().offset + concatenation_map.back().file->GetSize();
+}
+
+bool ConcatenatedVfsFile::Resize(std::size_t new_size) {
+ return false;
+}
+
+VirtualDir ConcatenatedVfsFile::GetContainingDirectory() const {
+ if (concatenation_map.empty()) {
+ return nullptr;
+ }
+ return concatenation_map.front().file->GetContainingDirectory();
+}
+
+bool ConcatenatedVfsFile::IsWritable() const {
+ return false;
+}
+
+bool ConcatenatedVfsFile::IsReadable() const {
+ return true;
+}
+
+std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
+ const ConcatenationEntry key{
+ .offset = offset,
+ .file = nullptr,
+ };
+
+ // Read nothing if the map is empty.
+ if (concatenation_map.empty()) {
+ return 0;
+ }
+
+ // Binary search to find the iterator to the first position we can check.
+ // It must exist, since we are not empty and are comparing unsigned integers.
+ auto it = std::prev(std::upper_bound(concatenation_map.begin(), concatenation_map.end(), key));
+ u64 cur_length = length;
+ u64 cur_offset = offset;
+
+ while (cur_length > 0 && it != concatenation_map.end()) {
+ // Check if we can read the file at this position.
+ const auto& file = it->file;
+ const u64 map_offset = it->offset;
+ const u64 file_size = file->GetSize();
+
+ if (cur_offset > map_offset + file_size) {
+ // Entirely out of bounds read.
+ break;
+ }
+
+ // Read the file at this position.
+ const u64 file_seek = cur_offset - map_offset;
+ const u64 intended_read_size = std::min<u64>(cur_length, file_size - file_seek);
+ const u64 actual_read_size =
+ file->Read(data + (cur_offset - offset), intended_read_size, file_seek);
+
+ // Update tracking.
+ cur_offset += actual_read_size;
+ cur_length -= actual_read_size;
+ it++;
+
+ // If we encountered a short read, we're done.
+ if (actual_read_size < intended_read_size) {
+ break;
+ }
+ }
+
+ return cur_offset - offset;
+}
+
+std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
+ return 0;
+}
+
+bool ConcatenatedVfsFile::Rename(std::string_view new_name) {
+ return false;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_concat.h b/src/core/file_sys/vfs/vfs_concat.h
new file mode 100644
index 000000000..6d12af762
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_concat.h
@@ -0,0 +1,57 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <compare>
+#include <map>
+#include <memory>
+#include "core/file_sys/vfs/vfs.h"
+
+namespace FileSys {
+
+// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently
+// read-only.
+class ConcatenatedVfsFile : public VfsFile {
+private:
+ struct ConcatenationEntry {
+ u64 offset;
+ VirtualFile file;
+
+ auto operator<=>(const ConcatenationEntry& other) const {
+ return this->offset <=> other.offset;
+ }
+ };
+ using ConcatenationMap = std::vector<ConcatenationEntry>;
+
+ explicit ConcatenatedVfsFile(std::string&& name,
+ std::vector<ConcatenationEntry>&& concatenation_map);
+ bool VerifyContinuity() const;
+
+public:
+ ~ConcatenatedVfsFile() override;
+
+ /// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
+ static VirtualFile MakeConcatenatedFile(std::string&& name, std::vector<VirtualFile>&& files);
+
+ /// Convenience function that turns a map of offsets to files into a concatenated file, filling
+ /// gaps with a given filler byte.
+ static VirtualFile MakeConcatenatedFile(u8 filler_byte, std::string&& name,
+ std::vector<std::pair<u64, VirtualFile>>&& files);
+
+ std::string GetName() const override;
+ std::size_t GetSize() const override;
+ bool Resize(std::size_t new_size) override;
+ VirtualDir GetContainingDirectory() const override;
+ bool IsWritable() const override;
+ bool IsReadable() const override;
+ std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
+ std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
+ bool Rename(std::string_view new_name) override;
+
+private:
+ ConcatenationMap concatenation_map;
+ std::string name;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_layered.cpp b/src/core/file_sys/vfs/vfs_layered.cpp
new file mode 100644
index 000000000..47b2a3c78
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_layered.cpp
@@ -0,0 +1,132 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <set>
+#include <unordered_set>
+#include <utility>
+#include "core/file_sys/vfs/vfs_layered.h"
+
+namespace FileSys {
+
+LayeredVfsDirectory::LayeredVfsDirectory(std::vector<VirtualDir> dirs_, std::string name_)
+ : dirs(std::move(dirs_)), name(std::move(name_)) {}
+
+LayeredVfsDirectory::~LayeredVfsDirectory() = default;
+
+VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(std::vector<VirtualDir> dirs,
+ std::string name) {
+ if (dirs.empty())
+ return nullptr;
+ if (dirs.size() == 1)
+ return dirs[0];
+
+ return VirtualDir(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
+}
+
+VirtualFile LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
+ for (const auto& layer : dirs) {
+ const auto file = layer->GetFileRelative(path);
+ if (file != nullptr)
+ return file;
+ }
+
+ return nullptr;
+}
+
+VirtualDir LayeredVfsDirectory::GetDirectoryRelative(std::string_view path) const {
+ std::vector<VirtualDir> out;
+ for (const auto& layer : dirs) {
+ auto dir = layer->GetDirectoryRelative(path);
+ if (dir != nullptr) {
+ out.emplace_back(std::move(dir));
+ }
+ }
+
+ return MakeLayeredDirectory(std::move(out));
+}
+
+VirtualFile LayeredVfsDirectory::GetFile(std::string_view file_name) const {
+ return GetFileRelative(file_name);
+}
+
+VirtualDir LayeredVfsDirectory::GetSubdirectory(std::string_view subdir_name) const {
+ return GetDirectoryRelative(subdir_name);
+}
+
+std::string LayeredVfsDirectory::GetFullPath() const {
+ return dirs[0]->GetFullPath();
+}
+
+std::vector<VirtualFile> LayeredVfsDirectory::GetFiles() const {
+ std::vector<VirtualFile> out;
+ std::unordered_set<std::string> out_names;
+
+ for (const auto& layer : dirs) {
+ for (auto& file : layer->GetFiles()) {
+ const auto [it, is_new] = out_names.emplace(file->GetName());
+ if (is_new) {
+ out.emplace_back(std::move(file));
+ }
+ }
+ }
+
+ return out;
+}
+
+std::vector<VirtualDir> LayeredVfsDirectory::GetSubdirectories() const {
+ std::vector<VirtualDir> out;
+ std::unordered_set<std::string> out_names;
+
+ for (const auto& layer : dirs) {
+ for (const auto& sd : layer->GetSubdirectories()) {
+ out_names.emplace(sd->GetName());
+ }
+ }
+
+ out.reserve(out_names.size());
+ for (const auto& subdir : out_names) {
+ out.emplace_back(GetSubdirectory(subdir));
+ }
+
+ return out;
+}
+
+bool LayeredVfsDirectory::IsWritable() const {
+ return false;
+}
+
+bool LayeredVfsDirectory::IsReadable() const {
+ return true;
+}
+
+std::string LayeredVfsDirectory::GetName() const {
+ return name.empty() ? dirs[0]->GetName() : name;
+}
+
+VirtualDir LayeredVfsDirectory::GetParentDirectory() const {
+ return dirs[0]->GetParentDirectory();
+}
+
+VirtualDir LayeredVfsDirectory::CreateSubdirectory(std::string_view subdir_name) {
+ return nullptr;
+}
+
+VirtualFile LayeredVfsDirectory::CreateFile(std::string_view file_name) {
+ return nullptr;
+}
+
+bool LayeredVfsDirectory::DeleteSubdirectory(std::string_view subdir_name) {
+ return false;
+}
+
+bool LayeredVfsDirectory::DeleteFile(std::string_view file_name) {
+ return false;
+}
+
+bool LayeredVfsDirectory::Rename(std::string_view new_name) {
+ name = new_name;
+ return true;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_layered.h b/src/core/file_sys/vfs/vfs_layered.h
new file mode 100644
index 000000000..0027ffa9a
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_layered.h
@@ -0,0 +1,46 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include "core/file_sys/vfs/vfs.h"
+
+namespace FileSys {
+
+// Class that stacks multiple VfsDirectories on top of each other, attempting to read from the first
+// one and falling back to the one after. The highest priority directory (overwrites all others)
+// should be element 0 in the dirs vector.
+class LayeredVfsDirectory : public VfsDirectory {
+ explicit LayeredVfsDirectory(std::vector<VirtualDir> dirs_, std::string name_);
+
+public:
+ ~LayeredVfsDirectory() override;
+
+ /// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases.
+ static VirtualDir MakeLayeredDirectory(std::vector<VirtualDir> dirs, std::string name = "");
+
+ VirtualFile GetFileRelative(std::string_view path) const override;
+ VirtualDir GetDirectoryRelative(std::string_view path) const override;
+ VirtualFile GetFile(std::string_view file_name) const override;
+ VirtualDir GetSubdirectory(std::string_view subdir_name) const override;
+ std::string GetFullPath() const override;
+
+ std::vector<VirtualFile> GetFiles() const override;
+ std::vector<VirtualDir> GetSubdirectories() const override;
+ bool IsWritable() const override;
+ bool IsReadable() const override;
+ std::string GetName() const override;
+ VirtualDir GetParentDirectory() const override;
+ VirtualDir CreateSubdirectory(std::string_view subdir_name) override;
+ VirtualFile CreateFile(std::string_view file_name) override;
+ bool DeleteSubdirectory(std::string_view subdir_name) override;
+ bool DeleteFile(std::string_view file_name) override;
+ bool Rename(std::string_view new_name) override;
+
+private:
+ std::vector<VirtualDir> dirs;
+ std::string name;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_offset.cpp b/src/core/file_sys/vfs/vfs_offset.cpp
new file mode 100644
index 000000000..1a37d2670
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_offset.cpp
@@ -0,0 +1,98 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <utility>
+
+#include "core/file_sys/vfs/vfs_offset.h"
+
+namespace FileSys {
+
+OffsetVfsFile::OffsetVfsFile(VirtualFile file_, std::size_t size_, std::size_t offset_,
+ std::string name_, VirtualDir parent_)
+ : file(file_), offset(offset_), size(size_), name(std::move(name_)),
+ parent(parent_ == nullptr ? file->GetContainingDirectory() : std::move(parent_)) {}
+
+OffsetVfsFile::~OffsetVfsFile() = default;
+
+std::string OffsetVfsFile::GetName() const {
+ return name.empty() ? file->GetName() : name;
+}
+
+std::size_t OffsetVfsFile::GetSize() const {
+ return size;
+}
+
+bool OffsetVfsFile::Resize(std::size_t new_size) {
+ if (offset + new_size < file->GetSize()) {
+ size = new_size;
+ } else {
+ auto res = file->Resize(offset + new_size);
+ if (!res)
+ return false;
+ size = new_size;
+ }
+
+ return true;
+}
+
+VirtualDir OffsetVfsFile::GetContainingDirectory() const {
+ return parent;
+}
+
+bool OffsetVfsFile::IsWritable() const {
+ return file->IsWritable();
+}
+
+bool OffsetVfsFile::IsReadable() const {
+ return file->IsReadable();
+}
+
+std::size_t OffsetVfsFile::Read(u8* data, std::size_t length, std::size_t r_offset) const {
+ return file->Read(data, TrimToFit(length, r_offset), offset + r_offset);
+}
+
+std::size_t OffsetVfsFile::Write(const u8* data, std::size_t length, std::size_t r_offset) {
+ return file->Write(data, TrimToFit(length, r_offset), offset + r_offset);
+}
+
+std::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const {
+ if (r_offset >= size) {
+ return std::nullopt;
+ }
+
+ return file->ReadByte(offset + r_offset);
+}
+
+std::vector<u8> OffsetVfsFile::ReadBytes(std::size_t r_size, std::size_t r_offset) const {
+ return file->ReadBytes(TrimToFit(r_size, r_offset), offset + r_offset);
+}
+
+std::vector<u8> OffsetVfsFile::ReadAllBytes() const {
+ return file->ReadBytes(size, offset);
+}
+
+bool OffsetVfsFile::WriteByte(u8 data, std::size_t r_offset) {
+ if (r_offset < size)
+ return file->WriteByte(data, offset + r_offset);
+
+ return false;
+}
+
+std::size_t OffsetVfsFile::WriteBytes(const std::vector<u8>& data, std::size_t r_offset) {
+ return file->Write(data.data(), TrimToFit(data.size(), r_offset), offset + r_offset);
+}
+
+bool OffsetVfsFile::Rename(std::string_view new_name) {
+ return file->Rename(new_name);
+}
+
+std::size_t OffsetVfsFile::GetOffset() const {
+ return offset;
+}
+
+std::size_t OffsetVfsFile::TrimToFit(std::size_t r_size, std::size_t r_offset) const {
+ return std::clamp(r_size, std::size_t{0}, size - r_offset);
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_offset.h b/src/core/file_sys/vfs/vfs_offset.h
new file mode 100644
index 000000000..4abe41d8e
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_offset.h
@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+
+#include "core/file_sys/vfs/vfs.h"
+
+namespace FileSys {
+
+// An implementation of VfsFile that wraps around another VfsFile at a certain offset.
+// Similar to seeking to an offset.
+// If the file is writable, operations that would write past the end of the offset file will expand
+// the size of this wrapper.
+class OffsetVfsFile : public VfsFile {
+public:
+ OffsetVfsFile(VirtualFile file, std::size_t size, std::size_t offset = 0,
+ std::string new_name = "", VirtualDir new_parent = nullptr);
+ ~OffsetVfsFile() override;
+
+ std::string GetName() const override;
+ std::size_t GetSize() const override;
+ bool Resize(std::size_t new_size) override;
+ VirtualDir GetContainingDirectory() const override;
+ bool IsWritable() const override;
+ bool IsReadable() const override;
+ std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
+ std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
+ std::optional<u8> ReadByte(std::size_t offset) const override;
+ std::vector<u8> ReadBytes(std::size_t size, std::size_t offset) const override;
+ std::vector<u8> ReadAllBytes() const override;
+ bool WriteByte(u8 data, std::size_t offset) override;
+ std::size_t WriteBytes(const std::vector<u8>& data, std::size_t offset) override;
+
+ bool Rename(std::string_view new_name) override;
+
+ std::size_t GetOffset() const;
+
+private:
+ std::size_t TrimToFit(std::size_t r_size, std::size_t r_offset) const;
+
+ VirtualFile file;
+ std::size_t offset;
+ std::size_t size;
+ std::string name;
+ VirtualDir parent;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_real.cpp b/src/core/file_sys/vfs/vfs_real.cpp
new file mode 100644
index 000000000..1e6d8163b
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_real.cpp
@@ -0,0 +1,528 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <cstddef>
+#include <iterator>
+#include <utility>
+#include "common/assert.h"
+#include "common/fs/file.h"
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "common/logging/log.h"
+#include "core/file_sys/vfs/vfs.h"
+#include "core/file_sys/vfs/vfs_real.h"
+
+// For FileTimeStampRaw
+#include <sys/stat.h>
+
+#ifdef _MSC_VER
+#define stat _stat64
+#endif
+
+namespace FileSys {
+
+namespace FS = Common::FS;
+
+namespace {
+
+constexpr size_t MaxOpenFiles = 512;
+
+constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) {
+ switch (mode) {
+ case Mode::Read:
+ return FS::FileAccessMode::Read;
+ case Mode::Write:
+ case Mode::ReadWrite:
+ case Mode::Append:
+ case Mode::ReadAppend:
+ case Mode::WriteAppend:
+ case Mode::All:
+ return FS::FileAccessMode::ReadWrite;
+ default:
+ return {};
+ }
+}
+
+} // Anonymous namespace
+
+RealVfsFilesystem::RealVfsFilesystem() : VfsFilesystem(nullptr) {}
+RealVfsFilesystem::~RealVfsFilesystem() = default;
+
+std::string RealVfsFilesystem::GetName() const {
+ return "Real";
+}
+
+bool RealVfsFilesystem::IsReadable() const {
+ return true;
+}
+
+bool RealVfsFilesystem::IsWritable() const {
+ return true;
+}
+
+VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const {
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+ if (!FS::Exists(path)) {
+ return VfsEntryType::None;
+ }
+ if (FS::IsDir(path)) {
+ return VfsEntryType::Directory;
+ }
+
+ return VfsEntryType::File;
+}
+
+VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::optional<u64> size,
+ Mode perms) {
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+ std::scoped_lock lk{list_lock};
+
+ if (auto it = cache.find(path); it != cache.end()) {
+ if (auto file = it->second.lock(); file) {
+ return file;
+ }
+ }
+
+ if (!size && !FS::IsFile(path)) {
+ return nullptr;
+ }
+
+ auto reference = std::make_unique<FileReference>();
+ this->InsertReferenceIntoListLocked(*reference);
+
+ auto file = std::shared_ptr<RealVfsFile>(
+ new RealVfsFile(*this, std::move(reference), path, perms, size));
+ cache[path] = file;
+
+ return file;
+}
+
+VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
+ return OpenFileFromEntry(path_, {}, perms);
+}
+
+VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+ {
+ std::scoped_lock lk{list_lock};
+ cache.erase(path);
+ }
+
+ // Current usages of CreateFile expect to delete the contents of an existing file.
+ if (FS::IsFile(path)) {
+ FS::IOFile temp{path, FS::FileAccessMode::Write, FS::FileType::BinaryFile};
+
+ if (!temp.IsOpen()) {
+ return nullptr;
+ }
+
+ temp.Close();
+
+ return OpenFile(path, perms);
+ }
+
+ if (!FS::NewFile(path)) {
+ return nullptr;
+ }
+
+ return OpenFile(path, perms);
+}
+
+VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) {
+ // Unused
+ return nullptr;
+}
+
+VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) {
+ const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
+ const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
+ {
+ std::scoped_lock lk{list_lock};
+ cache.erase(old_path);
+ cache.erase(new_path);
+ }
+ if (!FS::RenameFile(old_path, new_path)) {
+ return nullptr;
+ }
+ return OpenFile(new_path, Mode::ReadWrite);
+}
+
+bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+ {
+ std::scoped_lock lk{list_lock};
+ cache.erase(path);
+ }
+ return FS::RemoveFile(path);
+}
+
+VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) {
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+ return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
+}
+
+VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+ if (!FS::CreateDirs(path)) {
+ return nullptr;
+ }
+ return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
+}
+
+VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_,
+ std::string_view new_path_) {
+ // Unused
+ return nullptr;
+}
+
+VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
+ std::string_view new_path_) {
+ const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
+ const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
+
+ if (!FS::RenameDir(old_path, new_path)) {
+ return nullptr;
+ }
+ return OpenDirectory(new_path, Mode::ReadWrite);
+}
+
+bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) {
+ const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
+ return FS::RemoveDirRecursively(path);
+}
+
+std::unique_lock<std::mutex> RealVfsFilesystem::RefreshReference(const std::string& path,
+ Mode perms,
+ FileReference& reference) {
+ std::unique_lock lk{list_lock};
+
+ // Temporarily remove from list.
+ this->RemoveReferenceFromListLocked(reference);
+
+ // Restore file if needed.
+ if (!reference.file) {
+ this->EvictSingleReferenceLocked();
+
+ reference.file =
+ FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile);
+ if (reference.file) {
+ num_open_files++;
+ }
+ }
+
+ // Reinsert into list.
+ this->InsertReferenceIntoListLocked(reference);
+
+ return lk;
+}
+
+void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference) {
+ std::scoped_lock lk{list_lock};
+
+ // Remove from list.
+ this->RemoveReferenceFromListLocked(*reference);
+
+ // Close the file.
+ if (reference->file) {
+ reference->file.reset();
+ num_open_files--;
+ }
+}
+
+void RealVfsFilesystem::EvictSingleReferenceLocked() {
+ if (num_open_files < MaxOpenFiles || open_references.empty()) {
+ return;
+ }
+
+ // Get and remove from list.
+ auto& reference = open_references.back();
+ this->RemoveReferenceFromListLocked(reference);
+
+ // Close the file.
+ if (reference.file) {
+ reference.file.reset();
+ num_open_files--;
+ }
+
+ // Reinsert into closed list.
+ this->InsertReferenceIntoListLocked(reference);
+}
+
+void RealVfsFilesystem::InsertReferenceIntoListLocked(FileReference& reference) {
+ if (reference.file) {
+ open_references.push_front(reference);
+ } else {
+ closed_references.push_front(reference);
+ }
+}
+
+void RealVfsFilesystem::RemoveReferenceFromListLocked(FileReference& reference) {
+ if (reference.file) {
+ open_references.erase(open_references.iterator_to(reference));
+ } else {
+ closed_references.erase(closed_references.iterator_to(reference));
+ }
+}
+
+RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_,
+ const std::string& path_, Mode perms_, std::optional<u64> size_)
+ : base(base_), reference(std::move(reference_)), path(path_),
+ parent_path(FS::GetParentPath(path_)), path_components(FS::SplitPathComponentsCopy(path_)),
+ size(size_), perms(perms_) {}
+
+RealVfsFile::~RealVfsFile() {
+ base.DropReference(std::move(reference));
+}
+
+std::string RealVfsFile::GetName() const {
+ return path_components.empty() ? "" : std::string(path_components.back());
+}
+
+std::size_t RealVfsFile::GetSize() const {
+ if (size) {
+ return *size;
+ }
+ auto lk = base.RefreshReference(path, perms, *reference);
+ return reference->file ? reference->file->GetSize() : 0;
+}
+
+bool RealVfsFile::Resize(std::size_t new_size) {
+ size.reset();
+ auto lk = base.RefreshReference(path, perms, *reference);
+ return reference->file ? reference->file->SetSize(new_size) : false;
+}
+
+VirtualDir RealVfsFile::GetContainingDirectory() const {
+ return base.OpenDirectory(parent_path, perms);
+}
+
+bool RealVfsFile::IsWritable() const {
+ return True(perms & Mode::Write);
+}
+
+bool RealVfsFile::IsReadable() const {
+ return True(perms & Mode::Read);
+}
+
+std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
+ auto lk = base.RefreshReference(path, perms, *reference);
+ if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) {
+ return 0;
+ }
+ return reference->file->ReadSpan(std::span{data, length});
+}
+
+std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
+ size.reset();
+ auto lk = base.RefreshReference(path, perms, *reference);
+ if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) {
+ return 0;
+ }
+ return reference->file->WriteSpan(std::span{data, length});
+}
+
+bool RealVfsFile::Rename(std::string_view name) {
+ return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr;
+}
+
+// TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if
+// constexpr' because there is a compile error in the branch not used.
+
+template <>
+std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>() const {
+ if (perms == Mode::Append) {
+ return {};
+ }
+
+ std::vector<VirtualFile> out;
+
+ const FS::DirEntryCallable callback = [this,
+ &out](const std::filesystem::directory_entry& entry) {
+ const auto full_path_string = FS::PathToUTF8String(entry.path());
+
+ out.emplace_back(base.OpenFileFromEntry(full_path_string, entry.file_size(), perms));
+
+ return true;
+ };
+
+ FS::IterateDirEntries(path, callback, FS::DirEntryFilter::File);
+
+ return out;
+}
+
+template <>
+std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDirectory>() const {
+ if (perms == Mode::Append) {
+ return {};
+ }
+
+ std::vector<VirtualDir> out;
+
+ const FS::DirEntryCallable callback = [this,
+ &out](const std::filesystem::directory_entry& entry) {
+ const auto full_path_string = FS::PathToUTF8String(entry.path());
+
+ out.emplace_back(base.OpenDirectory(full_path_string, perms));
+
+ return true;
+ };
+
+ FS::IterateDirEntries(path, callback, FS::DirEntryFilter::Directory);
+
+ return out;
+}
+
+RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_)
+ : base(base_), path(FS::RemoveTrailingSlash(path_)), parent_path(FS::GetParentPath(path)),
+ path_components(FS::SplitPathComponentsCopy(path)), perms(perms_) {
+ if (!FS::Exists(path) && True(perms & Mode::Write)) {
+ void(FS::CreateDirs(path));
+ }
+}
+
+RealVfsDirectory::~RealVfsDirectory() = default;
+
+VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) const {
+ const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
+ if (!FS::Exists(full_path) || FS::IsDir(full_path)) {
+ return nullptr;
+ }
+ return base.OpenFile(full_path, perms);
+}
+
+VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view relative_path) const {
+ const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
+ if (!FS::Exists(full_path) || !FS::IsDir(full_path)) {
+ return nullptr;
+ }
+ return base.OpenDirectory(full_path, perms);
+}
+
+VirtualFile RealVfsDirectory::GetFile(std::string_view name) const {
+ return GetFileRelative(name);
+}
+
+VirtualDir RealVfsDirectory::GetSubdirectory(std::string_view name) const {
+ return GetDirectoryRelative(name);
+}
+
+VirtualFile RealVfsDirectory::CreateFileRelative(std::string_view relative_path) {
+ const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
+ if (!FS::CreateParentDirs(full_path)) {
+ return nullptr;
+ }
+ return base.CreateFile(full_path, perms);
+}
+
+VirtualDir RealVfsDirectory::CreateDirectoryRelative(std::string_view relative_path) {
+ const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
+ return base.CreateDirectory(full_path, perms);
+}
+
+bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
+ const auto full_path = FS::SanitizePath(this->path + '/' + std::string(name));
+ return base.DeleteDirectory(full_path);
+}
+
+std::vector<VirtualFile> RealVfsDirectory::GetFiles() const {
+ return IterateEntries<RealVfsFile, VfsFile>();
+}
+
+FileTimeStampRaw RealVfsDirectory::GetFileTimeStamp(std::string_view path_) const {
+ const auto full_path = FS::SanitizePath(path + '/' + std::string(path_));
+ const auto fs_path = std::filesystem::path{FS::ToU8String(full_path)};
+ struct stat file_status;
+
+#ifdef _WIN32
+ const auto stat_result = _wstat64(fs_path.c_str(), &file_status);
+#else
+ const auto stat_result = stat(fs_path.c_str(), &file_status);
+#endif
+
+ if (stat_result != 0) {
+ return {};
+ }
+
+ return {
+ .created{static_cast<u64>(file_status.st_ctime)},
+ .accessed{static_cast<u64>(file_status.st_atime)},
+ .modified{static_cast<u64>(file_status.st_mtime)},
+ };
+}
+
+std::vector<VirtualDir> RealVfsDirectory::GetSubdirectories() const {
+ return IterateEntries<RealVfsDirectory, VfsDirectory>();
+}
+
+bool RealVfsDirectory::IsWritable() const {
+ return True(perms & Mode::Write);
+}
+
+bool RealVfsDirectory::IsReadable() const {
+ return True(perms & Mode::Read);
+}
+
+std::string RealVfsDirectory::GetName() const {
+ return path_components.empty() ? "" : std::string(path_components.back());
+}
+
+VirtualDir RealVfsDirectory::GetParentDirectory() const {
+ if (path_components.size() <= 1) {
+ return nullptr;
+ }
+
+ return base.OpenDirectory(parent_path, perms);
+}
+
+VirtualDir RealVfsDirectory::CreateSubdirectory(std::string_view name) {
+ const std::string subdir_path = (path + '/').append(name);
+ return base.CreateDirectory(subdir_path, perms);
+}
+
+VirtualFile RealVfsDirectory::CreateFile(std::string_view name) {
+ const std::string file_path = (path + '/').append(name);
+ return base.CreateFile(file_path, perms);
+}
+
+bool RealVfsDirectory::DeleteSubdirectory(std::string_view name) {
+ const std::string subdir_path = (path + '/').append(name);
+ return base.DeleteDirectory(subdir_path);
+}
+
+bool RealVfsDirectory::DeleteFile(std::string_view name) {
+ const std::string file_path = (path + '/').append(name);
+ return base.DeleteFile(file_path);
+}
+
+bool RealVfsDirectory::Rename(std::string_view name) {
+ const std::string new_name = (parent_path + '/').append(name);
+ return base.MoveFile(path, new_name) != nullptr;
+}
+
+std::string RealVfsDirectory::GetFullPath() const {
+ auto out = path;
+ std::replace(out.begin(), out.end(), '\\', '/');
+ return out;
+}
+
+std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries() const {
+ if (perms == Mode::Append) {
+ return {};
+ }
+
+ std::map<std::string, VfsEntryType, std::less<>> out;
+
+ const FS::DirEntryCallable callback = [&out](const std::filesystem::directory_entry& entry) {
+ const auto filename = FS::PathToUTF8String(entry.path().filename());
+ out.insert_or_assign(filename,
+ entry.is_directory() ? VfsEntryType::Directory : VfsEntryType::File);
+ return true;
+ };
+
+ FS::IterateDirEntries(path, callback);
+
+ return out;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_real.h b/src/core/file_sys/vfs/vfs_real.h
new file mode 100644
index 000000000..1560bc1f9
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_real.h
@@ -0,0 +1,145 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <map>
+#include <mutex>
+#include <optional>
+#include <string_view>
+#include "common/intrusive_list.h"
+#include "core/file_sys/mode.h"
+#include "core/file_sys/vfs/vfs.h"
+
+namespace Common::FS {
+class IOFile;
+}
+
+namespace FileSys {
+
+struct FileReference : public Common::IntrusiveListBaseNode<FileReference> {
+ std::shared_ptr<Common::FS::IOFile> file{};
+};
+
+class RealVfsFile;
+class RealVfsDirectory;
+
+class RealVfsFilesystem : public VfsFilesystem {
+public:
+ RealVfsFilesystem();
+ ~RealVfsFilesystem() override;
+
+ std::string GetName() const override;
+ bool IsReadable() const override;
+ bool IsWritable() const override;
+ VfsEntryType GetEntryType(std::string_view path) const override;
+ VirtualFile OpenFile(std::string_view path, Mode perms = Mode::Read) override;
+ VirtualFile CreateFile(std::string_view path, Mode perms = Mode::ReadWrite) override;
+ VirtualFile CopyFile(std::string_view old_path, std::string_view new_path) override;
+ VirtualFile MoveFile(std::string_view old_path, std::string_view new_path) override;
+ bool DeleteFile(std::string_view path) override;
+ VirtualDir OpenDirectory(std::string_view path, Mode perms = Mode::Read) override;
+ VirtualDir CreateDirectory(std::string_view path, Mode perms = Mode::ReadWrite) override;
+ VirtualDir CopyDirectory(std::string_view old_path, std::string_view new_path) override;
+ VirtualDir MoveDirectory(std::string_view old_path, std::string_view new_path) override;
+ bool DeleteDirectory(std::string_view path) override;
+
+private:
+ using ReferenceListType = Common::IntrusiveListBaseTraits<FileReference>::ListType;
+ std::map<std::string, std::weak_ptr<VfsFile>, std::less<>> cache;
+ ReferenceListType open_references;
+ ReferenceListType closed_references;
+ std::mutex list_lock;
+ size_t num_open_files{};
+
+private:
+ friend class RealVfsFile;
+ std::unique_lock<std::mutex> RefreshReference(const std::string& path, Mode perms,
+ FileReference& reference);
+ void DropReference(std::unique_ptr<FileReference>&& reference);
+
+private:
+ friend class RealVfsDirectory;
+ VirtualFile OpenFileFromEntry(std::string_view path, std::optional<u64> size,
+ Mode perms = Mode::Read);
+
+private:
+ void EvictSingleReferenceLocked();
+ void InsertReferenceIntoListLocked(FileReference& reference);
+ void RemoveReferenceFromListLocked(FileReference& reference);
+};
+
+// An implementation of VfsFile that represents a file on the user's computer.
+class RealVfsFile : public VfsFile {
+ friend class RealVfsDirectory;
+ friend class RealVfsFilesystem;
+
+public:
+ ~RealVfsFile() override;
+
+ std::string GetName() const override;
+ std::size_t GetSize() const override;
+ bool Resize(std::size_t new_size) override;
+ VirtualDir GetContainingDirectory() const override;
+ bool IsWritable() const override;
+ bool IsReadable() const override;
+ std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
+ std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
+ bool Rename(std::string_view name) override;
+
+private:
+ RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference,
+ const std::string& path, Mode perms = Mode::Read, std::optional<u64> size = {});
+
+ RealVfsFilesystem& base;
+ std::unique_ptr<FileReference> reference;
+ std::string path;
+ std::string parent_path;
+ std::vector<std::string> path_components;
+ std::optional<u64> size;
+ Mode perms;
+};
+
+// An implementation of VfsDirectory that represents a directory on the user's computer.
+class RealVfsDirectory : public VfsDirectory {
+ friend class RealVfsFilesystem;
+
+public:
+ ~RealVfsDirectory() override;
+
+ VirtualFile GetFileRelative(std::string_view relative_path) const override;
+ VirtualDir GetDirectoryRelative(std::string_view relative_path) const override;
+ VirtualFile GetFile(std::string_view name) const override;
+ VirtualDir GetSubdirectory(std::string_view name) const override;
+ VirtualFile CreateFileRelative(std::string_view relative_path) override;
+ VirtualDir CreateDirectoryRelative(std::string_view relative_path) override;
+ bool DeleteSubdirectoryRecursive(std::string_view name) override;
+ std::vector<VirtualFile> GetFiles() const override;
+ FileTimeStampRaw GetFileTimeStamp(std::string_view path) const override;
+ std::vector<VirtualDir> GetSubdirectories() const override;
+ bool IsWritable() const override;
+ bool IsReadable() const override;
+ std::string GetName() const override;
+ VirtualDir GetParentDirectory() const override;
+ VirtualDir CreateSubdirectory(std::string_view name) override;
+ VirtualFile CreateFile(std::string_view name) override;
+ bool DeleteSubdirectory(std::string_view name) override;
+ bool DeleteFile(std::string_view name) override;
+ bool Rename(std::string_view name) override;
+ std::string GetFullPath() const override;
+ std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override;
+
+private:
+ RealVfsDirectory(RealVfsFilesystem& base, const std::string& path, Mode perms = Mode::Read);
+
+ template <typename T, typename R>
+ std::vector<std::shared_ptr<R>> IterateEntries() const;
+
+ RealVfsFilesystem& base;
+ std::string path;
+ std::string parent_path;
+ std::vector<std::string> path_components;
+ Mode perms;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_static.h b/src/core/file_sys/vfs/vfs_static.h
new file mode 100644
index 000000000..bb53560ac
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_static.h
@@ -0,0 +1,80 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <algorithm>
+#include <memory>
+#include <string_view>
+
+#include "core/file_sys/vfs/vfs.h"
+
+namespace FileSys {
+
+class StaticVfsFile : public VfsFile {
+public:
+ explicit StaticVfsFile(u8 value_, std::size_t size_ = 0, std::string name_ = "",
+ VirtualDir parent_ = nullptr)
+ : value{value_}, size{size_}, name{std::move(name_)}, parent{std::move(parent_)} {}
+
+ std::string GetName() const override {
+ return name;
+ }
+
+ std::size_t GetSize() const override {
+ return size;
+ }
+
+ bool Resize(std::size_t new_size) override {
+ size = new_size;
+ return true;
+ }
+
+ VirtualDir GetContainingDirectory() const override {
+ return parent;
+ }
+
+ bool IsWritable() const override {
+ return false;
+ }
+
+ bool IsReadable() const override {
+ return true;
+ }
+
+ std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override {
+ const auto read = std::min(length, size - offset);
+ std::fill(data, data + read, value);
+ return read;
+ }
+
+ std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override {
+ return 0;
+ }
+
+ std::optional<u8> ReadByte(std::size_t offset) const override {
+ if (offset >= size) {
+ return std::nullopt;
+ }
+
+ return value;
+ }
+
+ std::vector<u8> ReadBytes(std::size_t length, std::size_t offset) const override {
+ const auto read = std::min(length, size - offset);
+ return std::vector<u8>(read, value);
+ }
+
+ bool Rename(std::string_view new_name) override {
+ name = new_name;
+ return true;
+ }
+
+private:
+ u8 value;
+ std::size_t size;
+ std::string name;
+ VirtualDir parent;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_types.h b/src/core/file_sys/vfs/vfs_types.h
new file mode 100644
index 000000000..4a583ed64
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_types.h
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+
+#include "common/common_types.h"
+
+namespace FileSys {
+
+class VfsDirectory;
+class VfsFile;
+class VfsFilesystem;
+
+// Declarations for Vfs* pointer types
+
+using VirtualDir = std::shared_ptr<VfsDirectory>;
+using VirtualFile = std::shared_ptr<VfsFile>;
+using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
+
+struct FileTimeStampRaw {
+ u64 created{};
+ u64 accessed{};
+ u64 modified{};
+ u64 padding{};
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_vector.cpp b/src/core/file_sys/vfs/vfs_vector.cpp
new file mode 100644
index 000000000..0d54461c8
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_vector.cpp
@@ -0,0 +1,133 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <utility>
+#include "core/file_sys/vfs/vfs_vector.h"
+
+namespace FileSys {
+VectorVfsFile::VectorVfsFile(std::vector<u8> initial_data, std::string name_, VirtualDir parent_)
+ : data(std::move(initial_data)), parent(std::move(parent_)), name(std::move(name_)) {}
+
+VectorVfsFile::~VectorVfsFile() = default;
+
+std::string VectorVfsFile::GetName() const {
+ return name;
+}
+
+size_t VectorVfsFile::GetSize() const {
+ return data.size();
+}
+
+bool VectorVfsFile::Resize(size_t new_size) {
+ data.resize(new_size);
+ return true;
+}
+
+VirtualDir VectorVfsFile::GetContainingDirectory() const {
+ return parent;
+}
+
+bool VectorVfsFile::IsWritable() const {
+ return true;
+}
+
+bool VectorVfsFile::IsReadable() const {
+ return true;
+}
+
+std::size_t VectorVfsFile::Read(u8* data_, std::size_t length, std::size_t offset) const {
+ const auto read = std::min(length, data.size() - offset);
+ std::memcpy(data_, data.data() + offset, read);
+ return read;
+}
+
+std::size_t VectorVfsFile::Write(const u8* data_, std::size_t length, std::size_t offset) {
+ if (offset + length > data.size())
+ data.resize(offset + length);
+ const auto write = std::min(length, data.size() - offset);
+ std::memcpy(data.data() + offset, data_, write);
+ return write;
+}
+
+bool VectorVfsFile::Rename(std::string_view name_) {
+ name = name_;
+ return true;
+}
+
+void VectorVfsFile::Assign(std::vector<u8> new_data) {
+ data = std::move(new_data);
+}
+
+VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
+ std::vector<VirtualDir> dirs_, std::string name_,
+ VirtualDir parent_)
+ : files(std::move(files_)), dirs(std::move(dirs_)), parent(std::move(parent_)),
+ name(std::move(name_)) {}
+
+VectorVfsDirectory::~VectorVfsDirectory() = default;
+
+std::vector<VirtualFile> VectorVfsDirectory::GetFiles() const {
+ return files;
+}
+
+std::vector<VirtualDir> VectorVfsDirectory::GetSubdirectories() const {
+ return dirs;
+}
+
+bool VectorVfsDirectory::IsWritable() const {
+ return false;
+}
+
+bool VectorVfsDirectory::IsReadable() const {
+ return true;
+}
+
+std::string VectorVfsDirectory::GetName() const {
+ return name;
+}
+
+VirtualDir VectorVfsDirectory::GetParentDirectory() const {
+ return parent;
+}
+
+template <typename T>
+static bool FindAndRemoveVectorElement(std::vector<T>& vec, std::string_view name) {
+ const auto iter =
+ std::find_if(vec.begin(), vec.end(), [name](const T& e) { return e->GetName() == name; });
+ if (iter == vec.end())
+ return false;
+
+ vec.erase(iter);
+ return true;
+}
+
+bool VectorVfsDirectory::DeleteSubdirectory(std::string_view subdir_name) {
+ return FindAndRemoveVectorElement(dirs, subdir_name);
+}
+
+bool VectorVfsDirectory::DeleteFile(std::string_view file_name) {
+ return FindAndRemoveVectorElement(files, file_name);
+}
+
+bool VectorVfsDirectory::Rename(std::string_view name_) {
+ name = name_;
+ return true;
+}
+
+VirtualDir VectorVfsDirectory::CreateSubdirectory(std::string_view subdir_name) {
+ return nullptr;
+}
+
+VirtualFile VectorVfsDirectory::CreateFile(std::string_view file_name) {
+ return nullptr;
+}
+
+void VectorVfsDirectory::AddFile(VirtualFile file) {
+ files.push_back(std::move(file));
+}
+
+void VectorVfsDirectory::AddDirectory(VirtualDir dir) {
+ dirs.push_back(std::move(dir));
+}
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs/vfs_vector.h b/src/core/file_sys/vfs/vfs_vector.h
new file mode 100644
index 000000000..587187dd2
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_vector.h
@@ -0,0 +1,131 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <vector>
+#include "core/file_sys/vfs/vfs.h"
+
+namespace FileSys {
+
+// An implementation of VfsFile that is backed by a statically-sized array
+template <std::size_t size>
+class ArrayVfsFile : public VfsFile {
+public:
+ explicit ArrayVfsFile(const std::array<u8, size>& data_, std::string name_ = "",
+ VirtualDir parent_ = nullptr)
+ : data(data_), name(std::move(name_)), parent(std::move(parent_)) {}
+
+ std::string GetName() const override {
+ return name;
+ }
+
+ std::size_t GetSize() const override {
+ return size;
+ }
+
+ bool Resize(std::size_t new_size) override {
+ return false;
+ }
+
+ VirtualDir GetContainingDirectory() const override {
+ return parent;
+ }
+
+ bool IsWritable() const override {
+ return false;
+ }
+
+ bool IsReadable() const override {
+ return true;
+ }
+
+ std::size_t Read(u8* data_, std::size_t length, std::size_t offset) const override {
+ const auto read = std::min(length, size - offset);
+ std::memcpy(data_, data.data() + offset, read);
+ return read;
+ }
+
+ std::size_t Write(const u8* data_, std::size_t length, std::size_t offset) override {
+ return 0;
+ }
+
+ bool Rename(std::string_view new_name) override {
+ name = new_name;
+ return true;
+ }
+
+private:
+ std::array<u8, size> data;
+ std::string name;
+ VirtualDir parent;
+};
+
+template <std::size_t Size, typename... Args>
+std::shared_ptr<ArrayVfsFile<Size>> MakeArrayFile(const std::array<u8, Size>& data,
+ Args&&... args) {
+ return std::make_shared<ArrayVfsFile<Size>>(data, std::forward<Args>(args)...);
+}
+
+// An implementation of VfsFile that is backed by a vector optionally supplied upon construction
+class VectorVfsFile : public VfsFile {
+public:
+ explicit VectorVfsFile(std::vector<u8> initial_data = {}, std::string name_ = "",
+ VirtualDir parent_ = nullptr);
+ ~VectorVfsFile() override;
+
+ std::string GetName() const override;
+ std::size_t GetSize() const override;
+ bool Resize(std::size_t new_size) override;
+ VirtualDir GetContainingDirectory() const override;
+ bool IsWritable() const override;
+ bool IsReadable() const override;
+ std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
+ std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
+ bool Rename(std::string_view name) override;
+
+ virtual void Assign(std::vector<u8> new_data);
+
+private:
+ std::vector<u8> data;
+ VirtualDir parent;
+ std::string name;
+};
+
+// An implementation of VfsDirectory that maintains two vectors for subdirectories and files.
+// Vector data is supplied upon construction.
+class VectorVfsDirectory : public VfsDirectory {
+public:
+ explicit VectorVfsDirectory(std::vector<VirtualFile> files = {},
+ std::vector<VirtualDir> dirs = {}, std::string name = "",
+ VirtualDir parent = nullptr);
+ ~VectorVfsDirectory() override;
+
+ std::vector<VirtualFile> GetFiles() const override;
+ std::vector<VirtualDir> GetSubdirectories() const override;
+ bool IsWritable() const override;
+ bool IsReadable() const override;
+ std::string GetName() const override;
+ VirtualDir GetParentDirectory() const override;
+ bool DeleteSubdirectory(std::string_view subdir_name) override;
+ bool DeleteFile(std::string_view file_name) override;
+ bool Rename(std::string_view name) override;
+ VirtualDir CreateSubdirectory(std::string_view subdir_name) override;
+ VirtualFile CreateFile(std::string_view file_name) override;
+
+ virtual void AddFile(VirtualFile file);
+ virtual void AddDirectory(VirtualDir dir);
+
+private:
+ std::vector<VirtualFile> files;
+ std::vector<VirtualDir> dirs;
+
+ VirtualDir parent;
+ std::string name;
+};
+
+} // namespace FileSys