diff options
| author | 2024-01-26 09:55:25 -0500 | |
|---|---|---|
| committer | 2024-01-26 09:55:25 -0500 | |
| commit | 55482ab5dce463d5014498b006c18a90d0d004e6 (patch) | |
| tree | b343faa9cadc692265efb4b6b88e157c97ef76d2 /src/core/file_sys/vfs | |
| parent | Merge pull request #12796 from t895/controller-optimizations (diff) | |
| parent | Address review comments and fix compilation problems (diff) | |
| download | yuzu-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.cpp | 551 | ||||
| -rw-r--r-- | src/core/file_sys/vfs/vfs.h | 326 | ||||
| -rw-r--r-- | src/core/file_sys/vfs/vfs_cached.cpp | 63 | ||||
| -rw-r--r-- | src/core/file_sys/vfs/vfs_cached.h | 31 | ||||
| -rw-r--r-- | src/core/file_sys/vfs/vfs_concat.cpp | 192 | ||||
| -rw-r--r-- | src/core/file_sys/vfs/vfs_concat.h | 57 | ||||
| -rw-r--r-- | src/core/file_sys/vfs/vfs_layered.cpp | 132 | ||||
| -rw-r--r-- | src/core/file_sys/vfs/vfs_layered.h | 46 | ||||
| -rw-r--r-- | src/core/file_sys/vfs/vfs_offset.cpp | 98 | ||||
| -rw-r--r-- | src/core/file_sys/vfs/vfs_offset.h | 50 | ||||
| -rw-r--r-- | src/core/file_sys/vfs/vfs_real.cpp | 527 | ||||
| -rw-r--r-- | src/core/file_sys/vfs/vfs_real.h | 148 | ||||
| -rw-r--r-- | src/core/file_sys/vfs/vfs_static.h | 80 | ||||
| -rw-r--r-- | src/core/file_sys/vfs/vfs_types.h | 29 | ||||
| -rw-r--r-- | src/core/file_sys/vfs/vfs_vector.cpp | 133 | ||||
| -rw-r--r-- | src/core/file_sys/vfs/vfs_vector.h | 131 |
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 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | VfsFilesystem::VfsFilesystem(VirtualDir root_) : root(std::move(root_)) {} | ||
| 13 | |||
| 14 | VfsFilesystem::~VfsFilesystem() = default; | ||
| 15 | |||
| 16 | std::string VfsFilesystem::GetName() const { | ||
| 17 | return root->GetName(); | ||
| 18 | } | ||
| 19 | |||
| 20 | bool VfsFilesystem::IsReadable() const { | ||
| 21 | return root->IsReadable(); | ||
| 22 | } | ||
| 23 | |||
| 24 | bool VfsFilesystem::IsWritable() const { | ||
| 25 | return root->IsWritable(); | ||
| 26 | } | ||
| 27 | |||
| 28 | VfsEntryType 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 | |||
| 38 | VirtualFile VfsFilesystem::OpenFile(std::string_view path_, OpenMode perms) { | ||
| 39 | const auto path = Common::FS::SanitizePath(path_); | ||
| 40 | return root->GetFileRelative(path); | ||
| 41 | } | ||
| 42 | |||
| 43 | VirtualFile VfsFilesystem::CreateFile(std::string_view path_, OpenMode perms) { | ||
| 44 | const auto path = Common::FS::SanitizePath(path_); | ||
| 45 | return root->CreateFileRelative(path); | ||
| 46 | } | ||
| 47 | |||
| 48 | VirtualFile 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 | |||
| 74 | VirtualFile 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 | |||
| 87 | bool 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 | |||
| 95 | VirtualDir VfsFilesystem::OpenDirectory(std::string_view path_, OpenMode perms) { | ||
| 96 | const auto path = Common::FS::SanitizePath(path_); | ||
| 97 | return root->GetDirectoryRelative(path); | ||
| 98 | } | ||
| 99 | |||
| 100 | VirtualDir VfsFilesystem::CreateDirectory(std::string_view path_, OpenMode perms) { | ||
| 101 | const auto path = Common::FS::SanitizePath(path_); | ||
| 102 | return root->CreateDirectoryRelative(path); | ||
| 103 | } | ||
| 104 | |||
| 105 | VirtualDir 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 | |||
| 136 | VirtualDir 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 | |||
| 149 | bool 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 | |||
| 157 | VfsFile::~VfsFile() = default; | ||
| 158 | |||
| 159 | std::string VfsFile::GetExtension() const { | ||
| 160 | return std::string(Common::FS::GetExtensionFromFilename(GetName())); | ||
| 161 | } | ||
| 162 | |||
| 163 | VfsDirectory::~VfsDirectory() = default; | ||
| 164 | |||
| 165 | std::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 | |||
| 175 | std::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 | |||
| 182 | std::vector<u8> VfsFile::ReadAllBytes() const { | ||
| 183 | return ReadBytes(GetSize()); | ||
| 184 | } | ||
| 185 | |||
| 186 | bool VfsFile::WriteByte(u8 data, std::size_t offset) { | ||
| 187 | return Write(&data, 1, offset) == 1; | ||
| 188 | } | ||
| 189 | |||
| 190 | std::size_t VfsFile::WriteBytes(const std::vector<u8>& data, std::size_t offset) { | ||
| 191 | return Write(data.data(), data.size(), offset); | ||
| 192 | } | ||
| 193 | |||
| 194 | std::string VfsFile::GetFullPath() const { | ||
| 195 | if (GetContainingDirectory() == nullptr) | ||
| 196 | return '/' + GetName(); | ||
| 197 | |||
| 198 | return GetContainingDirectory()->GetFullPath() + '/' + GetName(); | ||
| 199 | } | ||
| 200 | |||
| 201 | VirtualFile 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 | |||
| 227 | VirtualFile VfsDirectory::GetFileAbsolute(std::string_view path) const { | ||
| 228 | if (IsRoot()) { | ||
| 229 | return GetFileRelative(path); | ||
| 230 | } | ||
| 231 | |||
| 232 | return GetParentDirectory()->GetFileAbsolute(path); | ||
| 233 | } | ||
| 234 | |||
| 235 | VirtualDir 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 | |||
| 255 | VirtualDir VfsDirectory::GetDirectoryAbsolute(std::string_view path) const { | ||
| 256 | if (IsRoot()) { | ||
| 257 | return GetDirectoryRelative(path); | ||
| 258 | } | ||
| 259 | |||
| 260 | return GetParentDirectory()->GetDirectoryAbsolute(path); | ||
| 261 | } | ||
| 262 | |||
| 263 | VirtualFile 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 | |||
| 270 | FileTimeStampRaw VfsDirectory::GetFileTimeStamp([[maybe_unused]] std::string_view path) const { | ||
| 271 | return {}; | ||
| 272 | } | ||
| 273 | |||
| 274 | VirtualDir 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 | |||
| 281 | bool VfsDirectory::IsRoot() const { | ||
| 282 | return GetParentDirectory() == nullptr; | ||
| 283 | } | ||
| 284 | |||
| 285 | std::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 | |||
| 299 | VirtualFile 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 | |||
| 320 | VirtualFile VfsDirectory::CreateFileAbsolute(std::string_view path) { | ||
| 321 | if (IsRoot()) { | ||
| 322 | return CreateFileRelative(path); | ||
| 323 | } | ||
| 324 | |||
| 325 | return GetParentDirectory()->CreateFileAbsolute(path); | ||
| 326 | } | ||
| 327 | |||
| 328 | VirtualDir 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 | |||
| 349 | VirtualDir VfsDirectory::CreateDirectoryAbsolute(std::string_view path) { | ||
| 350 | if (IsRoot()) { | ||
| 351 | return CreateDirectoryRelative(path); | ||
| 352 | } | ||
| 353 | |||
| 354 | return GetParentDirectory()->CreateDirectoryAbsolute(path); | ||
| 355 | } | ||
| 356 | |||
| 357 | bool 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 | |||
| 379 | bool 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 | |||
| 401 | bool 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 | |||
| 416 | std::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 | |||
| 425 | std::string VfsDirectory::GetFullPath() const { | ||
| 426 | if (IsRoot()) | ||
| 427 | return GetName(); | ||
| 428 | |||
| 429 | return GetParentDirectory()->GetFullPath() + '/' + GetName(); | ||
| 430 | } | ||
| 431 | |||
| 432 | bool ReadOnlyVfsDirectory::IsWritable() const { | ||
| 433 | return false; | ||
| 434 | } | ||
| 435 | |||
| 436 | bool ReadOnlyVfsDirectory::IsReadable() const { | ||
| 437 | return true; | ||
| 438 | } | ||
| 439 | |||
| 440 | VirtualDir ReadOnlyVfsDirectory::CreateSubdirectory(std::string_view name) { | ||
| 441 | return nullptr; | ||
| 442 | } | ||
| 443 | |||
| 444 | VirtualFile ReadOnlyVfsDirectory::CreateFile(std::string_view name) { | ||
| 445 | return nullptr; | ||
| 446 | } | ||
| 447 | |||
| 448 | VirtualFile ReadOnlyVfsDirectory::CreateFileAbsolute(std::string_view path) { | ||
| 449 | return nullptr; | ||
| 450 | } | ||
| 451 | |||
| 452 | VirtualFile ReadOnlyVfsDirectory::CreateFileRelative(std::string_view path) { | ||
| 453 | return nullptr; | ||
| 454 | } | ||
| 455 | |||
| 456 | VirtualDir ReadOnlyVfsDirectory::CreateDirectoryAbsolute(std::string_view path) { | ||
| 457 | return nullptr; | ||
| 458 | } | ||
| 459 | |||
| 460 | VirtualDir ReadOnlyVfsDirectory::CreateDirectoryRelative(std::string_view path) { | ||
| 461 | return nullptr; | ||
| 462 | } | ||
| 463 | |||
| 464 | bool ReadOnlyVfsDirectory::DeleteSubdirectory(std::string_view name) { | ||
| 465 | return false; | ||
| 466 | } | ||
| 467 | |||
| 468 | bool ReadOnlyVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) { | ||
| 469 | return false; | ||
| 470 | } | ||
| 471 | |||
| 472 | bool ReadOnlyVfsDirectory::CleanSubdirectoryRecursive(std::string_view name) { | ||
| 473 | return false; | ||
| 474 | } | ||
| 475 | |||
| 476 | bool ReadOnlyVfsDirectory::DeleteFile(std::string_view name) { | ||
| 477 | return false; | ||
| 478 | } | ||
| 479 | |||
| 480 | bool ReadOnlyVfsDirectory::Rename(std::string_view name) { | ||
| 481 | return false; | ||
| 482 | } | ||
| 483 | |||
| 484 | bool 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 | |||
| 504 | bool 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 | |||
| 526 | bool 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 | |||
| 545 | VirtualDir 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 | |||
| 19 | namespace FileSys { | ||
| 20 | |||
| 21 | // An enumeration representing what can be at the end of a path in a VfsFilesystem | ||
| 22 | enum 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. | ||
| 31 | class VfsFilesystem { | ||
| 32 | public: | ||
| 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 | |||
| 76 | protected: | ||
| 77 | // Root directory in default implementation. | ||
| 78 | VirtualDir root; | ||
| 79 | }; | ||
| 80 | |||
| 81 | // A class representing a file in an abstract filesystem. | ||
| 82 | class VfsFile { | ||
| 83 | public: | ||
| 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. | ||
| 185 | class VfsDirectory { | ||
| 186 | public: | ||
| 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. | ||
| 291 | class ReadOnlyVfsDirectory : public VfsDirectory { | ||
| 292 | public: | ||
| 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 | ||
| 309 | bool 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. | ||
| 315 | bool 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. | ||
| 320 | bool 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. | ||
| 324 | VirtualDir 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 | |||
| 7 | namespace FileSys { | ||
| 8 | |||
| 9 | CachedVfsDirectory::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 | |||
| 19 | CachedVfsDirectory::~CachedVfsDirectory() = default; | ||
| 20 | |||
| 21 | VirtualFile 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 | |||
| 30 | VirtualDir 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 | |||
| 39 | std::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 | |||
| 47 | std::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 | |||
| 55 | std::string CachedVfsDirectory::GetName() const { | ||
| 56 | return name; | ||
| 57 | } | ||
| 58 | |||
| 59 | VirtualDir 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 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | class CachedVfsDirectory : public ReadOnlyVfsDirectory { | ||
| 13 | public: | ||
| 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 | |||
| 24 | private: | ||
| 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 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | ConcatenatedVfsFile::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 | |||
| 18 | bool 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 | |||
| 31 | ConcatenatedVfsFile::~ConcatenatedVfsFile() = default; | ||
| 32 | |||
| 33 | VirtualFile 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 | |||
| 62 | VirtualFile 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 | |||
| 100 | std::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 | |||
| 110 | std::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 | |||
| 117 | bool ConcatenatedVfsFile::Resize(std::size_t new_size) { | ||
| 118 | return false; | ||
| 119 | } | ||
| 120 | |||
| 121 | VirtualDir ConcatenatedVfsFile::GetContainingDirectory() const { | ||
| 122 | if (concatenation_map.empty()) { | ||
| 123 | return nullptr; | ||
| 124 | } | ||
| 125 | return concatenation_map.front().file->GetContainingDirectory(); | ||
| 126 | } | ||
| 127 | |||
| 128 | bool ConcatenatedVfsFile::IsWritable() const { | ||
| 129 | return false; | ||
| 130 | } | ||
| 131 | |||
| 132 | bool ConcatenatedVfsFile::IsReadable() const { | ||
| 133 | return true; | ||
| 134 | } | ||
| 135 | |||
| 136 | std::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 | |||
| 184 | std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { | ||
| 185 | return 0; | ||
| 186 | } | ||
| 187 | |||
| 188 | bool 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 | |||
| 11 | namespace FileSys { | ||
| 12 | |||
| 13 | // Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently | ||
| 14 | // read-only. | ||
| 15 | class ConcatenatedVfsFile : public VfsFile { | ||
| 16 | private: | ||
| 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 | |||
| 31 | public: | ||
| 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 | |||
| 52 | private: | ||
| 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 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | LayeredVfsDirectory::LayeredVfsDirectory(std::vector<VirtualDir> dirs_, std::string name_) | ||
| 13 | : dirs(std::move(dirs_)), name(std::move(name_)) {} | ||
| 14 | |||
| 15 | LayeredVfsDirectory::~LayeredVfsDirectory() = default; | ||
| 16 | |||
| 17 | VirtualDir 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 | |||
| 27 | VirtualFile 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 | |||
| 37 | VirtualDir 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 | |||
| 49 | VirtualFile LayeredVfsDirectory::GetFile(std::string_view file_name) const { | ||
| 50 | return GetFileRelative(file_name); | ||
| 51 | } | ||
| 52 | |||
| 53 | VirtualDir LayeredVfsDirectory::GetSubdirectory(std::string_view subdir_name) const { | ||
| 54 | return GetDirectoryRelative(subdir_name); | ||
| 55 | } | ||
| 56 | |||
| 57 | std::string LayeredVfsDirectory::GetFullPath() const { | ||
| 58 | return dirs[0]->GetFullPath(); | ||
| 59 | } | ||
| 60 | |||
| 61 | std::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 | |||
| 77 | std::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 | |||
| 95 | bool LayeredVfsDirectory::IsWritable() const { | ||
| 96 | return false; | ||
| 97 | } | ||
| 98 | |||
| 99 | bool LayeredVfsDirectory::IsReadable() const { | ||
| 100 | return true; | ||
| 101 | } | ||
| 102 | |||
| 103 | std::string LayeredVfsDirectory::GetName() const { | ||
| 104 | return name.empty() ? dirs[0]->GetName() : name; | ||
| 105 | } | ||
| 106 | |||
| 107 | VirtualDir LayeredVfsDirectory::GetParentDirectory() const { | ||
| 108 | return dirs[0]->GetParentDirectory(); | ||
| 109 | } | ||
| 110 | |||
| 111 | VirtualDir LayeredVfsDirectory::CreateSubdirectory(std::string_view subdir_name) { | ||
| 112 | return nullptr; | ||
| 113 | } | ||
| 114 | |||
| 115 | VirtualFile LayeredVfsDirectory::CreateFile(std::string_view file_name) { | ||
| 116 | return nullptr; | ||
| 117 | } | ||
| 118 | |||
| 119 | bool LayeredVfsDirectory::DeleteSubdirectory(std::string_view subdir_name) { | ||
| 120 | return false; | ||
| 121 | } | ||
| 122 | |||
| 123 | bool LayeredVfsDirectory::DeleteFile(std::string_view file_name) { | ||
| 124 | return false; | ||
| 125 | } | ||
| 126 | |||
| 127 | bool 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 | |||
| 9 | namespace 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. | ||
| 14 | class LayeredVfsDirectory : public VfsDirectory { | ||
| 15 | explicit LayeredVfsDirectory(std::vector<VirtualDir> dirs_, std::string name_); | ||
| 16 | |||
| 17 | public: | ||
| 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 | |||
| 41 | private: | ||
| 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 | |||
| 9 | namespace FileSys { | ||
| 10 | |||
| 11 | OffsetVfsFile::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 | |||
| 16 | OffsetVfsFile::~OffsetVfsFile() = default; | ||
| 17 | |||
| 18 | std::string OffsetVfsFile::GetName() const { | ||
| 19 | return name.empty() ? file->GetName() : name; | ||
| 20 | } | ||
| 21 | |||
| 22 | std::size_t OffsetVfsFile::GetSize() const { | ||
| 23 | return size; | ||
| 24 | } | ||
| 25 | |||
| 26 | bool 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 | |||
| 39 | VirtualDir OffsetVfsFile::GetContainingDirectory() const { | ||
| 40 | return parent; | ||
| 41 | } | ||
| 42 | |||
| 43 | bool OffsetVfsFile::IsWritable() const { | ||
| 44 | return file->IsWritable(); | ||
| 45 | } | ||
| 46 | |||
| 47 | bool OffsetVfsFile::IsReadable() const { | ||
| 48 | return file->IsReadable(); | ||
| 49 | } | ||
| 50 | |||
| 51 | std::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 | |||
| 55 | std::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 | |||
| 59 | std::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 | |||
| 67 | std::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 | |||
| 71 | std::vector<u8> OffsetVfsFile::ReadAllBytes() const { | ||
| 72 | return file->ReadBytes(size, offset); | ||
| 73 | } | ||
| 74 | |||
| 75 | bool 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 | |||
| 82 | std::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 | |||
| 86 | bool OffsetVfsFile::Rename(std::string_view new_name) { | ||
| 87 | return file->Rename(new_name); | ||
| 88 | } | ||
| 89 | |||
| 90 | std::size_t OffsetVfsFile::GetOffset() const { | ||
| 91 | return offset; | ||
| 92 | } | ||
| 93 | |||
| 94 | std::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 | |||
| 10 | namespace 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. | ||
| 16 | class OffsetVfsFile : public VfsFile { | ||
| 17 | public: | ||
| 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 | |||
| 40 | private: | ||
| 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 | |||
| 23 | namespace FileSys { | ||
| 24 | |||
| 25 | namespace FS = Common::FS; | ||
| 26 | |||
| 27 | namespace { | ||
| 28 | |||
| 29 | constexpr size_t MaxOpenFiles = 512; | ||
| 30 | |||
| 31 | constexpr 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 | |||
| 47 | RealVfsFilesystem::RealVfsFilesystem() : VfsFilesystem(nullptr) {} | ||
| 48 | RealVfsFilesystem::~RealVfsFilesystem() = default; | ||
| 49 | |||
| 50 | std::string RealVfsFilesystem::GetName() const { | ||
| 51 | return "Real"; | ||
| 52 | } | ||
| 53 | |||
| 54 | bool RealVfsFilesystem::IsReadable() const { | ||
| 55 | return true; | ||
| 56 | } | ||
| 57 | |||
| 58 | bool RealVfsFilesystem::IsWritable() const { | ||
| 59 | return true; | ||
| 60 | } | ||
| 61 | |||
| 62 | VfsEntryType 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 | |||
| 74 | VirtualFile 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 | |||
| 99 | VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, OpenMode perms) { | ||
| 100 | return OpenFileFromEntry(path_, {}, perms); | ||
| 101 | } | ||
| 102 | |||
| 103 | VirtualFile 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 | |||
| 130 | VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_view new_path_) { | ||
| 131 | // Unused | ||
| 132 | return nullptr; | ||
| 133 | } | ||
| 134 | |||
| 135 | VirtualFile 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 | |||
| 149 | bool 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 | |||
| 158 | VirtualDir 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 | |||
| 163 | VirtualDir 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 | |||
| 171 | VirtualDir RealVfsFilesystem::CopyDirectory(std::string_view old_path_, | ||
| 172 | std::string_view new_path_) { | ||
| 173 | // Unused | ||
| 174 | return nullptr; | ||
| 175 | } | ||
| 176 | |||
| 177 | VirtualDir 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 | |||
| 188 | bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) { | ||
| 189 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | ||
| 190 | return FS::RemoveDirRecursively(path); | ||
| 191 | } | ||
| 192 | |||
| 193 | std::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 | |||
| 218 | void 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 | |||
| 231 | void 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 | |||
| 250 | void 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 | |||
| 258 | void 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 | |||
| 266 | RealVfsFile::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 | |||
| 272 | RealVfsFile::~RealVfsFile() { | ||
| 273 | base.DropReference(std::move(reference)); | ||
| 274 | } | ||
| 275 | |||
| 276 | std::string RealVfsFile::GetName() const { | ||
| 277 | return path_components.empty() ? "" : std::string(path_components.back()); | ||
| 278 | } | ||
| 279 | |||
| 280 | std::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 | |||
| 288 | bool 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 | |||
| 294 | VirtualDir RealVfsFile::GetContainingDirectory() const { | ||
| 295 | return base.OpenDirectory(parent_path, perms); | ||
| 296 | } | ||
| 297 | |||
| 298 | bool RealVfsFile::IsWritable() const { | ||
| 299 | return True(perms & OpenMode::Write); | ||
| 300 | } | ||
| 301 | |||
| 302 | bool RealVfsFile::IsReadable() const { | ||
| 303 | return True(perms & OpenMode::Read); | ||
| 304 | } | ||
| 305 | |||
| 306 | std::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 | |||
| 314 | std::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 | |||
| 323 | bool 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 | |||
| 330 | template <> | ||
| 331 | std::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 | |||
| 352 | template <> | ||
| 353 | std::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 | |||
| 374 | RealVfsDirectory::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 | |||
| 383 | RealVfsDirectory::~RealVfsDirectory() = default; | ||
| 384 | |||
| 385 | VirtualFile 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 | |||
| 393 | VirtualDir 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 | |||
| 401 | VirtualFile RealVfsDirectory::GetFile(std::string_view name) const { | ||
| 402 | return GetFileRelative(name); | ||
| 403 | } | ||
| 404 | |||
| 405 | VirtualDir RealVfsDirectory::GetSubdirectory(std::string_view name) const { | ||
| 406 | return GetDirectoryRelative(name); | ||
| 407 | } | ||
| 408 | |||
| 409 | VirtualFile 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 | |||
| 417 | VirtualDir 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 | |||
| 422 | bool 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 | |||
| 427 | std::vector<VirtualFile> RealVfsDirectory::GetFiles() const { | ||
| 428 | return IterateEntries<RealVfsFile, VfsFile>(); | ||
| 429 | } | ||
| 430 | |||
| 431 | FileTimeStampRaw 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 | |||
| 453 | std::vector<VirtualDir> RealVfsDirectory::GetSubdirectories() const { | ||
| 454 | return IterateEntries<RealVfsDirectory, VfsDirectory>(); | ||
| 455 | } | ||
| 456 | |||
| 457 | bool RealVfsDirectory::IsWritable() const { | ||
| 458 | return True(perms & OpenMode::Write); | ||
| 459 | } | ||
| 460 | |||
| 461 | bool RealVfsDirectory::IsReadable() const { | ||
| 462 | return True(perms & OpenMode::Read); | ||
| 463 | } | ||
| 464 | |||
| 465 | std::string RealVfsDirectory::GetName() const { | ||
| 466 | return path_components.empty() ? "" : std::string(path_components.back()); | ||
| 467 | } | ||
| 468 | |||
| 469 | VirtualDir RealVfsDirectory::GetParentDirectory() const { | ||
| 470 | if (path_components.size() <= 1) { | ||
| 471 | return nullptr; | ||
| 472 | } | ||
| 473 | |||
| 474 | return base.OpenDirectory(parent_path, perms); | ||
| 475 | } | ||
| 476 | |||
| 477 | VirtualDir RealVfsDirectory::CreateSubdirectory(std::string_view name) { | ||
| 478 | const std::string subdir_path = (path + '/').append(name); | ||
| 479 | return base.CreateDirectory(subdir_path, perms); | ||
| 480 | } | ||
| 481 | |||
| 482 | VirtualFile RealVfsDirectory::CreateFile(std::string_view name) { | ||
| 483 | const std::string file_path = (path + '/').append(name); | ||
| 484 | return base.CreateFile(file_path, perms); | ||
| 485 | } | ||
| 486 | |||
| 487 | bool RealVfsDirectory::DeleteSubdirectory(std::string_view name) { | ||
| 488 | const std::string subdir_path = (path + '/').append(name); | ||
| 489 | return base.DeleteDirectory(subdir_path); | ||
| 490 | } | ||
| 491 | |||
| 492 | bool RealVfsDirectory::DeleteFile(std::string_view name) { | ||
| 493 | const std::string file_path = (path + '/').append(name); | ||
| 494 | return base.DeleteFile(file_path); | ||
| 495 | } | ||
| 496 | |||
| 497 | bool 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 | |||
| 502 | std::string RealVfsDirectory::GetFullPath() const { | ||
| 503 | auto out = path; | ||
| 504 | std::replace(out.begin(), out.end(), '\\', '/'); | ||
| 505 | return out; | ||
| 506 | } | ||
| 507 | |||
| 508 | std::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 | |||
| 14 | namespace Common::FS { | ||
| 15 | class IOFile; | ||
| 16 | } | ||
| 17 | |||
| 18 | namespace FileSys { | ||
| 19 | |||
| 20 | struct FileReference : public Common::IntrusiveListBaseNode<FileReference> { | ||
| 21 | std::shared_ptr<Common::FS::IOFile> file{}; | ||
| 22 | }; | ||
| 23 | |||
| 24 | class RealVfsFile; | ||
| 25 | class RealVfsDirectory; | ||
| 26 | |||
| 27 | class RealVfsFilesystem : public VfsFilesystem { | ||
| 28 | public: | ||
| 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 | |||
| 48 | private: | ||
| 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 | |||
| 56 | private: | ||
| 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 | |||
| 62 | private: | ||
| 63 | friend class RealVfsDirectory; | ||
| 64 | VirtualFile OpenFileFromEntry(std::string_view path, std::optional<u64> size, | ||
| 65 | OpenMode perms = OpenMode::Read); | ||
| 66 | |||
| 67 | private: | ||
| 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. | ||
| 74 | class RealVfsFile : public VfsFile { | ||
| 75 | friend class RealVfsDirectory; | ||
| 76 | friend class RealVfsFilesystem; | ||
| 77 | |||
| 78 | public: | ||
| 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 | |||
| 91 | private: | ||
| 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. | ||
| 106 | class RealVfsDirectory : public VfsDirectory { | ||
| 107 | friend class RealVfsFilesystem; | ||
| 108 | |||
| 109 | public: | ||
| 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 | |||
| 134 | private: | ||
| 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 | |||
| 12 | namespace FileSys { | ||
| 13 | |||
| 14 | class StaticVfsFile : public VfsFile { | ||
| 15 | public: | ||
| 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 | |||
| 73 | private: | ||
| 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 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | class VfsDirectory; | ||
| 13 | class VfsFile; | ||
| 14 | class VfsFilesystem; | ||
| 15 | |||
| 16 | // Declarations for Vfs* pointer types | ||
| 17 | |||
| 18 | using VirtualDir = std::shared_ptr<VfsDirectory>; | ||
| 19 | using VirtualFile = std::shared_ptr<VfsFile>; | ||
| 20 | using VirtualFilesystem = std::shared_ptr<VfsFilesystem>; | ||
| 21 | |||
| 22 | struct 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 | |||
| 8 | namespace FileSys { | ||
| 9 | VectorVfsFile::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 | |||
| 12 | VectorVfsFile::~VectorVfsFile() = default; | ||
| 13 | |||
| 14 | std::string VectorVfsFile::GetName() const { | ||
| 15 | return name; | ||
| 16 | } | ||
| 17 | |||
| 18 | size_t VectorVfsFile::GetSize() const { | ||
| 19 | return data.size(); | ||
| 20 | } | ||
| 21 | |||
| 22 | bool VectorVfsFile::Resize(size_t new_size) { | ||
| 23 | data.resize(new_size); | ||
| 24 | return true; | ||
| 25 | } | ||
| 26 | |||
| 27 | VirtualDir VectorVfsFile::GetContainingDirectory() const { | ||
| 28 | return parent; | ||
| 29 | } | ||
| 30 | |||
| 31 | bool VectorVfsFile::IsWritable() const { | ||
| 32 | return true; | ||
| 33 | } | ||
| 34 | |||
| 35 | bool VectorVfsFile::IsReadable() const { | ||
| 36 | return true; | ||
| 37 | } | ||
| 38 | |||
| 39 | std::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 | |||
| 45 | std::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 | |||
| 53 | bool VectorVfsFile::Rename(std::string_view name_) { | ||
| 54 | name = name_; | ||
| 55 | return true; | ||
| 56 | } | ||
| 57 | |||
| 58 | void VectorVfsFile::Assign(std::vector<u8> new_data) { | ||
| 59 | data = std::move(new_data); | ||
| 60 | } | ||
| 61 | |||
| 62 | VectorVfsDirectory::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 | |||
| 68 | VectorVfsDirectory::~VectorVfsDirectory() = default; | ||
| 69 | |||
| 70 | std::vector<VirtualFile> VectorVfsDirectory::GetFiles() const { | ||
| 71 | return files; | ||
| 72 | } | ||
| 73 | |||
| 74 | std::vector<VirtualDir> VectorVfsDirectory::GetSubdirectories() const { | ||
| 75 | return dirs; | ||
| 76 | } | ||
| 77 | |||
| 78 | bool VectorVfsDirectory::IsWritable() const { | ||
| 79 | return false; | ||
| 80 | } | ||
| 81 | |||
| 82 | bool VectorVfsDirectory::IsReadable() const { | ||
| 83 | return true; | ||
| 84 | } | ||
| 85 | |||
| 86 | std::string VectorVfsDirectory::GetName() const { | ||
| 87 | return name; | ||
| 88 | } | ||
| 89 | |||
| 90 | VirtualDir VectorVfsDirectory::GetParentDirectory() const { | ||
| 91 | return parent; | ||
| 92 | } | ||
| 93 | |||
| 94 | template <typename T> | ||
| 95 | static 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 | |||
| 105 | bool VectorVfsDirectory::DeleteSubdirectory(std::string_view subdir_name) { | ||
| 106 | return FindAndRemoveVectorElement(dirs, subdir_name); | ||
| 107 | } | ||
| 108 | |||
| 109 | bool VectorVfsDirectory::DeleteFile(std::string_view file_name) { | ||
| 110 | return FindAndRemoveVectorElement(files, file_name); | ||
| 111 | } | ||
| 112 | |||
| 113 | bool VectorVfsDirectory::Rename(std::string_view name_) { | ||
| 114 | name = name_; | ||
| 115 | return true; | ||
| 116 | } | ||
| 117 | |||
| 118 | VirtualDir VectorVfsDirectory::CreateSubdirectory(std::string_view subdir_name) { | ||
| 119 | return nullptr; | ||
| 120 | } | ||
| 121 | |||
| 122 | VirtualFile VectorVfsDirectory::CreateFile(std::string_view file_name) { | ||
| 123 | return nullptr; | ||
| 124 | } | ||
| 125 | |||
| 126 | void VectorVfsDirectory::AddFile(VirtualFile file) { | ||
| 127 | files.push_back(std::move(file)); | ||
| 128 | } | ||
| 129 | |||
| 130 | void 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 | |||
| 13 | namespace FileSys { | ||
| 14 | |||
| 15 | // An implementation of VfsFile that is backed by a statically-sized array | ||
| 16 | template <std::size_t size> | ||
| 17 | class ArrayVfsFile : public VfsFile { | ||
| 18 | public: | ||
| 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 | |||
| 62 | private: | ||
| 63 | std::array<u8, size> data; | ||
| 64 | std::string name; | ||
| 65 | VirtualDir parent; | ||
| 66 | }; | ||
| 67 | |||
| 68 | template <std::size_t Size, typename... Args> | ||
| 69 | std::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 | ||
| 75 | class VectorVfsFile : public VfsFile { | ||
| 76 | public: | ||
| 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 | |||
| 93 | private: | ||
| 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. | ||
| 101 | class VectorVfsDirectory : public VfsDirectory { | ||
| 102 | public: | ||
| 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 | |||
| 123 | private: | ||
| 124 | std::vector<VirtualFile> files; | ||
| 125 | std::vector<VirtualDir> dirs; | ||
| 126 | |||
| 127 | VirtualDir parent; | ||
| 128 | std::string name; | ||
| 129 | }; | ||
| 130 | |||
| 131 | } // namespace FileSys | ||