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