summaryrefslogtreecommitdiff
path: root/src/core/file_sys/vfs
diff options
context:
space:
mode:
authorGravatar liamwhite2024-01-26 09:55:25 -0500
committerGravatar GitHub2024-01-26 09:55:25 -0500
commit55482ab5dce463d5014498b006c18a90d0d004e6 (patch)
treeb343faa9cadc692265efb4b6b88e157c97ef76d2 /src/core/file_sys/vfs
parentMerge pull request #12796 from t895/controller-optimizations (diff)
parentAddress review comments and fix compilation problems (diff)
downloadyuzu-55482ab5dce463d5014498b006c18a90d0d004e6.tar.gz
yuzu-55482ab5dce463d5014498b006c18a90d0d004e6.tar.xz
yuzu-55482ab5dce463d5014498b006c18a90d0d004e6.zip
Merge pull request #12707 from FearlessTobi/fs-housekeeping
fs: Various cleanups & add path class for later use
Diffstat (limited to 'src/core/file_sys/vfs')
-rw-r--r--src/core/file_sys/vfs/vfs.cpp551
-rw-r--r--src/core/file_sys/vfs/vfs.h326
-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.cpp527
-rw-r--r--src/core/file_sys/vfs/vfs_real.h148
-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..a04292760
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs.cpp
@@ -0,0 +1,551 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <numeric>
6#include <string>
7#include "common/fs/path_util.h"
8#include "core/file_sys/vfs/vfs.h"
9
10namespace FileSys {
11
12VfsFilesystem::VfsFilesystem(VirtualDir root_) : root(std::move(root_)) {}
13
14VfsFilesystem::~VfsFilesystem() = default;
15
16std::string VfsFilesystem::GetName() const {
17 return root->GetName();
18}
19
20bool VfsFilesystem::IsReadable() const {
21 return root->IsReadable();
22}
23
24bool VfsFilesystem::IsWritable() const {
25 return root->IsWritable();
26}
27
28VfsEntryType VfsFilesystem::GetEntryType(std::string_view path_) const {
29 const auto path = Common::FS::SanitizePath(path_);
30 if (root->GetFileRelative(path) != nullptr)
31 return VfsEntryType::File;
32 if (root->GetDirectoryRelative(path) != nullptr)
33 return VfsEntryType::Directory;
34
35 return VfsEntryType::None;
36}
37
38VirtualFile VfsFilesystem::OpenFile(std::string_view path_, OpenMode perms) {
39 const auto path = Common::FS::SanitizePath(path_);
40 return root->GetFileRelative(path);
41}
42
43VirtualFile VfsFilesystem::CreateFile(std::string_view path_, OpenMode perms) {
44 const auto path = Common::FS::SanitizePath(path_);
45 return root->CreateFileRelative(path);
46}
47
48VirtualFile VfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) {
49 const auto old_path = Common::FS::SanitizePath(old_path_);
50 const auto new_path = Common::FS::SanitizePath(new_path_);
51
52 // VfsDirectory impls are only required to implement copy across the current directory.
53 if (Common::FS::GetParentPath(old_path) == Common::FS::GetParentPath(new_path)) {
54 if (!root->Copy(Common::FS::GetFilename(old_path), Common::FS::GetFilename(new_path)))
55 return nullptr;
56 return OpenFile(new_path, OpenMode::ReadWrite);
57 }
58
59 // Do it using RawCopy. Non-default impls are encouraged to optimize this.
60 const auto old_file = OpenFile(old_path, OpenMode::Read);
61 if (old_file == nullptr)
62 return nullptr;
63 auto new_file = OpenFile(new_path, OpenMode::Read);
64 if (new_file != nullptr)
65 return nullptr;
66 new_file = CreateFile(new_path, OpenMode::Write);
67 if (new_file == nullptr)
68 return nullptr;
69 if (!VfsRawCopy(old_file, new_file))
70 return nullptr;
71 return new_file;
72}
73
74VirtualFile VfsFilesystem::MoveFile(std::string_view old_path, std::string_view new_path) {
75 const auto sanitized_old_path = Common::FS::SanitizePath(old_path);
76 const auto sanitized_new_path = Common::FS::SanitizePath(new_path);
77
78 // Again, non-default impls are highly encouraged to provide a more optimized version of this.
79 auto out = CopyFile(sanitized_old_path, sanitized_new_path);
80 if (out == nullptr)
81 return nullptr;
82 if (DeleteFile(sanitized_old_path))
83 return out;
84 return nullptr;
85}
86
87bool VfsFilesystem::DeleteFile(std::string_view path_) {
88 const auto path = Common::FS::SanitizePath(path_);
89 auto parent = OpenDirectory(Common::FS::GetParentPath(path), OpenMode::Write);
90 if (parent == nullptr)
91 return false;
92 return parent->DeleteFile(Common::FS::GetFilename(path));
93}
94
95VirtualDir VfsFilesystem::OpenDirectory(std::string_view path_, OpenMode perms) {
96 const auto path = Common::FS::SanitizePath(path_);
97 return root->GetDirectoryRelative(path);
98}
99
100VirtualDir VfsFilesystem::CreateDirectory(std::string_view path_, OpenMode perms) {
101 const auto path = Common::FS::SanitizePath(path_);
102 return root->CreateDirectoryRelative(path);
103}
104
105VirtualDir VfsFilesystem::CopyDirectory(std::string_view old_path_, std::string_view new_path_) {
106 const auto old_path = Common::FS::SanitizePath(old_path_);
107 const auto new_path = Common::FS::SanitizePath(new_path_);
108
109 // Non-default impls are highly encouraged to provide a more optimized version of this.
110 auto old_dir = OpenDirectory(old_path, OpenMode::Read);
111 if (old_dir == nullptr)
112 return nullptr;
113 auto new_dir = OpenDirectory(new_path, OpenMode::Read);
114 if (new_dir != nullptr)
115 return nullptr;
116 new_dir = CreateDirectory(new_path, OpenMode::Write);
117 if (new_dir == nullptr)
118 return nullptr;
119
120 for (const auto& file : old_dir->GetFiles()) {
121 const auto x = CopyFile(old_path + '/' + file->GetName(), new_path + '/' + file->GetName());
122 if (x == nullptr)
123 return nullptr;
124 }
125
126 for (const auto& dir : old_dir->GetSubdirectories()) {
127 const auto x =
128 CopyDirectory(old_path + '/' + dir->GetName(), new_path + '/' + dir->GetName());
129 if (x == nullptr)
130 return nullptr;
131 }
132
133 return new_dir;
134}
135
136VirtualDir VfsFilesystem::MoveDirectory(std::string_view old_path, std::string_view new_path) {
137 const auto sanitized_old_path = Common::FS::SanitizePath(old_path);
138 const auto sanitized_new_path = Common::FS::SanitizePath(new_path);
139
140 // Non-default impls are highly encouraged to provide a more optimized version of this.
141 auto out = CopyDirectory(sanitized_old_path, sanitized_new_path);
142 if (out == nullptr)
143 return nullptr;
144 if (DeleteDirectory(sanitized_old_path))
145 return out;
146 return nullptr;
147}
148
149bool VfsFilesystem::DeleteDirectory(std::string_view path_) {
150 const auto path = Common::FS::SanitizePath(path_);
151 auto parent = OpenDirectory(Common::FS::GetParentPath(path), OpenMode::Write);
152 if (parent == nullptr)
153 return false;
154 return parent->DeleteSubdirectoryRecursive(Common::FS::GetFilename(path));
155}
156
157VfsFile::~VfsFile() = default;
158
159std::string VfsFile::GetExtension() const {
160 return std::string(Common::FS::GetExtensionFromFilename(GetName()));
161}
162
163VfsDirectory::~VfsDirectory() = default;
164
165std::optional<u8> VfsFile::ReadByte(std::size_t offset) const {
166 u8 out{};
167 const std::size_t size = Read(&out, sizeof(u8), offset);
168 if (size == 1) {
169 return out;
170 }
171
172 return std::nullopt;
173}
174
175std::vector<u8> VfsFile::ReadBytes(std::size_t size, std::size_t offset) const {
176 std::vector<u8> out(size);
177 std::size_t read_size = Read(out.data(), size, offset);
178 out.resize(read_size);
179 return out;
180}
181
182std::vector<u8> VfsFile::ReadAllBytes() const {
183 return ReadBytes(GetSize());
184}
185
186bool VfsFile::WriteByte(u8 data, std::size_t offset) {
187 return Write(&data, 1, offset) == 1;
188}
189
190std::size_t VfsFile::WriteBytes(const std::vector<u8>& data, std::size_t offset) {
191 return Write(data.data(), data.size(), offset);
192}
193
194std::string VfsFile::GetFullPath() const {
195 if (GetContainingDirectory() == nullptr)
196 return '/' + GetName();
197
198 return GetContainingDirectory()->GetFullPath() + '/' + GetName();
199}
200
201VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const {
202 auto vec = Common::FS::SplitPathComponents(path);
203 if (vec.empty()) {
204 return nullptr;
205 }
206
207 if (vec.size() == 1) {
208 return GetFile(vec[0]);
209 }
210
211 auto dir = GetSubdirectory(vec[0]);
212 for (std::size_t component = 1; component < vec.size() - 1; ++component) {
213 if (dir == nullptr) {
214 return nullptr;
215 }
216
217 dir = dir->GetSubdirectory(vec[component]);
218 }
219
220 if (dir == nullptr) {
221 return nullptr;
222 }
223
224 return dir->GetFile(vec.back());
225}
226
227VirtualFile VfsDirectory::GetFileAbsolute(std::string_view path) const {
228 if (IsRoot()) {
229 return GetFileRelative(path);
230 }
231
232 return GetParentDirectory()->GetFileAbsolute(path);
233}
234
235VirtualDir VfsDirectory::GetDirectoryRelative(std::string_view path) const {
236 auto vec = Common::FS::SplitPathComponents(path);
237 if (vec.empty()) {
238 // TODO(DarkLordZach): Return this directory if path is '/' or similar. Can't currently
239 // because of const-ness
240 return nullptr;
241 }
242
243 auto dir = GetSubdirectory(vec[0]);
244 for (std::size_t component = 1; component < vec.size(); ++component) {
245 if (dir == nullptr) {
246 return nullptr;
247 }
248
249 dir = dir->GetSubdirectory(vec[component]);
250 }
251
252 return dir;
253}
254
255VirtualDir VfsDirectory::GetDirectoryAbsolute(std::string_view path) const {
256 if (IsRoot()) {
257 return GetDirectoryRelative(path);
258 }
259
260 return GetParentDirectory()->GetDirectoryAbsolute(path);
261}
262
263VirtualFile VfsDirectory::GetFile(std::string_view name) const {
264 const auto& files = GetFiles();
265 const auto iter = std::find_if(files.begin(), files.end(),
266 [&name](const auto& file1) { return name == file1->GetName(); });
267 return iter == files.end() ? nullptr : *iter;
268}
269
270FileTimeStampRaw VfsDirectory::GetFileTimeStamp([[maybe_unused]] std::string_view path) const {
271 return {};
272}
273
274VirtualDir VfsDirectory::GetSubdirectory(std::string_view name) const {
275 const auto& subs = GetSubdirectories();
276 const auto iter = std::find_if(subs.begin(), subs.end(),
277 [&name](const auto& file1) { return name == file1->GetName(); });
278 return iter == subs.end() ? nullptr : *iter;
279}
280
281bool VfsDirectory::IsRoot() const {
282 return GetParentDirectory() == nullptr;
283}
284
285std::size_t VfsDirectory::GetSize() const {
286 const auto& files = GetFiles();
287 const auto sum_sizes = [](const auto& range) {
288 return std::accumulate(range.begin(), range.end(), 0ULL,
289 [](const auto& f1, const auto& f2) { return f1 + f2->GetSize(); });
290 };
291
292 const auto file_total = sum_sizes(files);
293 const auto& sub_dir = GetSubdirectories();
294 const auto subdir_total = sum_sizes(sub_dir);
295
296 return file_total + subdir_total;
297}
298
299VirtualFile VfsDirectory::CreateFileRelative(std::string_view path) {
300 auto vec = Common::FS::SplitPathComponents(path);
301 if (vec.empty()) {
302 return nullptr;
303 }
304
305 if (vec.size() == 1) {
306 return CreateFile(vec[0]);
307 }
308
309 auto dir = GetSubdirectory(vec[0]);
310 if (dir == nullptr) {
311 dir = CreateSubdirectory(vec[0]);
312 if (dir == nullptr) {
313 return nullptr;
314 }
315 }
316
317 return dir->CreateFileRelative(Common::FS::GetPathWithoutTop(path));
318}
319
320VirtualFile VfsDirectory::CreateFileAbsolute(std::string_view path) {
321 if (IsRoot()) {
322 return CreateFileRelative(path);
323 }
324
325 return GetParentDirectory()->CreateFileAbsolute(path);
326}
327
328VirtualDir VfsDirectory::CreateDirectoryRelative(std::string_view path) {
329 auto vec = Common::FS::SplitPathComponents(path);
330 if (vec.empty()) {
331 return nullptr;
332 }
333
334 if (vec.size() == 1) {
335 return CreateSubdirectory(vec[0]);
336 }
337
338 auto dir = GetSubdirectory(vec[0]);
339 if (dir == nullptr) {
340 dir = CreateSubdirectory(vec[0]);
341 if (dir == nullptr) {
342 return nullptr;
343 }
344 }
345
346 return dir->CreateDirectoryRelative(Common::FS::GetPathWithoutTop(path));
347}
348
349VirtualDir VfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
350 if (IsRoot()) {
351 return CreateDirectoryRelative(path);
352 }
353
354 return GetParentDirectory()->CreateDirectoryAbsolute(path);
355}
356
357bool VfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
358 auto dir = GetSubdirectory(name);
359 if (dir == nullptr) {
360 return false;
361 }
362
363 bool success = true;
364 for (const auto& file : dir->GetFiles()) {
365 if (!DeleteFile(file->GetName())) {
366 success = false;
367 }
368 }
369
370 for (const auto& sdir : dir->GetSubdirectories()) {
371 if (!dir->DeleteSubdirectoryRecursive(sdir->GetName())) {
372 success = false;
373 }
374 }
375
376 return success;
377}
378
379bool VfsDirectory::CleanSubdirectoryRecursive(std::string_view name) {
380 auto dir = GetSubdirectory(name);
381 if (dir == nullptr) {
382 return false;
383 }
384
385 bool success = true;
386 for (const auto& file : dir->GetFiles()) {
387 if (!dir->DeleteFile(file->GetName())) {
388 success = false;
389 }
390 }
391
392 for (const auto& sdir : dir->GetSubdirectories()) {
393 if (!dir->DeleteSubdirectoryRecursive(sdir->GetName())) {
394 success = false;
395 }
396 }
397
398 return success;
399}
400
401bool VfsDirectory::Copy(std::string_view src, std::string_view dest) {
402 const auto f1 = GetFile(src);
403 auto f2 = CreateFile(dest);
404 if (f1 == nullptr || f2 == nullptr) {
405 return false;
406 }
407
408 if (!f2->Resize(f1->GetSize())) {
409 DeleteFile(dest);
410 return false;
411 }
412
413 return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize();
414}
415
416std::map<std::string, VfsEntryType, std::less<>> VfsDirectory::GetEntries() const {
417 std::map<std::string, VfsEntryType, std::less<>> out;
418 for (const auto& dir : GetSubdirectories())
419 out.emplace(dir->GetName(), VfsEntryType::Directory);
420 for (const auto& file : GetFiles())
421 out.emplace(file->GetName(), VfsEntryType::File);
422 return out;
423}
424
425std::string VfsDirectory::GetFullPath() const {
426 if (IsRoot())
427 return GetName();
428
429 return GetParentDirectory()->GetFullPath() + '/' + GetName();
430}
431
432bool ReadOnlyVfsDirectory::IsWritable() const {
433 return false;
434}
435
436bool ReadOnlyVfsDirectory::IsReadable() const {
437 return true;
438}
439
440VirtualDir ReadOnlyVfsDirectory::CreateSubdirectory(std::string_view name) {
441 return nullptr;
442}
443
444VirtualFile ReadOnlyVfsDirectory::CreateFile(std::string_view name) {
445 return nullptr;
446}
447
448VirtualFile ReadOnlyVfsDirectory::CreateFileAbsolute(std::string_view path) {
449 return nullptr;
450}
451
452VirtualFile ReadOnlyVfsDirectory::CreateFileRelative(std::string_view path) {
453 return nullptr;
454}
455
456VirtualDir ReadOnlyVfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
457 return nullptr;
458}
459
460VirtualDir ReadOnlyVfsDirectory::CreateDirectoryRelative(std::string_view path) {
461 return nullptr;
462}
463
464bool ReadOnlyVfsDirectory::DeleteSubdirectory(std::string_view name) {
465 return false;
466}
467
468bool ReadOnlyVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
469 return false;
470}
471
472bool ReadOnlyVfsDirectory::CleanSubdirectoryRecursive(std::string_view name) {
473 return false;
474}
475
476bool ReadOnlyVfsDirectory::DeleteFile(std::string_view name) {
477 return false;
478}
479
480bool ReadOnlyVfsDirectory::Rename(std::string_view name) {
481 return false;
482}
483
484bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t block_size) {
485 if (file1->GetSize() != file2->GetSize())
486 return false;
487
488 std::vector<u8> f1_v(block_size);
489 std::vector<u8> f2_v(block_size);
490 for (std::size_t i = 0; i < file1->GetSize(); i += block_size) {
491 auto f1_vs = file1->Read(f1_v.data(), block_size, i);
492 auto f2_vs = file2->Read(f2_v.data(), block_size, i);
493
494 if (f1_vs != f2_vs)
495 return false;
496 auto iters = std::mismatch(f1_v.begin(), f1_v.end(), f2_v.begin(), f2_v.end());
497 if (iters.first != f1_v.end() && iters.second != f2_v.end())
498 return false;
499 }
500
501 return true;
502}
503
504bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t block_size) {
505 if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
506 return false;
507 if (!dest->Resize(src->GetSize()))
508 return false;
509
510 std::vector<u8> temp(std::min(block_size, src->GetSize()));
511 for (std::size_t i = 0; i < src->GetSize(); i += block_size) {
512 const auto read = std::min(block_size, src->GetSize() - i);
513
514 if (src->Read(temp.data(), read, i) != read) {
515 return false;
516 }
517
518 if (dest->Write(temp.data(), read, i) != read) {
519 return false;
520 }
521 }
522
523 return true;
524}
525
526bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, std::size_t block_size) {
527 if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
528 return false;
529
530 for (const auto& file : src->GetFiles()) {
531 const auto out = dest->CreateFile(file->GetName());
532 if (!VfsRawCopy(file, out, block_size))
533 return false;
534 }
535
536 for (const auto& dir : src->GetSubdirectories()) {
537 const auto out = dest->CreateSubdirectory(dir->GetName());
538 if (!VfsRawCopyD(dir, out, block_size))
539 return false;
540 }
541
542 return true;
543}
544
545VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) {
546 const auto res = rel->GetDirectoryRelative(path);
547 if (res == nullptr)
548 return rel->CreateDirectoryRelative(path);
549 return res;
550}
551} // 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..f846a9669
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs.h
@@ -0,0 +1,326 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <functional>
7#include <map>
8#include <memory>
9#include <optional>
10#include <string>
11#include <type_traits>
12#include <vector>
13
14#include "common/common_funcs.h"
15#include "common/common_types.h"
16#include "core/file_sys/fs_filesystem.h"
17#include "core/file_sys/vfs/vfs_types.h"
18
19namespace FileSys {
20
21// An enumeration representing what can be at the end of a path in a VfsFilesystem
22enum class VfsEntryType {
23 None,
24 File,
25 Directory,
26};
27
28// A class representing an abstract filesystem. A default implementation given the root VirtualDir
29// is provided for convenience, but if the Vfs implementation has any additional state or
30// functionality, they will need to override.
31class VfsFilesystem {
32public:
33 YUZU_NON_COPYABLE(VfsFilesystem);
34 YUZU_NON_MOVEABLE(VfsFilesystem);
35
36 explicit VfsFilesystem(VirtualDir root);
37 virtual ~VfsFilesystem();
38
39 // Gets the friendly name for the filesystem.
40 virtual std::string GetName() const;
41
42 // Return whether or not the user has read permissions on this filesystem.
43 virtual bool IsReadable() const;
44 // Return whether or not the user has write permission on this filesystem.
45 virtual bool IsWritable() const;
46
47 // Determine if the entry at path is non-existent, a file, or a directory.
48 virtual VfsEntryType GetEntryType(std::string_view path) const;
49
50 // Opens the file with path relative to root. If it doesn't exist, returns nullptr.
51 virtual VirtualFile OpenFile(std::string_view path, OpenMode perms);
52 // Creates a new, empty file at path
53 virtual VirtualFile CreateFile(std::string_view path, OpenMode perms);
54 // Copies the file from old_path to new_path, returning the new file on success and nullptr on
55 // failure.
56 virtual VirtualFile CopyFile(std::string_view old_path, std::string_view new_path);
57 // Moves the file from old_path to new_path, returning the moved file on success and nullptr on
58 // failure.
59 virtual VirtualFile MoveFile(std::string_view old_path, std::string_view new_path);
60 // Deletes the file with path relative to root, returning true on success.
61 virtual bool DeleteFile(std::string_view path);
62
63 // Opens the directory with path relative to root. If it doesn't exist, returns nullptr.
64 virtual VirtualDir OpenDirectory(std::string_view path, OpenMode perms);
65 // Creates a new, empty directory at path
66 virtual VirtualDir CreateDirectory(std::string_view path, OpenMode perms);
67 // Copies the directory from old_path to new_path, returning the new directory on success and
68 // nullptr on failure.
69 virtual VirtualDir CopyDirectory(std::string_view old_path, std::string_view new_path);
70 // Moves the directory from old_path to new_path, returning the moved directory on success and
71 // nullptr on failure.
72 virtual VirtualDir MoveDirectory(std::string_view old_path, std::string_view new_path);
73 // Deletes the directory with path relative to root, returning true on success.
74 virtual bool DeleteDirectory(std::string_view path);
75
76protected:
77 // Root directory in default implementation.
78 VirtualDir root;
79};
80
81// A class representing a file in an abstract filesystem.
82class VfsFile {
83public:
84 YUZU_NON_COPYABLE(VfsFile);
85 YUZU_NON_MOVEABLE(VfsFile);
86
87 VfsFile() = default;
88 virtual ~VfsFile();
89
90 // Retrieves the file name.
91 virtual std::string GetName() const = 0;
92 // Retrieves the extension of the file name.
93 virtual std::string GetExtension() const;
94 // Retrieves the size of the file.
95 virtual std::size_t GetSize() const = 0;
96 // Resizes the file to new_size. Returns whether or not the operation was successful.
97 virtual bool Resize(std::size_t new_size) = 0;
98 // Gets a pointer to the directory containing this file, returning nullptr if there is none.
99 virtual VirtualDir GetContainingDirectory() const = 0;
100
101 // Returns whether or not the file can be written to.
102 virtual bool IsWritable() const = 0;
103 // Returns whether or not the file can be read from.
104 virtual bool IsReadable() const = 0;
105
106 // The primary method of reading from the file. Reads length bytes into data starting at offset
107 // into file. Returns number of bytes successfully read.
108 virtual std::size_t Read(u8* data, std::size_t length, std::size_t offset = 0) const = 0;
109 // The primary method of writing to the file. Writes length bytes from data starting at offset
110 // into file. Returns number of bytes successfully written.
111 virtual std::size_t Write(const u8* data, std::size_t length, std::size_t offset = 0) = 0;
112
113 // Reads exactly one byte at the offset provided, returning std::nullopt on error.
114 virtual std::optional<u8> ReadByte(std::size_t offset = 0) const;
115 // Reads size bytes starting at offset in file into a vector.
116 virtual std::vector<u8> ReadBytes(std::size_t size, std::size_t offset = 0) const;
117 // Reads all the bytes from the file into a vector. Equivalent to 'file->Read(file->GetSize(),
118 // 0)'
119 virtual std::vector<u8> ReadAllBytes() const;
120
121 // Reads an array of type T, size number_elements starting at offset.
122 // Returns the number of bytes (sizeof(T)*number_elements) read successfully.
123 template <typename T>
124 std::size_t ReadArray(T* data, std::size_t number_elements, std::size_t offset = 0) const {
125 static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
126
127 return Read(reinterpret_cast<u8*>(data), number_elements * sizeof(T), offset);
128 }
129
130 // Reads size bytes into the memory starting at data starting at offset into the file.
131 // Returns the number of bytes read successfully.
132 template <typename T>
133 std::size_t ReadBytes(T* data, std::size_t size, std::size_t offset = 0) const {
134 static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
135 return Read(reinterpret_cast<u8*>(data), size, offset);
136 }
137
138 // Reads one object of type T starting at offset in file.
139 // Returns the number of bytes read successfully (sizeof(T)).
140 template <typename T>
141 std::size_t ReadObject(T* data, std::size_t offset = 0) const {
142 static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
143 return Read(reinterpret_cast<u8*>(data), sizeof(T), offset);
144 }
145
146 // Writes exactly one byte to offset in file and returns whether or not the byte was written
147 // successfully.
148 virtual bool WriteByte(u8 data, std::size_t offset = 0);
149 // Writes a vector of bytes to offset in file and returns the number of bytes successfully
150 // written.
151 virtual std::size_t WriteBytes(const std::vector<u8>& data, std::size_t offset = 0);
152
153 // Writes an array of type T, size number_elements to offset in file.
154 // Returns the number of bytes (sizeof(T)*number_elements) written successfully.
155 template <typename T>
156 std::size_t WriteArray(const T* data, std::size_t number_elements, std::size_t offset = 0) {
157 static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
158 return Write(reinterpret_cast<const u8*>(data), number_elements * sizeof(T), offset);
159 }
160
161 // Writes size bytes starting at memory location data to offset in file.
162 // Returns the number of bytes written successfully.
163 template <typename T>
164 std::size_t WriteBytes(const T* data, std::size_t size, std::size_t offset = 0) {
165 static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
166 return Write(reinterpret_cast<const u8*>(data), size, offset);
167 }
168
169 // Writes one object of type T to offset in file.
170 // Returns the number of bytes written successfully (sizeof(T)).
171 template <typename T>
172 std::size_t WriteObject(const T& data, std::size_t offset = 0) {
173 static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
174 return Write(reinterpret_cast<const u8*>(&data), sizeof(T), offset);
175 }
176
177 // Renames the file to name. Returns whether or not the operation was successful.
178 virtual bool Rename(std::string_view name) = 0;
179
180 // Returns the full path of this file as a string, recursively
181 virtual std::string GetFullPath() const;
182};
183
184// A class representing a directory in an abstract filesystem.
185class VfsDirectory {
186public:
187 YUZU_NON_COPYABLE(VfsDirectory);
188 YUZU_NON_MOVEABLE(VfsDirectory);
189
190 VfsDirectory() = default;
191 virtual ~VfsDirectory();
192
193 // Retrieves the file located at path as if the current directory was root. Returns nullptr if
194 // not found.
195 virtual VirtualFile GetFileRelative(std::string_view path) const;
196 // Calls GetFileRelative(path) on the root of the current directory.
197 virtual VirtualFile GetFileAbsolute(std::string_view path) const;
198
199 // Retrieves the directory located at path as if the current directory was root. Returns nullptr
200 // if not found.
201 virtual VirtualDir GetDirectoryRelative(std::string_view path) const;
202 // Calls GetDirectoryRelative(path) on the root of the current directory.
203 virtual VirtualDir GetDirectoryAbsolute(std::string_view path) const;
204
205 // Returns a vector containing all of the files in this directory.
206 virtual std::vector<VirtualFile> GetFiles() const = 0;
207 // Returns the file with filename matching name. Returns nullptr if directory doesn't have a
208 // file with name.
209 virtual VirtualFile GetFile(std::string_view name) const;
210
211 // Returns a struct containing the file's timestamp.
212 virtual FileTimeStampRaw GetFileTimeStamp(std::string_view path) const;
213
214 // Returns a vector containing all of the subdirectories in this directory.
215 virtual std::vector<VirtualDir> GetSubdirectories() const = 0;
216 // Returns the directory with name matching name. Returns nullptr if directory doesn't have a
217 // directory with name.
218 virtual VirtualDir GetSubdirectory(std::string_view name) const;
219
220 // Returns whether or not the directory can be written to.
221 virtual bool IsWritable() const = 0;
222 // Returns whether of not the directory can be read from.
223 virtual bool IsReadable() const = 0;
224
225 // Returns whether or not the directory is the root of the current file tree.
226 virtual bool IsRoot() const;
227
228 // Returns the name of the directory.
229 virtual std::string GetName() const = 0;
230 // Returns the total size of all files and subdirectories in this directory.
231 virtual std::size_t GetSize() const;
232 // Returns the parent directory of this directory. Returns nullptr if this directory is root or
233 // has no parent.
234 virtual VirtualDir GetParentDirectory() const = 0;
235
236 // Creates a new subdirectory with name name. Returns a pointer to the new directory or nullptr
237 // if the operation failed.
238 virtual VirtualDir CreateSubdirectory(std::string_view name) = 0;
239 // Creates a new file with name name. Returns a pointer to the new file or nullptr if the
240 // operation failed.
241 virtual VirtualFile CreateFile(std::string_view name) = 0;
242
243 // Creates a new file at the path relative to this directory. Also creates directories if
244 // they do not exist and is supported by this implementation. Returns nullptr on any failure.
245 virtual VirtualFile CreateFileRelative(std::string_view path);
246
247 // Creates a new file at the path relative to root of this directory. Also creates directories
248 // if they do not exist and is supported by this implementation. Returns nullptr on any failure.
249 virtual VirtualFile CreateFileAbsolute(std::string_view path);
250
251 // Creates a new directory at the path relative to this directory. Also creates directories if
252 // they do not exist and is supported by this implementation. Returns nullptr on any failure.
253 virtual VirtualDir CreateDirectoryRelative(std::string_view path);
254
255 // Creates a new directory at the path relative to root of this directory. Also creates
256 // directories if they do not exist and is supported by this implementation. Returns nullptr on
257 // any failure.
258 virtual VirtualDir CreateDirectoryAbsolute(std::string_view path);
259
260 // Deletes the subdirectory with the given name and returns true on success.
261 virtual bool DeleteSubdirectory(std::string_view name) = 0;
262
263 // Deletes all subdirectories and files within the provided directory and then deletes
264 // the directory itself. Returns true on success.
265 virtual bool DeleteSubdirectoryRecursive(std::string_view name);
266
267 // Deletes all subdirectories and files within the provided directory.
268 // Unlike DeleteSubdirectoryRecursive, this does not delete the provided directory.
269 virtual bool CleanSubdirectoryRecursive(std::string_view name);
270
271 // Returns whether or not the file with name name was deleted successfully.
272 virtual bool DeleteFile(std::string_view name) = 0;
273
274 // Returns whether or not this directory was renamed to name.
275 virtual bool Rename(std::string_view name) = 0;
276
277 // Returns whether or not the file with name src was successfully copied to a new file with name
278 // dest.
279 virtual bool Copy(std::string_view src, std::string_view dest);
280
281 // Gets all of the entries directly in the directory (files and dirs), returning a map between
282 // item name -> type.
283 virtual std::map<std::string, VfsEntryType, std::less<>> GetEntries() const;
284
285 // Returns the full path of this directory as a string, recursively
286 virtual std::string GetFullPath() const;
287};
288
289// A convenience partial-implementation of VfsDirectory that stubs out methods that should only work
290// if writable. This is to avoid redundant empty methods everywhere.
291class ReadOnlyVfsDirectory : public VfsDirectory {
292public:
293 bool IsWritable() const override;
294 bool IsReadable() const override;
295 VirtualDir CreateSubdirectory(std::string_view name) override;
296 VirtualFile CreateFile(std::string_view name) override;
297 VirtualFile CreateFileAbsolute(std::string_view path) override;
298 VirtualFile CreateFileRelative(std::string_view path) override;
299 VirtualDir CreateDirectoryAbsolute(std::string_view path) override;
300 VirtualDir CreateDirectoryRelative(std::string_view path) override;
301 bool DeleteSubdirectory(std::string_view name) override;
302 bool DeleteSubdirectoryRecursive(std::string_view name) override;
303 bool CleanSubdirectoryRecursive(std::string_view name) override;
304 bool DeleteFile(std::string_view name) override;
305 bool Rename(std::string_view name) override;
306};
307
308// Compare the two files, byte-for-byte, in increments specified by block_size
309bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2,
310 std::size_t block_size = 0x1000);
311
312// A method that copies the raw data between two different implementations of VirtualFile. If you
313// are using the same implementation, it is probably better to use the Copy method in the parent
314// directory of src/dest.
315bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t block_size = 0x1000);
316
317// A method that performs a similar function to VfsRawCopy above, but instead copies entire
318// directories. It suffers the same performance penalties as above and an implementation-specific
319// Copy should always be preferred.
320bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, std::size_t block_size = 0x1000);
321
322// Checks if the directory at path relative to rel exists. If it does, returns that. If it does not
323// it attempts to create it and returns the new dir or nullptr on failure.
324VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path);
325
326} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/file_sys/vfs/vfs_cached.h"
5#include "core/file_sys/vfs/vfs_types.h"
6
7namespace FileSys {
8
9CachedVfsDirectory::CachedVfsDirectory(VirtualDir&& source_dir)
10 : name(source_dir->GetName()), parent(source_dir->GetParentDirectory()) {
11 for (auto& dir : source_dir->GetSubdirectories()) {
12 dirs.emplace(dir->GetName(), std::make_shared<CachedVfsDirectory>(std::move(dir)));
13 }
14 for (auto& file : source_dir->GetFiles()) {
15 files.emplace(file->GetName(), std::move(file));
16 }
17}
18
19CachedVfsDirectory::~CachedVfsDirectory() = default;
20
21VirtualFile CachedVfsDirectory::GetFile(std::string_view file_name) const {
22 auto it = files.find(file_name);
23 if (it != files.end()) {
24 return it->second;
25 }
26
27 return nullptr;
28}
29
30VirtualDir CachedVfsDirectory::GetSubdirectory(std::string_view dir_name) const {
31 auto it = dirs.find(dir_name);
32 if (it != dirs.end()) {
33 return it->second;
34 }
35
36 return nullptr;
37}
38
39std::vector<VirtualFile> CachedVfsDirectory::GetFiles() const {
40 std::vector<VirtualFile> out;
41 for (auto& [file_name, file] : files) {
42 out.push_back(file);
43 }
44 return out;
45}
46
47std::vector<VirtualDir> CachedVfsDirectory::GetSubdirectories() const {
48 std::vector<VirtualDir> out;
49 for (auto& [dir_name, dir] : dirs) {
50 out.push_back(dir);
51 }
52 return out;
53}
54
55std::string CachedVfsDirectory::GetName() const {
56 return name;
57}
58
59VirtualDir CachedVfsDirectory::GetParentDirectory() const {
60 return parent;
61}
62
63} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string_view>
7#include <vector>
8#include "core/file_sys/vfs/vfs.h"
9
10namespace FileSys {
11
12class CachedVfsDirectory : public ReadOnlyVfsDirectory {
13public:
14 CachedVfsDirectory(VirtualDir&& source_directory);
15
16 ~CachedVfsDirectory() override;
17 VirtualFile GetFile(std::string_view file_name) const override;
18 VirtualDir GetSubdirectory(std::string_view dir_name) const override;
19 std::vector<VirtualFile> GetFiles() const override;
20 std::vector<VirtualDir> GetSubdirectories() const override;
21 std::string GetName() const override;
22 VirtualDir GetParentDirectory() const override;
23
24private:
25 std::string name;
26 VirtualDir parent;
27 std::map<std::string, VirtualDir, std::less<>> dirs;
28 std::map<std::string, VirtualFile, std::less<>> files;
29};
30
31} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <utility>
6
7#include "common/assert.h"
8#include "core/file_sys/vfs/vfs_concat.h"
9#include "core/file_sys/vfs/vfs_static.h"
10
11namespace FileSys {
12
13ConcatenatedVfsFile::ConcatenatedVfsFile(std::string&& name_, ConcatenationMap&& concatenation_map_)
14 : concatenation_map(std::move(concatenation_map_)), name(std::move(name_)) {
15 DEBUG_ASSERT(this->VerifyContinuity());
16}
17
18bool ConcatenatedVfsFile::VerifyContinuity() const {
19 u64 last_offset = 0;
20 for (auto& entry : concatenation_map) {
21 if (entry.offset != last_offset) {
22 return false;
23 }
24
25 last_offset = entry.offset + entry.file->GetSize();
26 }
27
28 return true;
29}
30
31ConcatenatedVfsFile::~ConcatenatedVfsFile() = default;
32
33VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(std::string&& name,
34 std::vector<VirtualFile>&& files) {
35 // Fold trivial cases.
36 if (files.empty()) {
37 return nullptr;
38 }
39 if (files.size() == 1) {
40 return files.front();
41 }
42
43 // Make the concatenation map from the input.
44 std::vector<ConcatenationEntry> concatenation_map;
45 concatenation_map.reserve(files.size());
46 u64 last_offset = 0;
47
48 for (auto& file : files) {
49 const auto size = file->GetSize();
50
51 concatenation_map.emplace_back(ConcatenationEntry{
52 .offset = last_offset,
53 .file = std::move(file),
54 });
55
56 last_offset += size;
57 }
58
59 return VirtualFile(new ConcatenatedVfsFile(std::move(name), std::move(concatenation_map)));
60}
61
62VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(
63 u8 filler_byte, std::string&& name, std::vector<std::pair<u64, VirtualFile>>&& files) {
64 // Fold trivial cases.
65 if (files.empty()) {
66 return nullptr;
67 }
68 if (files.size() == 1) {
69 return files.begin()->second;
70 }
71
72 // Make the concatenation map from the input.
73 std::vector<ConcatenationEntry> concatenation_map;
74
75 concatenation_map.reserve(files.size());
76 u64 last_offset = 0;
77
78 // Iteration of a multimap is ordered, so offset will be strictly non-decreasing.
79 for (auto& [offset, file] : files) {
80 const auto size = file->GetSize();
81
82 if (offset > last_offset) {
83 concatenation_map.emplace_back(ConcatenationEntry{
84 .offset = last_offset,
85 .file = std::make_shared<StaticVfsFile>(filler_byte, offset - last_offset),
86 });
87 }
88
89 concatenation_map.emplace_back(ConcatenationEntry{
90 .offset = offset,
91 .file = std::move(file),
92 });
93
94 last_offset = offset + size;
95 }
96
97 return VirtualFile(new ConcatenatedVfsFile(std::move(name), std::move(concatenation_map)));
98}
99
100std::string ConcatenatedVfsFile::GetName() const {
101 if (concatenation_map.empty()) {
102 return "";
103 }
104 if (!name.empty()) {
105 return name;
106 }
107 return concatenation_map.front().file->GetName();
108}
109
110std::size_t ConcatenatedVfsFile::GetSize() const {
111 if (concatenation_map.empty()) {
112 return 0;
113 }
114 return concatenation_map.back().offset + concatenation_map.back().file->GetSize();
115}
116
117bool ConcatenatedVfsFile::Resize(std::size_t new_size) {
118 return false;
119}
120
121VirtualDir ConcatenatedVfsFile::GetContainingDirectory() const {
122 if (concatenation_map.empty()) {
123 return nullptr;
124 }
125 return concatenation_map.front().file->GetContainingDirectory();
126}
127
128bool ConcatenatedVfsFile::IsWritable() const {
129 return false;
130}
131
132bool ConcatenatedVfsFile::IsReadable() const {
133 return true;
134}
135
136std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
137 const ConcatenationEntry key{
138 .offset = offset,
139 .file = nullptr,
140 };
141
142 // Read nothing if the map is empty.
143 if (concatenation_map.empty()) {
144 return 0;
145 }
146
147 // Binary search to find the iterator to the first position we can check.
148 // It must exist, since we are not empty and are comparing unsigned integers.
149 auto it = std::prev(std::upper_bound(concatenation_map.begin(), concatenation_map.end(), key));
150 u64 cur_length = length;
151 u64 cur_offset = offset;
152
153 while (cur_length > 0 && it != concatenation_map.end()) {
154 // Check if we can read the file at this position.
155 const auto& file = it->file;
156 const u64 map_offset = it->offset;
157 const u64 file_size = file->GetSize();
158
159 if (cur_offset > map_offset + file_size) {
160 // Entirely out of bounds read.
161 break;
162 }
163
164 // Read the file at this position.
165 const u64 file_seek = cur_offset - map_offset;
166 const u64 intended_read_size = std::min<u64>(cur_length, file_size - file_seek);
167 const u64 actual_read_size =
168 file->Read(data + (cur_offset - offset), intended_read_size, file_seek);
169
170 // Update tracking.
171 cur_offset += actual_read_size;
172 cur_length -= actual_read_size;
173 it++;
174
175 // If we encountered a short read, we're done.
176 if (actual_read_size < intended_read_size) {
177 break;
178 }
179 }
180
181 return cur_offset - offset;
182}
183
184std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
185 return 0;
186}
187
188bool ConcatenatedVfsFile::Rename(std::string_view new_name) {
189 return false;
190}
191
192} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <compare>
7#include <map>
8#include <memory>
9#include "core/file_sys/vfs/vfs.h"
10
11namespace FileSys {
12
13// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently
14// read-only.
15class ConcatenatedVfsFile : public VfsFile {
16private:
17 struct ConcatenationEntry {
18 u64 offset;
19 VirtualFile file;
20
21 auto operator<=>(const ConcatenationEntry& other) const {
22 return this->offset <=> other.offset;
23 }
24 };
25 using ConcatenationMap = std::vector<ConcatenationEntry>;
26
27 explicit ConcatenatedVfsFile(std::string&& name,
28 std::vector<ConcatenationEntry>&& concatenation_map);
29 bool VerifyContinuity() const;
30
31public:
32 ~ConcatenatedVfsFile() override;
33
34 /// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
35 static VirtualFile MakeConcatenatedFile(std::string&& name, std::vector<VirtualFile>&& files);
36
37 /// Convenience function that turns a map of offsets to files into a concatenated file, filling
38 /// gaps with a given filler byte.
39 static VirtualFile MakeConcatenatedFile(u8 filler_byte, std::string&& name,
40 std::vector<std::pair<u64, VirtualFile>>&& files);
41
42 std::string GetName() const override;
43 std::size_t GetSize() const override;
44 bool Resize(std::size_t new_size) override;
45 VirtualDir GetContainingDirectory() const override;
46 bool IsWritable() const override;
47 bool IsReadable() const override;
48 std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
49 std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
50 bool Rename(std::string_view new_name) override;
51
52private:
53 ConcatenationMap concatenation_map;
54 std::string name;
55};
56
57} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <set>
6#include <unordered_set>
7#include <utility>
8#include "core/file_sys/vfs/vfs_layered.h"
9
10namespace FileSys {
11
12LayeredVfsDirectory::LayeredVfsDirectory(std::vector<VirtualDir> dirs_, std::string name_)
13 : dirs(std::move(dirs_)), name(std::move(name_)) {}
14
15LayeredVfsDirectory::~LayeredVfsDirectory() = default;
16
17VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(std::vector<VirtualDir> dirs,
18 std::string name) {
19 if (dirs.empty())
20 return nullptr;
21 if (dirs.size() == 1)
22 return dirs[0];
23
24 return VirtualDir(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
25}
26
27VirtualFile LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
28 for (const auto& layer : dirs) {
29 const auto file = layer->GetFileRelative(path);
30 if (file != nullptr)
31 return file;
32 }
33
34 return nullptr;
35}
36
37VirtualDir LayeredVfsDirectory::GetDirectoryRelative(std::string_view path) const {
38 std::vector<VirtualDir> out;
39 for (const auto& layer : dirs) {
40 auto dir = layer->GetDirectoryRelative(path);
41 if (dir != nullptr) {
42 out.emplace_back(std::move(dir));
43 }
44 }
45
46 return MakeLayeredDirectory(std::move(out));
47}
48
49VirtualFile LayeredVfsDirectory::GetFile(std::string_view file_name) const {
50 return GetFileRelative(file_name);
51}
52
53VirtualDir LayeredVfsDirectory::GetSubdirectory(std::string_view subdir_name) const {
54 return GetDirectoryRelative(subdir_name);
55}
56
57std::string LayeredVfsDirectory::GetFullPath() const {
58 return dirs[0]->GetFullPath();
59}
60
61std::vector<VirtualFile> LayeredVfsDirectory::GetFiles() const {
62 std::vector<VirtualFile> out;
63 std::unordered_set<std::string> out_names;
64
65 for (const auto& layer : dirs) {
66 for (auto& file : layer->GetFiles()) {
67 const auto [it, is_new] = out_names.emplace(file->GetName());
68 if (is_new) {
69 out.emplace_back(std::move(file));
70 }
71 }
72 }
73
74 return out;
75}
76
77std::vector<VirtualDir> LayeredVfsDirectory::GetSubdirectories() const {
78 std::vector<VirtualDir> out;
79 std::unordered_set<std::string> out_names;
80
81 for (const auto& layer : dirs) {
82 for (const auto& sd : layer->GetSubdirectories()) {
83 out_names.emplace(sd->GetName());
84 }
85 }
86
87 out.reserve(out_names.size());
88 for (const auto& subdir : out_names) {
89 out.emplace_back(GetSubdirectory(subdir));
90 }
91
92 return out;
93}
94
95bool LayeredVfsDirectory::IsWritable() const {
96 return false;
97}
98
99bool LayeredVfsDirectory::IsReadable() const {
100 return true;
101}
102
103std::string LayeredVfsDirectory::GetName() const {
104 return name.empty() ? dirs[0]->GetName() : name;
105}
106
107VirtualDir LayeredVfsDirectory::GetParentDirectory() const {
108 return dirs[0]->GetParentDirectory();
109}
110
111VirtualDir LayeredVfsDirectory::CreateSubdirectory(std::string_view subdir_name) {
112 return nullptr;
113}
114
115VirtualFile LayeredVfsDirectory::CreateFile(std::string_view file_name) {
116 return nullptr;
117}
118
119bool LayeredVfsDirectory::DeleteSubdirectory(std::string_view subdir_name) {
120 return false;
121}
122
123bool LayeredVfsDirectory::DeleteFile(std::string_view file_name) {
124 return false;
125}
126
127bool LayeredVfsDirectory::Rename(std::string_view new_name) {
128 name = new_name;
129 return true;
130}
131
132} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include "core/file_sys/vfs/vfs.h"
8
9namespace FileSys {
10
11// Class that stacks multiple VfsDirectories on top of each other, attempting to read from the first
12// one and falling back to the one after. The highest priority directory (overwrites all others)
13// should be element 0 in the dirs vector.
14class LayeredVfsDirectory : public VfsDirectory {
15 explicit LayeredVfsDirectory(std::vector<VirtualDir> dirs_, std::string name_);
16
17public:
18 ~LayeredVfsDirectory() override;
19
20 /// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases.
21 static VirtualDir MakeLayeredDirectory(std::vector<VirtualDir> dirs, std::string name = "");
22
23 VirtualFile GetFileRelative(std::string_view path) const override;
24 VirtualDir GetDirectoryRelative(std::string_view path) const override;
25 VirtualFile GetFile(std::string_view file_name) const override;
26 VirtualDir GetSubdirectory(std::string_view subdir_name) const override;
27 std::string GetFullPath() const override;
28
29 std::vector<VirtualFile> GetFiles() const override;
30 std::vector<VirtualDir> GetSubdirectories() const override;
31 bool IsWritable() const override;
32 bool IsReadable() const override;
33 std::string GetName() const override;
34 VirtualDir GetParentDirectory() const override;
35 VirtualDir CreateSubdirectory(std::string_view subdir_name) override;
36 VirtualFile CreateFile(std::string_view file_name) override;
37 bool DeleteSubdirectory(std::string_view subdir_name) override;
38 bool DeleteFile(std::string_view file_name) override;
39 bool Rename(std::string_view new_name) override;
40
41private:
42 std::vector<VirtualDir> dirs;
43 std::string name;
44};
45
46} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <utility>
6
7#include "core/file_sys/vfs/vfs_offset.h"
8
9namespace FileSys {
10
11OffsetVfsFile::OffsetVfsFile(VirtualFile file_, std::size_t size_, std::size_t offset_,
12 std::string name_, VirtualDir parent_)
13 : file(file_), offset(offset_), size(size_), name(std::move(name_)),
14 parent(parent_ == nullptr ? file->GetContainingDirectory() : std::move(parent_)) {}
15
16OffsetVfsFile::~OffsetVfsFile() = default;
17
18std::string OffsetVfsFile::GetName() const {
19 return name.empty() ? file->GetName() : name;
20}
21
22std::size_t OffsetVfsFile::GetSize() const {
23 return size;
24}
25
26bool OffsetVfsFile::Resize(std::size_t new_size) {
27 if (offset + new_size < file->GetSize()) {
28 size = new_size;
29 } else {
30 auto res = file->Resize(offset + new_size);
31 if (!res)
32 return false;
33 size = new_size;
34 }
35
36 return true;
37}
38
39VirtualDir OffsetVfsFile::GetContainingDirectory() const {
40 return parent;
41}
42
43bool OffsetVfsFile::IsWritable() const {
44 return file->IsWritable();
45}
46
47bool OffsetVfsFile::IsReadable() const {
48 return file->IsReadable();
49}
50
51std::size_t OffsetVfsFile::Read(u8* data, std::size_t length, std::size_t r_offset) const {
52 return file->Read(data, TrimToFit(length, r_offset), offset + r_offset);
53}
54
55std::size_t OffsetVfsFile::Write(const u8* data, std::size_t length, std::size_t r_offset) {
56 return file->Write(data, TrimToFit(length, r_offset), offset + r_offset);
57}
58
59std::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const {
60 if (r_offset >= size) {
61 return std::nullopt;
62 }
63
64 return file->ReadByte(offset + r_offset);
65}
66
67std::vector<u8> OffsetVfsFile::ReadBytes(std::size_t r_size, std::size_t r_offset) const {
68 return file->ReadBytes(TrimToFit(r_size, r_offset), offset + r_offset);
69}
70
71std::vector<u8> OffsetVfsFile::ReadAllBytes() const {
72 return file->ReadBytes(size, offset);
73}
74
75bool OffsetVfsFile::WriteByte(u8 data, std::size_t r_offset) {
76 if (r_offset < size)
77 return file->WriteByte(data, offset + r_offset);
78
79 return false;
80}
81
82std::size_t OffsetVfsFile::WriteBytes(const std::vector<u8>& data, std::size_t r_offset) {
83 return file->Write(data.data(), TrimToFit(data.size(), r_offset), offset + r_offset);
84}
85
86bool OffsetVfsFile::Rename(std::string_view new_name) {
87 return file->Rename(new_name);
88}
89
90std::size_t OffsetVfsFile::GetOffset() const {
91 return offset;
92}
93
94std::size_t OffsetVfsFile::TrimToFit(std::size_t r_size, std::size_t r_offset) const {
95 return std::clamp(r_size, std::size_t{0}, size - r_offset);
96}
97
98} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7
8#include "core/file_sys/vfs/vfs.h"
9
10namespace FileSys {
11
12// An implementation of VfsFile that wraps around another VfsFile at a certain offset.
13// Similar to seeking to an offset.
14// If the file is writable, operations that would write past the end of the offset file will expand
15// the size of this wrapper.
16class OffsetVfsFile : public VfsFile {
17public:
18 OffsetVfsFile(VirtualFile file, std::size_t size, std::size_t offset = 0,
19 std::string new_name = "", VirtualDir new_parent = nullptr);
20 ~OffsetVfsFile() override;
21
22 std::string GetName() const override;
23 std::size_t GetSize() const override;
24 bool Resize(std::size_t new_size) override;
25 VirtualDir GetContainingDirectory() const override;
26 bool IsWritable() const override;
27 bool IsReadable() const override;
28 std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
29 std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
30 std::optional<u8> ReadByte(std::size_t offset) const override;
31 std::vector<u8> ReadBytes(std::size_t size, std::size_t offset) const override;
32 std::vector<u8> ReadAllBytes() const override;
33 bool WriteByte(u8 data, std::size_t offset) override;
34 std::size_t WriteBytes(const std::vector<u8>& data, std::size_t offset) override;
35
36 bool Rename(std::string_view new_name) override;
37
38 std::size_t GetOffset() const;
39
40private:
41 std::size_t TrimToFit(std::size_t r_size, std::size_t r_offset) const;
42
43 VirtualFile file;
44 std::size_t offset;
45 std::size_t size;
46 std::string name;
47 VirtualDir parent;
48};
49
50} // 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..627d5d251
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_real.cpp
@@ -0,0 +1,527 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <cstddef>
6#include <iterator>
7#include <utility>
8#include "common/assert.h"
9#include "common/fs/file.h"
10#include "common/fs/fs.h"
11#include "common/fs/path_util.h"
12#include "common/logging/log.h"
13#include "core/file_sys/vfs/vfs.h"
14#include "core/file_sys/vfs/vfs_real.h"
15
16// For FileTimeStampRaw
17#include <sys/stat.h>
18
19#ifdef _MSC_VER
20#define stat _stat64
21#endif
22
23namespace FileSys {
24
25namespace FS = Common::FS;
26
27namespace {
28
29constexpr size_t MaxOpenFiles = 512;
30
31constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(OpenMode mode) {
32 switch (mode) {
33 case OpenMode::Read:
34 return FS::FileAccessMode::Read;
35 case OpenMode::Write:
36 case OpenMode::ReadWrite:
37 case OpenMode::AllowAppend:
38 case OpenMode::All:
39 return FS::FileAccessMode::ReadWrite;
40 default:
41 return {};
42 }
43}
44
45} // Anonymous namespace
46
47RealVfsFilesystem::RealVfsFilesystem() : VfsFilesystem(nullptr) {}
48RealVfsFilesystem::~RealVfsFilesystem() = default;
49
50std::string RealVfsFilesystem::GetName() const {
51 return "Real";
52}
53
54bool RealVfsFilesystem::IsReadable() const {
55 return true;
56}
57
58bool RealVfsFilesystem::IsWritable() const {
59 return true;
60}
61
62VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const {
63 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
64 if (!FS::Exists(path)) {
65 return VfsEntryType::None;
66 }
67 if (FS::IsDir(path)) {
68 return VfsEntryType::Directory;
69 }
70
71 return VfsEntryType::File;
72}
73
74VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::optional<u64> size,
75 OpenMode perms) {
76 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
77 std::scoped_lock lk{list_lock};
78
79 if (auto it = cache.find(path); it != cache.end()) {
80 if (auto file = it->second.lock(); file) {
81 return file;
82 }
83 }
84
85 if (!size && !FS::IsFile(path)) {
86 return nullptr;
87 }
88
89 auto reference = std::make_unique<FileReference>();
90 this->InsertReferenceIntoListLocked(*reference);
91
92 auto file = std::shared_ptr<RealVfsFile>(
93 new RealVfsFile(*this, std::move(reference), path, perms, size));
94 cache[path] = file;
95
96 return file;
97}
98
99VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, OpenMode perms) {
100 return OpenFileFromEntry(path_, {}, perms);
101}
102
103VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, OpenMode perms) {
104 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
105 {
106 std::scoped_lock lk{list_lock};
107 cache.erase(path);
108 }
109
110 // Current usages of CreateFile expect to delete the contents of an existing file.
111 if (FS::IsFile(path)) {
112 FS::IOFile temp{path, FS::FileAccessMode::Write, FS::FileType::BinaryFile};
113
114 if (!temp.IsOpen()) {
115 return nullptr;
116 }
117
118 temp.Close();
119
120 return OpenFile(path, perms);
121 }
122
123 if (!FS::NewFile(path)) {
124 return nullptr;
125 }
126
127 return OpenFile(path, perms);
128}
129
130VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) {
131 // Unused
132 return nullptr;
133}
134
135VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) {
136 const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
137 const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
138 {
139 std::scoped_lock lk{list_lock};
140 cache.erase(old_path);
141 cache.erase(new_path);
142 }
143 if (!FS::RenameFile(old_path, new_path)) {
144 return nullptr;
145 }
146 return OpenFile(new_path, OpenMode::ReadWrite);
147}
148
149bool RealVfsFilesystem::DeleteFile(std::string_view path_) {
150 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
151 {
152 std::scoped_lock lk{list_lock};
153 cache.erase(path);
154 }
155 return FS::RemoveFile(path);
156}
157
158VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, OpenMode perms) {
159 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
160 return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
161}
162
163VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, OpenMode perms) {
164 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
165 if (!FS::CreateDirs(path)) {
166 return nullptr;
167 }
168 return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
169}
170
171VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_,
172 std::string_view new_path_) {
173 // Unused
174 return nullptr;
175}
176
177VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_,
178 std::string_view new_path_) {
179 const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault);
180 const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault);
181
182 if (!FS::RenameDir(old_path, new_path)) {
183 return nullptr;
184 }
185 return OpenDirectory(new_path, OpenMode::ReadWrite);
186}
187
188bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) {
189 const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault);
190 return FS::RemoveDirRecursively(path);
191}
192
193std::unique_lock<std::mutex> RealVfsFilesystem::RefreshReference(const std::string& path,
194 OpenMode perms,
195 FileReference& reference) {
196 std::unique_lock lk{list_lock};
197
198 // Temporarily remove from list.
199 this->RemoveReferenceFromListLocked(reference);
200
201 // Restore file if needed.
202 if (!reference.file) {
203 this->EvictSingleReferenceLocked();
204
205 reference.file =
206 FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile);
207 if (reference.file) {
208 num_open_files++;
209 }
210 }
211
212 // Reinsert into list.
213 this->InsertReferenceIntoListLocked(reference);
214
215 return lk;
216}
217
218void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference) {
219 std::scoped_lock lk{list_lock};
220
221 // Remove from list.
222 this->RemoveReferenceFromListLocked(*reference);
223
224 // Close the file.
225 if (reference->file) {
226 reference->file.reset();
227 num_open_files--;
228 }
229}
230
231void RealVfsFilesystem::EvictSingleReferenceLocked() {
232 if (num_open_files < MaxOpenFiles || open_references.empty()) {
233 return;
234 }
235
236 // Get and remove from list.
237 auto& reference = open_references.back();
238 this->RemoveReferenceFromListLocked(reference);
239
240 // Close the file.
241 if (reference.file) {
242 reference.file.reset();
243 num_open_files--;
244 }
245
246 // Reinsert into closed list.
247 this->InsertReferenceIntoListLocked(reference);
248}
249
250void RealVfsFilesystem::InsertReferenceIntoListLocked(FileReference& reference) {
251 if (reference.file) {
252 open_references.push_front(reference);
253 } else {
254 closed_references.push_front(reference);
255 }
256}
257
258void RealVfsFilesystem::RemoveReferenceFromListLocked(FileReference& reference) {
259 if (reference.file) {
260 open_references.erase(open_references.iterator_to(reference));
261 } else {
262 closed_references.erase(closed_references.iterator_to(reference));
263 }
264}
265
266RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_,
267 const std::string& path_, OpenMode perms_, std::optional<u64> size_)
268 : base(base_), reference(std::move(reference_)), path(path_),
269 parent_path(FS::GetParentPath(path_)), path_components(FS::SplitPathComponentsCopy(path_)),
270 size(size_), perms(perms_) {}
271
272RealVfsFile::~RealVfsFile() {
273 base.DropReference(std::move(reference));
274}
275
276std::string RealVfsFile::GetName() const {
277 return path_components.empty() ? "" : std::string(path_components.back());
278}
279
280std::size_t RealVfsFile::GetSize() const {
281 if (size) {
282 return *size;
283 }
284 auto lk = base.RefreshReference(path, perms, *reference);
285 return reference->file ? reference->file->GetSize() : 0;
286}
287
288bool RealVfsFile::Resize(std::size_t new_size) {
289 size.reset();
290 auto lk = base.RefreshReference(path, perms, *reference);
291 return reference->file ? reference->file->SetSize(new_size) : false;
292}
293
294VirtualDir RealVfsFile::GetContainingDirectory() const {
295 return base.OpenDirectory(parent_path, perms);
296}
297
298bool RealVfsFile::IsWritable() const {
299 return True(perms & OpenMode::Write);
300}
301
302bool RealVfsFile::IsReadable() const {
303 return True(perms & OpenMode::Read);
304}
305
306std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
307 auto lk = base.RefreshReference(path, perms, *reference);
308 if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) {
309 return 0;
310 }
311 return reference->file->ReadSpan(std::span{data, length});
312}
313
314std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
315 size.reset();
316 auto lk = base.RefreshReference(path, perms, *reference);
317 if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) {
318 return 0;
319 }
320 return reference->file->WriteSpan(std::span{data, length});
321}
322
323bool RealVfsFile::Rename(std::string_view name) {
324 return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr;
325}
326
327// TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if
328// constexpr' because there is a compile error in the branch not used.
329
330template <>
331std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>() const {
332 if (perms == OpenMode::AllowAppend) {
333 return {};
334 }
335
336 std::vector<VirtualFile> out;
337
338 const FS::DirEntryCallable callback = [this,
339 &out](const std::filesystem::directory_entry& entry) {
340 const auto full_path_string = FS::PathToUTF8String(entry.path());
341
342 out.emplace_back(base.OpenFileFromEntry(full_path_string, entry.file_size(), perms));
343
344 return true;
345 };
346
347 FS::IterateDirEntries(path, callback, FS::DirEntryFilter::File);
348
349 return out;
350}
351
352template <>
353std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDirectory>() const {
354 if (perms == OpenMode::AllowAppend) {
355 return {};
356 }
357
358 std::vector<VirtualDir> out;
359
360 const FS::DirEntryCallable callback = [this,
361 &out](const std::filesystem::directory_entry& entry) {
362 const auto full_path_string = FS::PathToUTF8String(entry.path());
363
364 out.emplace_back(base.OpenDirectory(full_path_string, perms));
365
366 return true;
367 };
368
369 FS::IterateDirEntries(path, callback, FS::DirEntryFilter::Directory);
370
371 return out;
372}
373
374RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_,
375 OpenMode perms_)
376 : base(base_), path(FS::RemoveTrailingSlash(path_)), parent_path(FS::GetParentPath(path)),
377 path_components(FS::SplitPathComponentsCopy(path)), perms(perms_) {
378 if (!FS::Exists(path) && True(perms & OpenMode::Write)) {
379 void(FS::CreateDirs(path));
380 }
381}
382
383RealVfsDirectory::~RealVfsDirectory() = default;
384
385VirtualFile RealVfsDirectory::GetFileRelative(std::string_view relative_path) const {
386 const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
387 if (!FS::Exists(full_path) || FS::IsDir(full_path)) {
388 return nullptr;
389 }
390 return base.OpenFile(full_path, perms);
391}
392
393VirtualDir RealVfsDirectory::GetDirectoryRelative(std::string_view relative_path) const {
394 const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
395 if (!FS::Exists(full_path) || !FS::IsDir(full_path)) {
396 return nullptr;
397 }
398 return base.OpenDirectory(full_path, perms);
399}
400
401VirtualFile RealVfsDirectory::GetFile(std::string_view name) const {
402 return GetFileRelative(name);
403}
404
405VirtualDir RealVfsDirectory::GetSubdirectory(std::string_view name) const {
406 return GetDirectoryRelative(name);
407}
408
409VirtualFile RealVfsDirectory::CreateFileRelative(std::string_view relative_path) {
410 const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
411 if (!FS::CreateParentDirs(full_path)) {
412 return nullptr;
413 }
414 return base.CreateFile(full_path, perms);
415}
416
417VirtualDir RealVfsDirectory::CreateDirectoryRelative(std::string_view relative_path) {
418 const auto full_path = FS::SanitizePath(path + '/' + std::string(relative_path));
419 return base.CreateDirectory(full_path, perms);
420}
421
422bool RealVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
423 const auto full_path = FS::SanitizePath(this->path + '/' + std::string(name));
424 return base.DeleteDirectory(full_path);
425}
426
427std::vector<VirtualFile> RealVfsDirectory::GetFiles() const {
428 return IterateEntries<RealVfsFile, VfsFile>();
429}
430
431FileTimeStampRaw RealVfsDirectory::GetFileTimeStamp(std::string_view path_) const {
432 const auto full_path = FS::SanitizePath(path + '/' + std::string(path_));
433 const auto fs_path = std::filesystem::path{FS::ToU8String(full_path)};
434 struct stat file_status;
435
436#ifdef _WIN32
437 const auto stat_result = _wstat64(fs_path.c_str(), &file_status);
438#else
439 const auto stat_result = stat(fs_path.c_str(), &file_status);
440#endif
441
442 if (stat_result != 0) {
443 return {};
444 }
445
446 return {
447 .created{static_cast<u64>(file_status.st_ctime)},
448 .accessed{static_cast<u64>(file_status.st_atime)},
449 .modified{static_cast<u64>(file_status.st_mtime)},
450 };
451}
452
453std::vector<VirtualDir> RealVfsDirectory::GetSubdirectories() const {
454 return IterateEntries<RealVfsDirectory, VfsDirectory>();
455}
456
457bool RealVfsDirectory::IsWritable() const {
458 return True(perms & OpenMode::Write);
459}
460
461bool RealVfsDirectory::IsReadable() const {
462 return True(perms & OpenMode::Read);
463}
464
465std::string RealVfsDirectory::GetName() const {
466 return path_components.empty() ? "" : std::string(path_components.back());
467}
468
469VirtualDir RealVfsDirectory::GetParentDirectory() const {
470 if (path_components.size() <= 1) {
471 return nullptr;
472 }
473
474 return base.OpenDirectory(parent_path, perms);
475}
476
477VirtualDir RealVfsDirectory::CreateSubdirectory(std::string_view name) {
478 const std::string subdir_path = (path + '/').append(name);
479 return base.CreateDirectory(subdir_path, perms);
480}
481
482VirtualFile RealVfsDirectory::CreateFile(std::string_view name) {
483 const std::string file_path = (path + '/').append(name);
484 return base.CreateFile(file_path, perms);
485}
486
487bool RealVfsDirectory::DeleteSubdirectory(std::string_view name) {
488 const std::string subdir_path = (path + '/').append(name);
489 return base.DeleteDirectory(subdir_path);
490}
491
492bool RealVfsDirectory::DeleteFile(std::string_view name) {
493 const std::string file_path = (path + '/').append(name);
494 return base.DeleteFile(file_path);
495}
496
497bool RealVfsDirectory::Rename(std::string_view name) {
498 const std::string new_name = (parent_path + '/').append(name);
499 return base.MoveFile(path, new_name) != nullptr;
500}
501
502std::string RealVfsDirectory::GetFullPath() const {
503 auto out = path;
504 std::replace(out.begin(), out.end(), '\\', '/');
505 return out;
506}
507
508std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries() const {
509 if (perms == OpenMode::AllowAppend) {
510 return {};
511 }
512
513 std::map<std::string, VfsEntryType, std::less<>> out;
514
515 const FS::DirEntryCallable callback = [&out](const std::filesystem::directory_entry& entry) {
516 const auto filename = FS::PathToUTF8String(entry.path().filename());
517 out.insert_or_assign(filename,
518 entry.is_directory() ? VfsEntryType::Directory : VfsEntryType::File);
519 return true;
520 };
521
522 FS::IterateDirEntries(path, callback);
523
524 return out;
525}
526
527} // 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..5c2172cce
--- /dev/null
+++ b/src/core/file_sys/vfs/vfs_real.h
@@ -0,0 +1,148 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <map>
7#include <mutex>
8#include <optional>
9#include <string_view>
10#include "common/intrusive_list.h"
11#include "core/file_sys/fs_filesystem.h"
12#include "core/file_sys/vfs/vfs.h"
13
14namespace Common::FS {
15class IOFile;
16}
17
18namespace FileSys {
19
20struct FileReference : public Common::IntrusiveListBaseNode<FileReference> {
21 std::shared_ptr<Common::FS::IOFile> file{};
22};
23
24class RealVfsFile;
25class RealVfsDirectory;
26
27class RealVfsFilesystem : public VfsFilesystem {
28public:
29 RealVfsFilesystem();
30 ~RealVfsFilesystem() override;
31
32 std::string GetName() const override;
33 bool IsReadable() const override;
34 bool IsWritable() const override;
35 VfsEntryType GetEntryType(std::string_view path) const override;
36 VirtualFile OpenFile(std::string_view path, OpenMode perms = OpenMode::Read) override;
37 VirtualFile CreateFile(std::string_view path, OpenMode perms = OpenMode::ReadWrite) override;
38 VirtualFile CopyFile(std::string_view old_path, std::string_view new_path) override;
39 VirtualFile MoveFile(std::string_view old_path, std::string_view new_path) override;
40 bool DeleteFile(std::string_view path) override;
41 VirtualDir OpenDirectory(std::string_view path, OpenMode perms = OpenMode::Read) override;
42 VirtualDir CreateDirectory(std::string_view path,
43 OpenMode perms = OpenMode::ReadWrite) override;
44 VirtualDir CopyDirectory(std::string_view old_path, std::string_view new_path) override;
45 VirtualDir MoveDirectory(std::string_view old_path, std::string_view new_path) override;
46 bool DeleteDirectory(std::string_view path) override;
47
48private:
49 using ReferenceListType = Common::IntrusiveListBaseTraits<FileReference>::ListType;
50 std::map<std::string, std::weak_ptr<VfsFile>, std::less<>> cache;
51 ReferenceListType open_references;
52 ReferenceListType closed_references;
53 std::mutex list_lock;
54 size_t num_open_files{};
55
56private:
57 friend class RealVfsFile;
58 std::unique_lock<std::mutex> RefreshReference(const std::string& path, OpenMode perms,
59 FileReference& reference);
60 void DropReference(std::unique_ptr<FileReference>&& reference);
61
62private:
63 friend class RealVfsDirectory;
64 VirtualFile OpenFileFromEntry(std::string_view path, std::optional<u64> size,
65 OpenMode perms = OpenMode::Read);
66
67private:
68 void EvictSingleReferenceLocked();
69 void InsertReferenceIntoListLocked(FileReference& reference);
70 void RemoveReferenceFromListLocked(FileReference& reference);
71};
72
73// An implementation of VfsFile that represents a file on the user's computer.
74class RealVfsFile : public VfsFile {
75 friend class RealVfsDirectory;
76 friend class RealVfsFilesystem;
77
78public:
79 ~RealVfsFile() override;
80
81 std::string GetName() const override;
82 std::size_t GetSize() const override;
83 bool Resize(std::size_t new_size) override;
84 VirtualDir GetContainingDirectory() const override;
85 bool IsWritable() const override;
86 bool IsReadable() const override;
87 std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
88 std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
89 bool Rename(std::string_view name) override;
90
91private:
92 RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference,
93 const std::string& path, OpenMode perms = OpenMode::Read,
94 std::optional<u64> size = {});
95
96 RealVfsFilesystem& base;
97 std::unique_ptr<FileReference> reference;
98 std::string path;
99 std::string parent_path;
100 std::vector<std::string> path_components;
101 std::optional<u64> size;
102 OpenMode perms;
103};
104
105// An implementation of VfsDirectory that represents a directory on the user's computer.
106class RealVfsDirectory : public VfsDirectory {
107 friend class RealVfsFilesystem;
108
109public:
110 ~RealVfsDirectory() override;
111
112 VirtualFile GetFileRelative(std::string_view relative_path) const override;
113 VirtualDir GetDirectoryRelative(std::string_view relative_path) const override;
114 VirtualFile GetFile(std::string_view name) const override;
115 VirtualDir GetSubdirectory(std::string_view name) const override;
116 VirtualFile CreateFileRelative(std::string_view relative_path) override;
117 VirtualDir CreateDirectoryRelative(std::string_view relative_path) override;
118 bool DeleteSubdirectoryRecursive(std::string_view name) override;
119 std::vector<VirtualFile> GetFiles() const override;
120 FileTimeStampRaw GetFileTimeStamp(std::string_view path) const override;
121 std::vector<VirtualDir> GetSubdirectories() const override;
122 bool IsWritable() const override;
123 bool IsReadable() const override;
124 std::string GetName() const override;
125 VirtualDir GetParentDirectory() const override;
126 VirtualDir CreateSubdirectory(std::string_view name) override;
127 VirtualFile CreateFile(std::string_view name) override;
128 bool DeleteSubdirectory(std::string_view name) override;
129 bool DeleteFile(std::string_view name) override;
130 bool Rename(std::string_view name) override;
131 std::string GetFullPath() const override;
132 std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override;
133
134private:
135 RealVfsDirectory(RealVfsFilesystem& base, const std::string& path,
136 OpenMode perms = OpenMode::Read);
137
138 template <typename T, typename R>
139 std::vector<std::shared_ptr<R>> IterateEntries() const;
140
141 RealVfsFilesystem& base;
142 std::string path;
143 std::string parent_path;
144 std::vector<std::string> path_components;
145 OpenMode perms;
146};
147
148} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <algorithm>
7#include <memory>
8#include <string_view>
9
10#include "core/file_sys/vfs/vfs.h"
11
12namespace FileSys {
13
14class StaticVfsFile : public VfsFile {
15public:
16 explicit StaticVfsFile(u8 value_, std::size_t size_ = 0, std::string name_ = "",
17 VirtualDir parent_ = nullptr)
18 : value{value_}, size{size_}, name{std::move(name_)}, parent{std::move(parent_)} {}
19
20 std::string GetName() const override {
21 return name;
22 }
23
24 std::size_t GetSize() const override {
25 return size;
26 }
27
28 bool Resize(std::size_t new_size) override {
29 size = new_size;
30 return true;
31 }
32
33 VirtualDir GetContainingDirectory() const override {
34 return parent;
35 }
36
37 bool IsWritable() const override {
38 return false;
39 }
40
41 bool IsReadable() const override {
42 return true;
43 }
44
45 std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override {
46 const auto read = std::min(length, size - offset);
47 std::fill(data, data + read, value);
48 return read;
49 }
50
51 std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override {
52 return 0;
53 }
54
55 std::optional<u8> ReadByte(std::size_t offset) const override {
56 if (offset >= size) {
57 return std::nullopt;
58 }
59
60 return value;
61 }
62
63 std::vector<u8> ReadBytes(std::size_t length, std::size_t offset) const override {
64 const auto read = std::min(length, size - offset);
65 return std::vector<u8>(read, value);
66 }
67
68 bool Rename(std::string_view new_name) override {
69 name = new_name;
70 return true;
71 }
72
73private:
74 u8 value;
75 std::size_t size;
76 std::string name;
77 VirtualDir parent;
78};
79
80} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7
8#include "common/common_types.h"
9
10namespace FileSys {
11
12class VfsDirectory;
13class VfsFile;
14class VfsFilesystem;
15
16// Declarations for Vfs* pointer types
17
18using VirtualDir = std::shared_ptr<VfsDirectory>;
19using VirtualFile = std::shared_ptr<VfsFile>;
20using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
21
22struct FileTimeStampRaw {
23 u64 created{};
24 u64 accessed{};
25 u64 modified{};
26 u64 padding{};
27};
28
29} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <utility>
6#include "core/file_sys/vfs/vfs_vector.h"
7
8namespace FileSys {
9VectorVfsFile::VectorVfsFile(std::vector<u8> initial_data, std::string name_, VirtualDir parent_)
10 : data(std::move(initial_data)), parent(std::move(parent_)), name(std::move(name_)) {}
11
12VectorVfsFile::~VectorVfsFile() = default;
13
14std::string VectorVfsFile::GetName() const {
15 return name;
16}
17
18size_t VectorVfsFile::GetSize() const {
19 return data.size();
20}
21
22bool VectorVfsFile::Resize(size_t new_size) {
23 data.resize(new_size);
24 return true;
25}
26
27VirtualDir VectorVfsFile::GetContainingDirectory() const {
28 return parent;
29}
30
31bool VectorVfsFile::IsWritable() const {
32 return true;
33}
34
35bool VectorVfsFile::IsReadable() const {
36 return true;
37}
38
39std::size_t VectorVfsFile::Read(u8* data_, std::size_t length, std::size_t offset) const {
40 const auto read = std::min(length, data.size() - offset);
41 std::memcpy(data_, data.data() + offset, read);
42 return read;
43}
44
45std::size_t VectorVfsFile::Write(const u8* data_, std::size_t length, std::size_t offset) {
46 if (offset + length > data.size())
47 data.resize(offset + length);
48 const auto write = std::min(length, data.size() - offset);
49 std::memcpy(data.data() + offset, data_, write);
50 return write;
51}
52
53bool VectorVfsFile::Rename(std::string_view name_) {
54 name = name_;
55 return true;
56}
57
58void VectorVfsFile::Assign(std::vector<u8> new_data) {
59 data = std::move(new_data);
60}
61
62VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
63 std::vector<VirtualDir> dirs_, std::string name_,
64 VirtualDir parent_)
65 : files(std::move(files_)), dirs(std::move(dirs_)), parent(std::move(parent_)),
66 name(std::move(name_)) {}
67
68VectorVfsDirectory::~VectorVfsDirectory() = default;
69
70std::vector<VirtualFile> VectorVfsDirectory::GetFiles() const {
71 return files;
72}
73
74std::vector<VirtualDir> VectorVfsDirectory::GetSubdirectories() const {
75 return dirs;
76}
77
78bool VectorVfsDirectory::IsWritable() const {
79 return false;
80}
81
82bool VectorVfsDirectory::IsReadable() const {
83 return true;
84}
85
86std::string VectorVfsDirectory::GetName() const {
87 return name;
88}
89
90VirtualDir VectorVfsDirectory::GetParentDirectory() const {
91 return parent;
92}
93
94template <typename T>
95static bool FindAndRemoveVectorElement(std::vector<T>& vec, std::string_view name) {
96 const auto iter =
97 std::find_if(vec.begin(), vec.end(), [name](const T& e) { return e->GetName() == name; });
98 if (iter == vec.end())
99 return false;
100
101 vec.erase(iter);
102 return true;
103}
104
105bool VectorVfsDirectory::DeleteSubdirectory(std::string_view subdir_name) {
106 return FindAndRemoveVectorElement(dirs, subdir_name);
107}
108
109bool VectorVfsDirectory::DeleteFile(std::string_view file_name) {
110 return FindAndRemoveVectorElement(files, file_name);
111}
112
113bool VectorVfsDirectory::Rename(std::string_view name_) {
114 name = name_;
115 return true;
116}
117
118VirtualDir VectorVfsDirectory::CreateSubdirectory(std::string_view subdir_name) {
119 return nullptr;
120}
121
122VirtualFile VectorVfsDirectory::CreateFile(std::string_view file_name) {
123 return nullptr;
124}
125
126void VectorVfsDirectory::AddFile(VirtualFile file) {
127 files.push_back(std::move(file));
128}
129
130void VectorVfsDirectory::AddDirectory(VirtualDir dir) {
131 dirs.push_back(std::move(dir));
132}
133} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <cstring>
8#include <memory>
9#include <string>
10#include <vector>
11#include "core/file_sys/vfs/vfs.h"
12
13namespace FileSys {
14
15// An implementation of VfsFile that is backed by a statically-sized array
16template <std::size_t size>
17class ArrayVfsFile : public VfsFile {
18public:
19 explicit ArrayVfsFile(const std::array<u8, size>& data_, std::string name_ = "",
20 VirtualDir parent_ = nullptr)
21 : data(data_), name(std::move(name_)), parent(std::move(parent_)) {}
22
23 std::string GetName() const override {
24 return name;
25 }
26
27 std::size_t GetSize() const override {
28 return size;
29 }
30
31 bool Resize(std::size_t new_size) override {
32 return false;
33 }
34
35 VirtualDir GetContainingDirectory() const override {
36 return parent;
37 }
38
39 bool IsWritable() const override {
40 return false;
41 }
42
43 bool IsReadable() const override {
44 return true;
45 }
46
47 std::size_t Read(u8* data_, std::size_t length, std::size_t offset) const override {
48 const auto read = std::min(length, size - offset);
49 std::memcpy(data_, data.data() + offset, read);
50 return read;
51 }
52
53 std::size_t Write(const u8* data_, std::size_t length, std::size_t offset) override {
54 return 0;
55 }
56
57 bool Rename(std::string_view new_name) override {
58 name = new_name;
59 return true;
60 }
61
62private:
63 std::array<u8, size> data;
64 std::string name;
65 VirtualDir parent;
66};
67
68template <std::size_t Size, typename... Args>
69std::shared_ptr<ArrayVfsFile<Size>> MakeArrayFile(const std::array<u8, Size>& data,
70 Args&&... args) {
71 return std::make_shared<ArrayVfsFile<Size>>(data, std::forward<Args>(args)...);
72}
73
74// An implementation of VfsFile that is backed by a vector optionally supplied upon construction
75class VectorVfsFile : public VfsFile {
76public:
77 explicit VectorVfsFile(std::vector<u8> initial_data = {}, std::string name_ = "",
78 VirtualDir parent_ = nullptr);
79 ~VectorVfsFile() override;
80
81 std::string GetName() const override;
82 std::size_t GetSize() const override;
83 bool Resize(std::size_t new_size) override;
84 VirtualDir GetContainingDirectory() const override;
85 bool IsWritable() const override;
86 bool IsReadable() const override;
87 std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
88 std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
89 bool Rename(std::string_view name) override;
90
91 virtual void Assign(std::vector<u8> new_data);
92
93private:
94 std::vector<u8> data;
95 VirtualDir parent;
96 std::string name;
97};
98
99// An implementation of VfsDirectory that maintains two vectors for subdirectories and files.
100// Vector data is supplied upon construction.
101class VectorVfsDirectory : public VfsDirectory {
102public:
103 explicit VectorVfsDirectory(std::vector<VirtualFile> files = {},
104 std::vector<VirtualDir> dirs = {}, std::string name = "",
105 VirtualDir parent = nullptr);
106 ~VectorVfsDirectory() override;
107
108 std::vector<VirtualFile> GetFiles() const override;
109 std::vector<VirtualDir> GetSubdirectories() const override;
110 bool IsWritable() const override;
111 bool IsReadable() const override;
112 std::string GetName() const override;
113 VirtualDir GetParentDirectory() const override;
114 bool DeleteSubdirectory(std::string_view subdir_name) override;
115 bool DeleteFile(std::string_view file_name) override;
116 bool Rename(std::string_view name) override;
117 VirtualDir CreateSubdirectory(std::string_view subdir_name) override;
118 VirtualFile CreateFile(std::string_view file_name) override;
119
120 virtual void AddFile(VirtualFile file);
121 virtual void AddDirectory(VirtualDir dir);
122
123private:
124 std::vector<VirtualFile> files;
125 std::vector<VirtualDir> dirs;
126
127 VirtualDir parent;
128 std::string name;
129};
130
131} // namespace FileSys