summaryrefslogtreecommitdiff
path: root/src/core/file_sys
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/file_sys')
-rw-r--r--src/core/file_sys/content_archive.cpp167
-rw-r--r--src/core/file_sys/content_archive.h95
-rw-r--r--src/core/file_sys/directory.h6
-rw-r--r--src/core/file_sys/disk_filesystem.cpp239
-rw-r--r--src/core/file_sys/disk_filesystem.h84
-rw-r--r--src/core/file_sys/errors.h2
-rw-r--r--src/core/file_sys/filesystem.cpp122
-rw-r--r--src/core/file_sys/filesystem.h170
-rw-r--r--src/core/file_sys/mode.h17
-rw-r--r--src/core/file_sys/partition_filesystem.cpp136
-rw-r--r--src/core/file_sys/partition_filesystem.h29
-rw-r--r--src/core/file_sys/path_parser.cpp98
-rw-r--r--src/core/file_sys/path_parser.h61
-rw-r--r--src/core/file_sys/program_metadata.cpp43
-rw-r--r--src/core/file_sys/program_metadata.h6
-rw-r--r--src/core/file_sys/romfs_factory.cpp8
-rw-r--r--src/core/file_sys/romfs_factory.h9
-rw-r--r--src/core/file_sys/romfs_filesystem.cpp110
-rw-r--r--src/core/file_sys/romfs_filesystem.h85
-rw-r--r--src/core/file_sys/savedata_factory.cpp34
-rw-r--r--src/core/file_sys/savedata_factory.h9
-rw-r--r--src/core/file_sys/sdmc_factory.cpp16
-rw-r--r--src/core/file_sys/sdmc_factory.h10
-rw-r--r--src/core/file_sys/storage.h63
-rw-r--r--src/core/file_sys/vfs.cpp238
-rw-r--r--src/core/file_sys/vfs.h237
-rw-r--r--src/core/file_sys/vfs_offset.cpp92
-rw-r--r--src/core/file_sys/vfs_offset.h46
-rw-r--r--src/core/file_sys/vfs_real.cpp177
-rw-r--r--src/core/file_sys/vfs_real.h69
30 files changed, 1267 insertions, 1211 deletions
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
new file mode 100644
index 000000000..6cfef774d
--- /dev/null
+++ b/src/core/file_sys/content_archive.cpp
@@ -0,0 +1,167 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/logging/log.h"
6#include "core/file_sys/content_archive.h"
7#include "core/file_sys/vfs_offset.h"
8#include "core/loader/loader.h"
9
10namespace FileSys {
11
12// Media offsets in headers are stored divided by 512. Mult. by this to get real offset.
13constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200;
14
15constexpr u64 SECTION_HEADER_SIZE = 0x200;
16constexpr u64 SECTION_HEADER_OFFSET = 0x400;
17
18constexpr u32 IVFC_MAX_LEVEL = 6;
19
20enum class NCASectionFilesystemType : u8 {
21 PFS0 = 0x2,
22 ROMFS = 0x3,
23};
24
25struct NCASectionHeaderBlock {
26 INSERT_PADDING_BYTES(3);
27 NCASectionFilesystemType filesystem_type;
28 u8 crypto_type;
29 INSERT_PADDING_BYTES(3);
30};
31static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size.");
32
33struct PFS0Superblock {
34 NCASectionHeaderBlock header_block;
35 std::array<u8, 0x20> hash;
36 u32_le size;
37 INSERT_PADDING_BYTES(4);
38 u64_le hash_table_offset;
39 u64_le hash_table_size;
40 u64_le pfs0_header_offset;
41 u64_le pfs0_size;
42 INSERT_PADDING_BYTES(432);
43};
44static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size.");
45
46struct IVFCLevel {
47 u64_le offset;
48 u64_le size;
49 u32_le block_size;
50 u32_le reserved;
51};
52static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size.");
53
54struct RomFSSuperblock {
55 NCASectionHeaderBlock header_block;
56 u32_le magic;
57 u32_le magic_number;
58 INSERT_PADDING_BYTES(8);
59 std::array<IVFCLevel, 6> levels;
60 INSERT_PADDING_BYTES(64);
61};
62static_assert(sizeof(RomFSSuperblock) == 0xE8, "RomFSSuperblock has incorrect size.");
63
64NCA::NCA(VirtualFile file_) : file(file_) {
65 if (sizeof(NCAHeader) != file->ReadObject(&header))
66 LOG_CRITICAL(Loader, "File reader errored out during header read.");
67
68 if (!IsValidNCA(header)) {
69 status = Loader::ResultStatus::ErrorInvalidFormat;
70 return;
71 }
72
73 std::ptrdiff_t number_sections =
74 std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
75 [](NCASectionTableEntry entry) { return entry.media_offset > 0; });
76
77 for (std::ptrdiff_t i = 0; i < number_sections; ++i) {
78 // Seek to beginning of this section.
79 NCASectionHeaderBlock block{};
80 if (sizeof(NCASectionHeaderBlock) !=
81 file->ReadObject(&block, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE))
82 LOG_CRITICAL(Loader, "File reader errored out during header read.");
83
84 if (block.filesystem_type == NCASectionFilesystemType::ROMFS) {
85 RomFSSuperblock sb{};
86 if (sizeof(RomFSSuperblock) !=
87 file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE))
88 LOG_CRITICAL(Loader, "File reader errored out during header read.");
89
90 const size_t romfs_offset =
91 header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER +
92 sb.levels[IVFC_MAX_LEVEL - 1].offset;
93 const size_t romfs_size = sb.levels[IVFC_MAX_LEVEL - 1].size;
94 files.emplace_back(std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset));
95 romfs = files.back();
96 } else if (block.filesystem_type == NCASectionFilesystemType::PFS0) {
97 PFS0Superblock sb{};
98 // Seek back to beginning of this section.
99 if (sizeof(PFS0Superblock) !=
100 file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE))
101 LOG_CRITICAL(Loader, "File reader errored out during header read.");
102
103 u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
104 MEDIA_OFFSET_MULTIPLIER) +
105 sb.pfs0_header_offset;
106 u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
107 header.section_tables[i].media_offset);
108 auto npfs = std::make_shared<PartitionFilesystem>(
109 std::make_shared<OffsetVfsFile>(file, size, offset));
110
111 if (npfs->GetStatus() == Loader::ResultStatus::Success) {
112 dirs.emplace_back(npfs);
113 if (IsDirectoryExeFS(dirs.back()))
114 exefs = dirs.back();
115 }
116 }
117 }
118
119 status = Loader::ResultStatus::Success;
120}
121
122Loader::ResultStatus NCA::GetStatus() const {
123 return status;
124}
125
126std::vector<std::shared_ptr<VfsFile>> NCA::GetFiles() const {
127 if (status != Loader::ResultStatus::Success)
128 return {};
129 return files;
130}
131
132std::vector<std::shared_ptr<VfsDirectory>> NCA::GetSubdirectories() const {
133 if (status != Loader::ResultStatus::Success)
134 return {};
135 return dirs;
136}
137
138std::string NCA::GetName() const {
139 return file->GetName();
140}
141
142std::shared_ptr<VfsDirectory> NCA::GetParentDirectory() const {
143 return file->GetContainingDirectory();
144}
145
146NCAContentType NCA::GetType() const {
147 return header.content_type;
148}
149
150u64 NCA::GetTitleId() const {
151 if (status != Loader::ResultStatus::Success)
152 return {};
153 return header.title_id;
154}
155
156VirtualFile NCA::GetRomFS() const {
157 return romfs;
158}
159
160VirtualDir NCA::GetExeFS() const {
161 return exefs;
162}
163
164bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
165 return false;
166}
167} // namespace FileSys
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
new file mode 100644
index 000000000..129a70b97
--- /dev/null
+++ b/src/core/file_sys/content_archive.h
@@ -0,0 +1,95 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "common/common_funcs.h"
8#include "common/common_types.h"
9#include "common/swap.h"
10#include "core/file_sys/partition_filesystem.h"
11
12namespace FileSys {
13
14enum class NCAContentType : u8 {
15 Program = 0,
16 Meta = 1,
17 Control = 2,
18 Manual = 3,
19 Data = 4,
20};
21
22struct NCASectionTableEntry {
23 u32_le media_offset;
24 u32_le media_end_offset;
25 INSERT_PADDING_BYTES(0x8);
26};
27static_assert(sizeof(NCASectionTableEntry) == 0x10, "NCASectionTableEntry has incorrect size.");
28
29struct NCAHeader {
30 std::array<u8, 0x100> rsa_signature_1;
31 std::array<u8, 0x100> rsa_signature_2;
32 u32_le magic;
33 u8 is_system;
34 NCAContentType content_type;
35 u8 crypto_type;
36 u8 key_index;
37 u64_le size;
38 u64_le title_id;
39 INSERT_PADDING_BYTES(0x4);
40 u32_le sdk_version;
41 u8 crypto_type_2;
42 INSERT_PADDING_BYTES(15);
43 std::array<u8, 0x10> rights_id;
44 std::array<NCASectionTableEntry, 0x4> section_tables;
45 std::array<std::array<u8, 0x20>, 0x4> hash_tables;
46 std::array<std::array<u8, 0x10>, 0x4> key_area;
47 INSERT_PADDING_BYTES(0xC0);
48};
49static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size.");
50
51inline bool IsDirectoryExeFS(std::shared_ptr<FileSys::VfsDirectory> pfs) {
52 // According to switchbrew, an exefs must only contain these two files:
53 return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
54}
55
56inline bool IsValidNCA(const NCAHeader& header) {
57 return header.magic == Common::MakeMagic('N', 'C', 'A', '2') ||
58 header.magic == Common::MakeMagic('N', 'C', 'A', '3');
59}
60
61// An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner.
62// After construction, use GetStatus to determine if the file is valid and ready to be used.
63class NCA : public ReadOnlyVfsDirectory {
64public:
65 explicit NCA(VirtualFile file);
66 Loader::ResultStatus GetStatus() const;
67
68 std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
69 std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
70 std::string GetName() const override;
71 std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
72
73 NCAContentType GetType() const;
74 u64 GetTitleId() const;
75
76 VirtualFile GetRomFS() const;
77 VirtualDir GetExeFS() const;
78
79protected:
80 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
81
82private:
83 std::vector<VirtualDir> dirs;
84 std::vector<VirtualFile> files;
85
86 VirtualFile romfs = nullptr;
87 VirtualDir exefs = nullptr;
88 VirtualFile file;
89
90 NCAHeader header{};
91
92 Loader::ResultStatus status{};
93};
94
95} // namespace FileSys
diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h
index c7639795e..213ce1826 100644
--- a/src/core/file_sys/directory.h
+++ b/src/core/file_sys/directory.h
@@ -8,13 +8,17 @@
8#include <cstddef> 8#include <cstddef>
9#include "common/common_funcs.h" 9#include "common/common_funcs.h"
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "core/file_sys/filesystem.h"
12 11
13//////////////////////////////////////////////////////////////////////////////////////////////////// 12////////////////////////////////////////////////////////////////////////////////////////////////////
14// FileSys namespace 13// FileSys namespace
15 14
16namespace FileSys { 15namespace FileSys {
17 16
17enum EntryType : u8 {
18 Directory = 0,
19 File = 1,
20};
21
18// Structure of a directory entry, from 22// Structure of a directory entry, from
19// http://switchbrew.org/index.php?title=Filesystem_services#DirectoryEntry 23// http://switchbrew.org/index.php?title=Filesystem_services#DirectoryEntry
20const size_t FILENAME_LENGTH = 0x300; 24const size_t FILENAME_LENGTH = 0x300;
diff --git a/src/core/file_sys/disk_filesystem.cpp b/src/core/file_sys/disk_filesystem.cpp
deleted file mode 100644
index d248c2df4..000000000
--- a/src/core/file_sys/disk_filesystem.cpp
+++ /dev/null
@@ -1,239 +0,0 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <cstring>
6#include <memory>
7#include "common/common_types.h"
8#include "common/logging/log.h"
9#include "core/file_sys/disk_filesystem.h"
10#include "core/file_sys/errors.h"
11
12namespace FileSys {
13
14static std::string ModeFlagsToString(Mode mode) {
15 std::string mode_str;
16 u32 mode_flags = static_cast<u32>(mode);
17
18 // Calculate the correct open mode for the file.
19 if ((mode_flags & static_cast<u32>(Mode::Read)) &&
20 (mode_flags & static_cast<u32>(Mode::Write))) {
21 if (mode_flags & static_cast<u32>(Mode::Append))
22 mode_str = "a+";
23 else
24 mode_str = "r+";
25 } else {
26 if (mode_flags & static_cast<u32>(Mode::Read))
27 mode_str = "r";
28 else if (mode_flags & static_cast<u32>(Mode::Append))
29 mode_str = "a";
30 else if (mode_flags & static_cast<u32>(Mode::Write))
31 mode_str = "w";
32 }
33
34 mode_str += "b";
35
36 return mode_str;
37}
38
39std::string Disk_FileSystem::GetName() const {
40 return "Disk";
41}
42
43ResultVal<std::unique_ptr<StorageBackend>> Disk_FileSystem::OpenFile(const std::string& path,
44 Mode mode) const {
45
46 // Calculate the correct open mode for the file.
47 std::string mode_str = ModeFlagsToString(mode);
48
49 std::string full_path = base_directory + path;
50 auto file = std::make_shared<FileUtil::IOFile>(full_path, mode_str.c_str());
51
52 if (!file->IsOpen()) {
53 return ERROR_PATH_NOT_FOUND;
54 }
55
56 return MakeResult<std::unique_ptr<StorageBackend>>(
57 std::make_unique<Disk_Storage>(std::move(file)));
58}
59
60ResultCode Disk_FileSystem::DeleteFile(const std::string& path) const {
61 std::string full_path = base_directory + path;
62
63 if (!FileUtil::Exists(full_path)) {
64 return ERROR_PATH_NOT_FOUND;
65 }
66
67 FileUtil::Delete(full_path);
68
69 return RESULT_SUCCESS;
70}
71
72ResultCode Disk_FileSystem::RenameFile(const std::string& src_path,
73 const std::string& dest_path) const {
74 const std::string full_src_path = base_directory + src_path;
75 const std::string full_dest_path = base_directory + dest_path;
76
77 if (!FileUtil::Exists(full_src_path)) {
78 return ERROR_PATH_NOT_FOUND;
79 }
80 // TODO(wwylele): Use correct error code
81 return FileUtil::Rename(full_src_path, full_dest_path) ? RESULT_SUCCESS : ResultCode(-1);
82}
83
84ResultCode Disk_FileSystem::DeleteDirectory(const Path& path) const {
85 LOG_WARNING(Service_FS, "(STUBBED) called");
86 // TODO(wwylele): Use correct error code
87 return ResultCode(-1);
88}
89
90ResultCode Disk_FileSystem::DeleteDirectoryRecursively(const Path& path) const {
91 LOG_WARNING(Service_FS, "(STUBBED) called");
92 // TODO(wwylele): Use correct error code
93 return ResultCode(-1);
94}
95
96ResultCode Disk_FileSystem::CreateFile(const std::string& path, u64 size) const {
97 LOG_WARNING(Service_FS, "(STUBBED) called");
98
99 std::string full_path = base_directory + path;
100 if (size == 0) {
101 FileUtil::CreateEmptyFile(full_path);
102 return RESULT_SUCCESS;
103 }
104
105 FileUtil::IOFile file(full_path, "wb");
106 // Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
107 // We do this by seeking to the right size, then writing a single null byte.
108 if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) {
109 return RESULT_SUCCESS;
110 }
111
112 LOG_ERROR(Service_FS, "Too large file");
113 // TODO(Subv): Find out the correct error code
114 return ResultCode(-1);
115}
116
117ResultCode Disk_FileSystem::CreateDirectory(const std::string& path) const {
118 // TODO(Subv): Perform path validation to prevent escaping the emulator sandbox.
119 std::string full_path = base_directory + path;
120
121 if (FileUtil::CreateDir(full_path)) {
122 return RESULT_SUCCESS;
123 }
124
125 LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating {}", full_path);
126 // TODO(wwylele): Use correct error code
127 return ResultCode(-1);
128}
129
130ResultCode Disk_FileSystem::RenameDirectory(const Path& src_path, const Path& dest_path) const {
131 LOG_WARNING(Service_FS, "(STUBBED) called");
132 // TODO(wwylele): Use correct error code
133 return ResultCode(-1);
134}
135
136ResultVal<std::unique_ptr<DirectoryBackend>> Disk_FileSystem::OpenDirectory(
137 const std::string& path) const {
138
139 std::string full_path = base_directory + path;
140
141 if (!FileUtil::IsDirectory(full_path)) {
142 // TODO(Subv): Find the correct error code for this.
143 return ResultCode(-1);
144 }
145
146 auto directory = std::make_unique<Disk_Directory>(full_path);
147 return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory));
148}
149
150u64 Disk_FileSystem::GetFreeSpaceSize() const {
151 LOG_WARNING(Service_FS, "(STUBBED) called");
152 return 0;
153}
154
155ResultVal<FileSys::EntryType> Disk_FileSystem::GetEntryType(const std::string& path) const {
156 std::string full_path = base_directory + path;
157 if (!FileUtil::Exists(full_path)) {
158 return ERROR_PATH_NOT_FOUND;
159 }
160
161 if (FileUtil::IsDirectory(full_path))
162 return MakeResult(EntryType::Directory);
163
164 return MakeResult(EntryType::File);
165}
166
167ResultVal<size_t> Disk_Storage::Read(const u64 offset, const size_t length, u8* buffer) const {
168 LOG_TRACE(Service_FS, "called offset={}, length={}", offset, length);
169 file->Seek(offset, SEEK_SET);
170 return MakeResult<size_t>(file->ReadBytes(buffer, length));
171}
172
173ResultVal<size_t> Disk_Storage::Write(const u64 offset, const size_t length, const bool flush,
174 const u8* buffer) const {
175 LOG_WARNING(Service_FS, "(STUBBED) called");
176 file->Seek(offset, SEEK_SET);
177 size_t written = file->WriteBytes(buffer, length);
178 if (flush) {
179 file->Flush();
180 }
181 return MakeResult<size_t>(written);
182}
183
184u64 Disk_Storage::GetSize() const {
185 return file->GetSize();
186}
187
188bool Disk_Storage::SetSize(const u64 size) const {
189 file->Resize(size);
190 file->Flush();
191 return true;
192}
193
194Disk_Directory::Disk_Directory(const std::string& path) {
195 unsigned size = FileUtil::ScanDirectoryTree(path, directory);
196 directory.size = size;
197 directory.isDirectory = true;
198 children_iterator = directory.children.begin();
199}
200
201u64 Disk_Directory::Read(const u64 count, Entry* entries) {
202 u64 entries_read = 0;
203
204 while (entries_read < count && children_iterator != directory.children.cend()) {
205 const FileUtil::FSTEntry& file = *children_iterator;
206 const std::string& filename = file.virtualName;
207 Entry& entry = entries[entries_read];
208
209 LOG_TRACE(Service_FS, "File {}: size={} dir={}", filename, file.size, file.isDirectory);
210
211 // TODO(Link Mauve): use a proper conversion to UTF-16.
212 for (size_t j = 0; j < FILENAME_LENGTH; ++j) {
213 entry.filename[j] = filename[j];
214 if (!filename[j])
215 break;
216 }
217
218 if (file.isDirectory) {
219 entry.file_size = 0;
220 entry.type = EntryType::Directory;
221 } else {
222 entry.file_size = file.size;
223 entry.type = EntryType::File;
224 }
225
226 ++entries_read;
227 ++children_iterator;
228 }
229 return entries_read;
230}
231
232u64 Disk_Directory::GetEntryCount() const {
233 // We convert the children iterator into a const_iterator to allow template argument deduction
234 // in std::distance.
235 std::vector<FileUtil::FSTEntry>::const_iterator current = children_iterator;
236 return std::distance(current, directory.children.end());
237}
238
239} // namespace FileSys
diff --git a/src/core/file_sys/disk_filesystem.h b/src/core/file_sys/disk_filesystem.h
deleted file mode 100644
index 591e39fda..000000000
--- a/src/core/file_sys/disk_filesystem.h
+++ /dev/null
@@ -1,84 +0,0 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <cstddef>
8#include <memory>
9#include <string>
10#include "common/common_types.h"
11#include "common/file_util.h"
12#include "core/file_sys/directory.h"
13#include "core/file_sys/filesystem.h"
14#include "core/file_sys/storage.h"
15#include "core/hle/result.h"
16
17namespace FileSys {
18
19class Disk_FileSystem : public FileSystemBackend {
20public:
21 explicit Disk_FileSystem(std::string base_directory)
22 : base_directory(std::move(base_directory)) {}
23
24 std::string GetName() const override;
25
26 ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path,
27 Mode mode) const override;
28 ResultCode DeleteFile(const std::string& path) const override;
29 ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const override;
30 ResultCode DeleteDirectory(const Path& path) const override;
31 ResultCode DeleteDirectoryRecursively(const Path& path) const override;
32 ResultCode CreateFile(const std::string& path, u64 size) const override;
33 ResultCode CreateDirectory(const std::string& path) const override;
34 ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
35 ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
36 const std::string& path) const override;
37 u64 GetFreeSpaceSize() const override;
38 ResultVal<EntryType> GetEntryType(const std::string& path) const override;
39
40protected:
41 std::string base_directory;
42};
43
44class Disk_Storage : public StorageBackend {
45public:
46 explicit Disk_Storage(std::shared_ptr<FileUtil::IOFile> file) : file(std::move(file)) {}
47
48 ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
49 ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
50 u64 GetSize() const override;
51 bool SetSize(u64 size) const override;
52 bool Close() const override {
53 return false;
54 }
55 void Flush() const override {}
56
57private:
58 std::shared_ptr<FileUtil::IOFile> file;
59};
60
61class Disk_Directory : public DirectoryBackend {
62public:
63 explicit Disk_Directory(const std::string& path);
64
65 ~Disk_Directory() override {
66 Close();
67 }
68
69 u64 Read(const u64 count, Entry* entries) override;
70 u64 GetEntryCount() const override;
71
72 bool Close() const override {
73 return true;
74 }
75
76protected:
77 FileUtil::FSTEntry directory;
78
79 // We need to remember the last entry we returned, so a subsequent call to Read will continue
80 // from the next one. This iterator will always point to the next unread entry.
81 std::vector<FileUtil::FSTEntry>::iterator children_iterator;
82};
83
84} // namespace FileSys
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h
index 1f3b8fa84..a152dbd33 100644
--- a/src/core/file_sys/errors.h
+++ b/src/core/file_sys/errors.h
@@ -11,7 +11,7 @@ namespace FileSys {
11namespace ErrCodes { 11namespace ErrCodes {
12enum { 12enum {
13 NotFound = 1, 13 NotFound = 1,
14 SaveDataNotFound = 1002, 14 TitleNotFound = 1002,
15 SdCardNotFound = 2001, 15 SdCardNotFound = 2001,
16 RomFSNotFound = 2520, 16 RomFSNotFound = 2520,
17}; 17};
diff --git a/src/core/file_sys/filesystem.cpp b/src/core/file_sys/filesystem.cpp
deleted file mode 100644
index 82fdb3c46..000000000
--- a/src/core/file_sys/filesystem.cpp
+++ /dev/null
@@ -1,122 +0,0 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <cstddef>
6#include <iomanip>
7#include <sstream>
8#include "common/logging/log.h"
9#include "common/string_util.h"
10#include "core/file_sys/filesystem.h"
11#include "core/memory.h"
12
13namespace FileSys {
14
15Path::Path(LowPathType type, u32 size, u32 pointer) : type(type) {
16 switch (type) {
17 case Binary: {
18 binary.resize(size);
19 Memory::ReadBlock(pointer, binary.data(), binary.size());
20 break;
21 }
22
23 case Char: {
24 string.resize(size - 1); // Data is always null-terminated.
25 Memory::ReadBlock(pointer, &string[0], string.size());
26 break;
27 }
28
29 case Wchar: {
30 u16str.resize(size / 2 - 1); // Data is always null-terminated.
31 Memory::ReadBlock(pointer, &u16str[0], u16str.size() * sizeof(char16_t));
32 break;
33 }
34
35 default:
36 break;
37 }
38}
39
40std::string Path::DebugStr() const {
41 switch (GetType()) {
42 case Invalid:
43 default:
44 return "[Invalid]";
45 case Empty:
46 return "[Empty]";
47 case Binary: {
48 std::stringstream res;
49 res << "[Binary: ";
50 for (unsigned byte : binary)
51 res << std::hex << std::setw(2) << std::setfill('0') << byte;
52 res << ']';
53 return res.str();
54 }
55 case Char:
56 return "[Char: " + AsString() + ']';
57 case Wchar:
58 return "[Wchar: " + AsString() + ']';
59 }
60}
61
62std::string Path::AsString() const {
63 switch (GetType()) {
64 case Char:
65 return string;
66 case Wchar:
67 return Common::UTF16ToUTF8(u16str);
68 case Empty:
69 return {};
70 case Invalid:
71 case Binary:
72 default:
73 // TODO(yuriks): Add assert
74 LOG_ERROR(Service_FS, "LowPathType cannot be converted to string!");
75 return {};
76 }
77}
78
79std::u16string Path::AsU16Str() const {
80 switch (GetType()) {
81 case Char:
82 return Common::UTF8ToUTF16(string);
83 case Wchar:
84 return u16str;
85 case Empty:
86 return {};
87 case Invalid:
88 case Binary:
89 // TODO(yuriks): Add assert
90 LOG_ERROR(Service_FS, "LowPathType cannot be converted to u16string!");
91 return {};
92 }
93
94 UNREACHABLE();
95}
96
97std::vector<u8> Path::AsBinary() const {
98 switch (GetType()) {
99 case Binary:
100 return binary;
101 case Char:
102 return std::vector<u8>(string.begin(), string.end());
103 case Wchar: {
104 // use two u8 for each character of u16str
105 std::vector<u8> to_return(u16str.size() * 2);
106 for (size_t i = 0; i < u16str.size(); ++i) {
107 u16 tmp_char = u16str.at(i);
108 to_return[i * 2] = (tmp_char & 0xFF00) >> 8;
109 to_return[i * 2 + 1] = (tmp_char & 0x00FF);
110 }
111 return to_return;
112 }
113 case Empty:
114 return {};
115 case Invalid:
116 default:
117 // TODO(yuriks): Add assert
118 LOG_ERROR(Service_FS, "LowPathType cannot be converted to binary!");
119 return {};
120 }
121}
122} // namespace FileSys
diff --git a/src/core/file_sys/filesystem.h b/src/core/file_sys/filesystem.h
deleted file mode 100644
index 1a32a373b..000000000
--- a/src/core/file_sys/filesystem.h
+++ /dev/null
@@ -1,170 +0,0 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <string>
9#include <utility>
10#include <vector>
11#include "common/bit_field.h"
12#include "common/common_types.h"
13#include "common/swap.h"
14#include "core/hle/result.h"
15
16namespace FileSys {
17
18class StorageBackend;
19class DirectoryBackend;
20
21// Path string type
22enum LowPathType : u32 {
23 Invalid = 0,
24 Empty = 1,
25 Binary = 2,
26 Char = 3,
27 Wchar = 4,
28};
29
30enum EntryType : u8 {
31 Directory = 0,
32 File = 1,
33};
34
35enum class Mode : u32 {
36 Read = 1,
37 Write = 2,
38 Append = 4,
39};
40
41class Path {
42public:
43 Path() : type(Invalid) {}
44 Path(const char* path) : type(Char), string(path) {}
45 Path(std::vector<u8> binary_data) : type(Binary), binary(std::move(binary_data)) {}
46 Path(LowPathType type, u32 size, u32 pointer);
47
48 LowPathType GetType() const {
49 return type;
50 }
51
52 /**
53 * Gets the string representation of the path for debugging
54 * @return String representation of the path for debugging
55 */
56 std::string DebugStr() const;
57
58 std::string AsString() const;
59 std::u16string AsU16Str() const;
60 std::vector<u8> AsBinary() const;
61
62private:
63 LowPathType type;
64 std::vector<u8> binary;
65 std::string string;
66 std::u16string u16str;
67};
68
69/// Parameters of the archive, as specified in the Create or Format call.
70struct ArchiveFormatInfo {
71 u32_le total_size; ///< The pre-defined size of the archive.
72 u32_le number_directories; ///< The pre-defined number of directories in the archive.
73 u32_le number_files; ///< The pre-defined number of files in the archive.
74 u8 duplicate_data; ///< Whether the archive should duplicate the data.
75};
76static_assert(std::is_pod<ArchiveFormatInfo>::value, "ArchiveFormatInfo is not POD");
77
78class FileSystemBackend : NonCopyable {
79public:
80 virtual ~FileSystemBackend() {}
81
82 /**
83 * Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.)
84 */
85 virtual std::string GetName() const = 0;
86
87 /**
88 * Create a file specified by its path
89 * @param path Path relative to the Archive
90 * @param size The size of the new file, filled with zeroes
91 * @return Result of the operation
92 */
93 virtual ResultCode CreateFile(const std::string& path, u64 size) const = 0;
94
95 /**
96 * Delete a file specified by its path
97 * @param path Path relative to the archive
98 * @return Result of the operation
99 */
100 virtual ResultCode DeleteFile(const std::string& path) const = 0;
101
102 /**
103 * Create a directory specified by its path
104 * @param path Path relative to the archive
105 * @return Result of the operation
106 */
107 virtual ResultCode CreateDirectory(const std::string& path) const = 0;
108
109 /**
110 * Delete a directory specified by its path
111 * @param path Path relative to the archive
112 * @return Result of the operation
113 */
114 virtual ResultCode DeleteDirectory(const Path& path) const = 0;
115
116 /**
117 * Delete a directory specified by its path and anything under it
118 * @param path Path relative to the archive
119 * @return Result of the operation
120 */
121 virtual ResultCode DeleteDirectoryRecursively(const Path& path) const = 0;
122
123 /**
124 * Rename a File specified by its path
125 * @param src_path Source path relative to the archive
126 * @param dest_path Destination path relative to the archive
127 * @return Result of the operation
128 */
129 virtual ResultCode RenameFile(const std::string& src_path,
130 const std::string& dest_path) const = 0;
131
132 /**
133 * Rename a Directory specified by its path
134 * @param src_path Source path relative to the archive
135 * @param dest_path Destination path relative to the archive
136 * @return Result of the operation
137 */
138 virtual ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const = 0;
139
140 /**
141 * Open a file specified by its path, using the specified mode
142 * @param path Path relative to the archive
143 * @param mode Mode to open the file with
144 * @return Opened file, or error code
145 */
146 virtual ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path,
147 Mode mode) const = 0;
148
149 /**
150 * Open a directory specified by its path
151 * @param path Path relative to the archive
152 * @return Opened directory, or error code
153 */
154 virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
155 const std::string& path) const = 0;
156
157 /**
158 * Get the free space
159 * @return The number of free bytes in the archive
160 */
161 virtual u64 GetFreeSpaceSize() const = 0;
162
163 /**
164 * Get the type of the specified path
165 * @return The type of the specified path or error code
166 */
167 virtual ResultVal<EntryType> GetEntryType(const std::string& path) const = 0;
168};
169
170} // namespace FileSys
diff --git a/src/core/file_sys/mode.h b/src/core/file_sys/mode.h
new file mode 100644
index 000000000..b4363152a
--- /dev/null
+++ b/src/core/file_sys/mode.h
@@ -0,0 +1,17 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "common/common_types.h"
8
9namespace FileSys {
10
11enum class Mode : u32 {
12 Read = 1,
13 Write = 2,
14 Append = 4,
15};
16
17} // namespace FileSys
diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp
index 46d438aca..15b1fb946 100644
--- a/src/core/file_sys/partition_filesystem.cpp
+++ b/src/core/file_sys/partition_filesystem.cpp
@@ -6,29 +6,30 @@
6#include "common/file_util.h" 6#include "common/file_util.h"
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8#include "core/file_sys/partition_filesystem.h" 8#include "core/file_sys/partition_filesystem.h"
9#include "core/file_sys/vfs_offset.h"
9#include "core/loader/loader.h" 10#include "core/loader/loader.h"
10 11
11namespace FileSys { 12namespace FileSys {
12 13
13Loader::ResultStatus PartitionFilesystem::Load(const std::string& file_path, size_t offset) { 14PartitionFilesystem::PartitionFilesystem(std::shared_ptr<VfsFile> file) {
14 FileUtil::IOFile file(file_path, "rb");
15 if (!file.IsOpen())
16 return Loader::ResultStatus::Error;
17
18 // At least be as large as the header 15 // At least be as large as the header
19 if (file.GetSize() < sizeof(Header)) 16 if (file->GetSize() < sizeof(Header)) {
20 return Loader::ResultStatus::Error; 17 status = Loader::ResultStatus::Error;
18 return;
19 }
21 20
22 file.Seek(offset, SEEK_SET);
23 // For cartridges, HFSs can get very large, so we need to calculate the size up to 21 // For cartridges, HFSs can get very large, so we need to calculate the size up to
24 // the actual content itself instead of just blindly reading in the entire file. 22 // the actual content itself instead of just blindly reading in the entire file.
25 Header pfs_header; 23 Header pfs_header;
26 if (!file.ReadBytes(&pfs_header, sizeof(Header))) 24 if (sizeof(Header) != file->ReadObject(&pfs_header)) {
27 return Loader::ResultStatus::Error; 25 status = Loader::ResultStatus::Error;
26 return;
27 }
28 28
29 if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') && 29 if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') &&
30 pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) { 30 pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) {
31 return Loader::ResultStatus::ErrorInvalidFormat; 31 status = Loader::ResultStatus::ErrorInvalidFormat;
32 return;
32 } 33 }
33 34
34 bool is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0'); 35 bool is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0');
@@ -38,99 +39,86 @@ Loader::ResultStatus PartitionFilesystem::Load(const std::string& file_path, siz
38 sizeof(Header) + (pfs_header.num_entries * entry_size) + pfs_header.strtab_size; 39 sizeof(Header) + (pfs_header.num_entries * entry_size) + pfs_header.strtab_size;
39 40
40 // Actually read in now... 41 // Actually read in now...
41 file.Seek(offset, SEEK_SET); 42 std::vector<u8> file_data = file->ReadBytes(metadata_size);
42 std::vector<u8> file_data(metadata_size);
43
44 if (!file.ReadBytes(file_data.data(), metadata_size))
45 return Loader::ResultStatus::Error;
46 43
47 Loader::ResultStatus result = Load(file_data); 44 if (file_data.size() != metadata_size) {
48 if (result != Loader::ResultStatus::Success) 45 status = Loader::ResultStatus::Error;
49 LOG_ERROR(Service_FS, "Failed to load PFS from file {}!", file_path); 46 return;
50 47 }
51 return result;
52}
53 48
54Loader::ResultStatus PartitionFilesystem::Load(const std::vector<u8>& file_data, size_t offset) { 49 size_t total_size = file_data.size();
55 size_t total_size = file_data.size() - offset; 50 if (total_size < sizeof(Header)) {
56 if (total_size < sizeof(Header)) 51 status = Loader::ResultStatus::Error;
57 return Loader::ResultStatus::Error; 52 return;
53 }
58 54
59 memcpy(&pfs_header, &file_data[offset], sizeof(Header)); 55 memcpy(&pfs_header, file_data.data(), sizeof(Header));
60 if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') && 56 if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') &&
61 pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) { 57 pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) {
62 return Loader::ResultStatus::ErrorInvalidFormat; 58 status = Loader::ResultStatus::ErrorInvalidFormat;
59 return;
63 } 60 }
64 61
65 is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0'); 62 is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0');
66 63
67 size_t entries_offset = offset + sizeof(Header); 64 size_t entries_offset = sizeof(Header);
68 size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry);
69 size_t strtab_offset = entries_offset + (pfs_header.num_entries * entry_size); 65 size_t strtab_offset = entries_offset + (pfs_header.num_entries * entry_size);
66 content_offset = strtab_offset + pfs_header.strtab_size;
70 for (u16 i = 0; i < pfs_header.num_entries; i++) { 67 for (u16 i = 0; i < pfs_header.num_entries; i++) {
71 FileEntry entry; 68 FSEntry entry;
72 69
73 memcpy(&entry.fs_entry, &file_data[entries_offset + (i * entry_size)], sizeof(FSEntry)); 70 memcpy(&entry, &file_data[entries_offset + (i * entry_size)], sizeof(FSEntry));
74 entry.name = std::string(reinterpret_cast<const char*>( 71 std::string name(
75 &file_data[strtab_offset + entry.fs_entry.strtab_offset])); 72 reinterpret_cast<const char*>(&file_data[strtab_offset + entry.strtab_offset]));
76 pfs_entries.push_back(std::move(entry));
77 }
78 73
79 content_offset = strtab_offset + pfs_header.strtab_size; 74 pfs_files.emplace_back(
75 std::make_shared<OffsetVfsFile>(file, entry.size, content_offset + entry.offset, name));
76 }
80 77
81 return Loader::ResultStatus::Success; 78 status = Loader::ResultStatus::Success;
82} 79}
83 80
84u32 PartitionFilesystem::GetNumEntries() const { 81Loader::ResultStatus PartitionFilesystem::GetStatus() const {
85 return pfs_header.num_entries; 82 return status;
86} 83}
87 84
88u64 PartitionFilesystem::GetEntryOffset(u32 index) const { 85std::vector<std::shared_ptr<VfsFile>> PartitionFilesystem::GetFiles() const {
89 if (index > GetNumEntries()) 86 return pfs_files;
90 return 0;
91
92 return content_offset + pfs_entries[index].fs_entry.offset;
93} 87}
94 88
95u64 PartitionFilesystem::GetEntrySize(u32 index) const { 89std::vector<std::shared_ptr<VfsDirectory>> PartitionFilesystem::GetSubdirectories() const {
96 if (index > GetNumEntries()) 90 return {};
97 return 0;
98
99 return pfs_entries[index].fs_entry.size;
100} 91}
101 92
102std::string PartitionFilesystem::GetEntryName(u32 index) const { 93std::string PartitionFilesystem::GetName() const {
103 if (index > GetNumEntries()) 94 return is_hfs ? "HFS0" : "PFS0";
104 return ""; 95}
105 96
106 return pfs_entries[index].name; 97std::shared_ptr<VfsDirectory> PartitionFilesystem::GetParentDirectory() const {
98 // TODO(DarkLordZach): Add support for nested containers.
99 return nullptr;
107} 100}
108 101
109u64 PartitionFilesystem::GetFileOffset(const std::string& name) const { 102void PartitionFilesystem::PrintDebugInfo() const {
103 LOG_DEBUG(Service_FS, "Magic: {:.4}", pfs_header.magic);
104 LOG_DEBUG(Service_FS, "Files: {}", pfs_header.num_entries);
110 for (u32 i = 0; i < pfs_header.num_entries; i++) { 105 for (u32 i = 0; i < pfs_header.num_entries; i++) {
111 if (pfs_entries[i].name == name) 106 LOG_DEBUG(Service_FS, " > File {}: {} (0x{:X} bytes, at 0x{:X})", i,
112 return content_offset + pfs_entries[i].fs_entry.offset; 107 pfs_files[i]->GetName(), pfs_files[i]->GetSize(),
108 dynamic_cast<OffsetVfsFile*>(pfs_files[i].get())->GetOffset());
113 } 109 }
114
115 return 0;
116} 110}
117 111
118u64 PartitionFilesystem::GetFileSize(const std::string& name) const { 112bool PartitionFilesystem::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
119 for (u32 i = 0; i < pfs_header.num_entries; i++) { 113 auto iter = std::find(pfs_files.begin(), pfs_files.end(), file);
120 if (pfs_entries[i].name == name) 114 if (iter == pfs_files.end())
121 return pfs_entries[i].fs_entry.size; 115 return false;
122 }
123 116
124 return 0; 117 pfs_files[iter - pfs_files.begin()] = pfs_files.back();
125} 118 pfs_files.pop_back();
126 119
127void PartitionFilesystem::Print() const { 120 pfs_dirs.emplace_back(dir);
128 LOG_DEBUG(Service_FS, "Magic: {}", pfs_header.magic); 121
129 LOG_DEBUG(Service_FS, "Files: {}", pfs_header.num_entries); 122 return true;
130 for (u32 i = 0; i < pfs_header.num_entries; i++) {
131 LOG_DEBUG(Service_FS, " > File {}: {} (0x{:X} bytes, at 0x{:X})", i,
132 pfs_entries[i].name.c_str(), pfs_entries[i].fs_entry.size,
133 GetFileOffset(pfs_entries[i].name));
134 }
135} 123}
136} // namespace FileSys 124} // namespace FileSys
diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h
index 9c5810cf1..9656b40bf 100644
--- a/src/core/file_sys/partition_filesystem.h
+++ b/src/core/file_sys/partition_filesystem.h
@@ -10,6 +10,7 @@
10#include "common/common_funcs.h" 10#include "common/common_funcs.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/swap.h" 12#include "common/swap.h"
13#include "core/file_sys/vfs.h"
13 14
14namespace Loader { 15namespace Loader {
15enum class ResultStatus; 16enum class ResultStatus;
@@ -21,19 +22,19 @@ namespace FileSys {
21 * Helper which implements an interface to parse PFS/HFS filesystems. 22 * Helper which implements an interface to parse PFS/HFS filesystems.
22 * Data can either be loaded from a file path or data with an offset into it. 23 * Data can either be loaded from a file path or data with an offset into it.
23 */ 24 */
24class PartitionFilesystem { 25class PartitionFilesystem : public ReadOnlyVfsDirectory {
25public: 26public:
26 Loader::ResultStatus Load(const std::string& file_path, size_t offset = 0); 27 explicit PartitionFilesystem(std::shared_ptr<VfsFile> file);
27 Loader::ResultStatus Load(const std::vector<u8>& file_data, size_t offset = 0); 28 Loader::ResultStatus GetStatus() const;
28 29
29 u32 GetNumEntries() const; 30 std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
30 u64 GetEntryOffset(u32 index) const; 31 std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
31 u64 GetEntrySize(u32 index) const; 32 std::string GetName() const override;
32 std::string GetEntryName(u32 index) const; 33 std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
33 u64 GetFileOffset(const std::string& name) const; 34 void PrintDebugInfo() const;
34 u64 GetFileSize(const std::string& name) const;
35 35
36 void Print() const; 36protected:
37 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
37 38
38private: 39private:
39 struct Header { 40 struct Header {
@@ -72,16 +73,14 @@ private:
72 73
73#pragma pack(pop) 74#pragma pack(pop)
74 75
75 struct FileEntry { 76 Loader::ResultStatus status;
76 FSEntry fs_entry;
77 std::string name;
78 };
79 77
80 Header pfs_header; 78 Header pfs_header;
81 bool is_hfs; 79 bool is_hfs;
82 size_t content_offset; 80 size_t content_offset;
83 81
84 std::vector<FileEntry> pfs_entries; 82 std::vector<VirtualFile> pfs_files;
83 std::vector<VirtualDir> pfs_dirs;
85}; 84};
86 85
87} // namespace FileSys 86} // namespace FileSys
diff --git a/src/core/file_sys/path_parser.cpp b/src/core/file_sys/path_parser.cpp
deleted file mode 100644
index 5a89b02b8..000000000
--- a/src/core/file_sys/path_parser.cpp
+++ /dev/null
@@ -1,98 +0,0 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <set>
7#include "common/file_util.h"
8#include "common/string_util.h"
9#include "core/file_sys/path_parser.h"
10
11namespace FileSys {
12
13PathParser::PathParser(const Path& path) {
14 if (path.GetType() != LowPathType::Char && path.GetType() != LowPathType::Wchar) {
15 is_valid = false;
16 return;
17 }
18
19 auto path_string = path.AsString();
20 if (path_string.size() == 0 || path_string[0] != '/') {
21 is_valid = false;
22 return;
23 }
24
25 // Filter out invalid characters for the host system.
26 // Although some of these characters are valid on 3DS, they are unlikely to be used by games.
27 if (std::find_if(path_string.begin(), path_string.end(), [](char c) {
28 static const std::set<char> invalid_chars{'<', '>', '\\', '|', ':', '\"', '*', '?'};
29 return invalid_chars.find(c) != invalid_chars.end();
30 }) != path_string.end()) {
31 is_valid = false;
32 return;
33 }
34
35 Common::SplitString(path_string, '/', path_sequence);
36
37 auto begin = path_sequence.begin();
38 auto end = path_sequence.end();
39 end = std::remove_if(begin, end, [](std::string& str) { return str == "" || str == "."; });
40 path_sequence = std::vector<std::string>(begin, end);
41
42 // checks if the path is out of bounds.
43 int level = 0;
44 for (auto& node : path_sequence) {
45 if (node == "..") {
46 --level;
47 if (level < 0) {
48 is_valid = false;
49 return;
50 }
51 } else {
52 ++level;
53 }
54 }
55
56 is_valid = true;
57 is_root = level == 0;
58}
59
60PathParser::HostStatus PathParser::GetHostStatus(const std::string& mount_point) const {
61 auto path = mount_point;
62 if (!FileUtil::IsDirectory(path))
63 return InvalidMountPoint;
64 if (path_sequence.empty()) {
65 return DirectoryFound;
66 }
67
68 for (auto iter = path_sequence.begin(); iter != path_sequence.end() - 1; iter++) {
69 if (path.back() != '/')
70 path += '/';
71 path += *iter;
72
73 if (!FileUtil::Exists(path))
74 return PathNotFound;
75 if (FileUtil::IsDirectory(path))
76 continue;
77 return FileInPath;
78 }
79
80 path += "/" + path_sequence.back();
81 if (!FileUtil::Exists(path))
82 return NotFound;
83 if (FileUtil::IsDirectory(path))
84 return DirectoryFound;
85 return FileFound;
86}
87
88std::string PathParser::BuildHostPath(const std::string& mount_point) const {
89 std::string path = mount_point;
90 for (auto& node : path_sequence) {
91 if (path.back() != '/')
92 path += '/';
93 path += node;
94 }
95 return path;
96}
97
98} // namespace FileSys
diff --git a/src/core/file_sys/path_parser.h b/src/core/file_sys/path_parser.h
deleted file mode 100644
index 184f59d55..000000000
--- a/src/core/file_sys/path_parser.h
+++ /dev/null
@@ -1,61 +0,0 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <string>
8#include <vector>
9#include "core/file_sys/filesystem.h"
10
11namespace FileSys {
12
13/**
14 * A helper class parsing and verifying a string-type Path.
15 * Every archives with a sub file system should use this class to parse the path argument and check
16 * the status of the file / directory in question on the host file system.
17 */
18class PathParser {
19public:
20 explicit PathParser(const Path& path);
21
22 /**
23 * Checks if the Path is valid.
24 * This function should be called once a PathParser is constructed.
25 * A Path is valid if:
26 * - it is a string path (with type LowPathType::Char or LowPathType::Wchar),
27 * - it starts with "/" (this seems a hard requirement in real 3DS),
28 * - it doesn't contain invalid characters, and
29 * - it doesn't go out of the root directory using "..".
30 */
31 bool IsValid() const {
32 return is_valid;
33 }
34
35 /// Checks if the Path represents the root directory.
36 bool IsRootDirectory() const {
37 return is_root;
38 }
39
40 enum HostStatus {
41 InvalidMountPoint,
42 PathNotFound, // "/a/b/c" when "a" doesn't exist
43 FileInPath, // "/a/b/c" when "a" is a file
44 FileFound, // "/a/b/c" when "c" is a file
45 DirectoryFound, // "/a/b/c" when "c" is a directory
46 NotFound // "/a/b/c" when "a/b/" exists but "c" doesn't exist
47 };
48
49 /// Checks the status of the specified file / directory by the Path on the host file system.
50 HostStatus GetHostStatus(const std::string& mount_point) const;
51
52 /// Builds a full path on the host file system.
53 std::string BuildHostPath(const std::string& mount_point) const;
54
55private:
56 std::vector<std::string> path_sequence;
57 bool is_valid{};
58 bool is_root{};
59};
60
61} // namespace FileSys
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index 226811115..63d4b6e4f 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -9,40 +9,29 @@
9 9
10namespace FileSys { 10namespace FileSys {
11 11
12Loader::ResultStatus ProgramMetadata::Load(const std::string& file_path) { 12Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
13 FileUtil::IOFile file(file_path, "rb"); 13 size_t total_size = static_cast<size_t>(file->GetSize());
14 if (!file.IsOpen()) 14 if (total_size < sizeof(Header))
15 return Loader::ResultStatus::Error; 15 return Loader::ResultStatus::Error;
16 16
17 std::vector<u8> file_data(file.GetSize()); 17 // TODO(DarkLordZach): Use ReadObject when Header/AcidHeader becomes trivially copyable.
18 18 std::vector<u8> npdm_header_data = file->ReadBytes(sizeof(Header));
19 if (!file.ReadBytes(file_data.data(), file_data.size())) 19 if (sizeof(Header) != npdm_header_data.size())
20 return Loader::ResultStatus::Error; 20 return Loader::ResultStatus::Error;
21 std::memcpy(&npdm_header, npdm_header_data.data(), sizeof(Header));
21 22
22 Loader::ResultStatus result = Load(file_data); 23 std::vector<u8> acid_header_data = file->ReadBytes(sizeof(AcidHeader), npdm_header.acid_offset);
23 if (result != Loader::ResultStatus::Success) 24 if (sizeof(AcidHeader) != acid_header_data.size())
24 LOG_ERROR(Service_FS, "Failed to load NPDM from file {}!", file_path);
25
26 return result;
27}
28
29Loader::ResultStatus ProgramMetadata::Load(const std::vector<u8> file_data, size_t offset) {
30 size_t total_size = static_cast<size_t>(file_data.size() - offset);
31 if (total_size < sizeof(Header))
32 return Loader::ResultStatus::Error; 25 return Loader::ResultStatus::Error;
26 std::memcpy(&acid_header, acid_header_data.data(), sizeof(AcidHeader));
33 27
34 size_t header_offset = offset; 28 if (sizeof(AciHeader) != file->ReadObject(&aci_header, npdm_header.aci_offset))
35 memcpy(&npdm_header, &file_data[offset], sizeof(Header)); 29 return Loader::ResultStatus::Error;
36
37 size_t aci_offset = header_offset + npdm_header.aci_offset;
38 size_t acid_offset = header_offset + npdm_header.acid_offset;
39 memcpy(&aci_header, &file_data[aci_offset], sizeof(AciHeader));
40 memcpy(&acid_header, &file_data[acid_offset], sizeof(AcidHeader));
41 30
42 size_t fac_offset = acid_offset + acid_header.fac_offset; 31 if (sizeof(FileAccessControl) != file->ReadObject(&acid_file_access, acid_header.fac_offset))
43 size_t fah_offset = aci_offset + aci_header.fah_offset; 32 return Loader::ResultStatus::Error;
44 memcpy(&acid_file_access, &file_data[fac_offset], sizeof(FileAccessControl)); 33 if (sizeof(FileAccessHeader) != file->ReadObject(&aci_file_access, aci_header.fah_offset))
45 memcpy(&aci_file_access, &file_data[fah_offset], sizeof(FileAccessHeader)); 34 return Loader::ResultStatus::Error;
46 35
47 return Loader::ResultStatus::Success; 36 return Loader::ResultStatus::Success;
48} 37}
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index b80a08485..06a7315db 100644
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -10,6 +10,7 @@
10#include "common/bit_field.h" 10#include "common/bit_field.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/swap.h" 12#include "common/swap.h"
13#include "partition_filesystem.h"
13 14
14namespace Loader { 15namespace Loader {
15enum class ResultStatus; 16enum class ResultStatus;
@@ -37,8 +38,7 @@ enum class ProgramFilePermission : u64 {
37 */ 38 */
38class ProgramMetadata { 39class ProgramMetadata {
39public: 40public:
40 Loader::ResultStatus Load(const std::string& file_path); 41 Loader::ResultStatus Load(VirtualFile file);
41 Loader::ResultStatus Load(const std::vector<u8> file_data, size_t offset = 0);
42 42
43 bool Is64BitProgram() const; 43 bool Is64BitProgram() const;
44 ProgramAddressSpaceType GetAddressSpaceType() const; 44 ProgramAddressSpaceType GetAddressSpaceType() const;
@@ -51,6 +51,7 @@ public:
51 void Print() const; 51 void Print() const;
52 52
53private: 53private:
54 // TODO(DarkLordZach): BitField is not trivially copyable.
54 struct Header { 55 struct Header {
55 std::array<char, 4> magic; 56 std::array<char, 4> magic;
56 std::array<u8, 8> reserved; 57 std::array<u8, 8> reserved;
@@ -77,6 +78,7 @@ private:
77 78
78 static_assert(sizeof(Header) == 0x80, "NPDM header structure size is wrong"); 79 static_assert(sizeof(Header) == 0x80, "NPDM header structure size is wrong");
79 80
81 // TODO(DarkLordZach): BitField is not trivially copyable.
80 struct AcidHeader { 82 struct AcidHeader {
81 std::array<u8, 0x100> signature; 83 std::array<u8, 0x100> signature;
82 std::array<u8, 0x100> nca_modulus; 84 std::array<u8, 0x100> nca_modulus;
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index 946fc0452..54fbd3267 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -7,21 +7,19 @@
7#include "common/common_types.h" 7#include "common/common_types.h"
8#include "common/logging/log.h" 8#include "common/logging/log.h"
9#include "core/file_sys/romfs_factory.h" 9#include "core/file_sys/romfs_factory.h"
10#include "core/file_sys/romfs_filesystem.h"
11 10
12namespace FileSys { 11namespace FileSys {
13 12
14RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) { 13RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) {
15 // Load the RomFS from the app 14 // Load the RomFS from the app
16 if (Loader::ResultStatus::Success != app_loader.ReadRomFS(romfs_file, data_offset, data_size)) { 15 if (Loader::ResultStatus::Success != app_loader.ReadRomFS(file)) {
17 LOG_ERROR(Service_FS, "Unable to read RomFS!"); 16 LOG_ERROR(Service_FS, "Unable to read RomFS!");
18 } 17 }
19} 18}
20 19
21ResultVal<std::unique_ptr<FileSystemBackend>> RomFSFactory::Open(u64 title_id) { 20ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id) {
22 // TODO(DarkLordZach): Use title id. 21 // TODO(DarkLordZach): Use title id.
23 auto archive = std::make_unique<RomFS_FileSystem>(romfs_file, data_offset, data_size); 22 return MakeResult<VirtualFile>(file);
24 return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
25} 23}
26 24
27} // namespace FileSys 25} // namespace FileSys
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h
index c9e20c3ab..c19787cd4 100644
--- a/src/core/file_sys/romfs_factory.h
+++ b/src/core/file_sys/romfs_factory.h
@@ -5,10 +5,7 @@
5#pragma once 5#pragma once
6 6
7#include <memory> 7#include <memory>
8#include <string>
9#include <vector>
10#include "common/common_types.h" 8#include "common/common_types.h"
11#include "core/file_sys/filesystem.h"
12#include "core/hle/result.h" 9#include "core/hle/result.h"
13#include "core/loader/loader.h" 10#include "core/loader/loader.h"
14 11
@@ -19,12 +16,10 @@ class RomFSFactory {
19public: 16public:
20 explicit RomFSFactory(Loader::AppLoader& app_loader); 17 explicit RomFSFactory(Loader::AppLoader& app_loader);
21 18
22 ResultVal<std::unique_ptr<FileSystemBackend>> Open(u64 title_id); 19 ResultVal<VirtualFile> Open(u64 title_id);
23 20
24private: 21private:
25 std::shared_ptr<FileUtil::IOFile> romfs_file; 22 VirtualFile file;
26 u64 data_offset;
27 u64 data_size;
28}; 23};
29 24
30} // namespace FileSys 25} // namespace FileSys
diff --git a/src/core/file_sys/romfs_filesystem.cpp b/src/core/file_sys/romfs_filesystem.cpp
deleted file mode 100644
index 83162622b..000000000
--- a/src/core/file_sys/romfs_filesystem.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <cstring>
6#include <memory>
7#include "common/common_types.h"
8#include "common/logging/log.h"
9#include "core/file_sys/romfs_filesystem.h"
10
11namespace FileSys {
12
13std::string RomFS_FileSystem::GetName() const {
14 return "RomFS";
15}
16
17ResultVal<std::unique_ptr<StorageBackend>> RomFS_FileSystem::OpenFile(const std::string& path,
18 Mode mode) const {
19 return MakeResult<std::unique_ptr<StorageBackend>>(
20 std::make_unique<RomFS_Storage>(romfs_file, data_offset, data_size));
21}
22
23ResultCode RomFS_FileSystem::DeleteFile(const std::string& path) const {
24 LOG_CRITICAL(Service_FS, "Attempted to delete a file from an ROMFS archive ({}).", GetName());
25 // TODO(bunnei): Use correct error code
26 return ResultCode(-1);
27}
28
29ResultCode RomFS_FileSystem::RenameFile(const std::string& src_path,
30 const std::string& dest_path) const {
31 LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive ({}).", GetName());
32 // TODO(wwylele): Use correct error code
33 return ResultCode(-1);
34}
35
36ResultCode RomFS_FileSystem::DeleteDirectory(const Path& path) const {
37 LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive ({}).",
38 GetName());
39 // TODO(wwylele): Use correct error code
40 return ResultCode(-1);
41}
42
43ResultCode RomFS_FileSystem::DeleteDirectoryRecursively(const Path& path) const {
44 LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive ({}).",
45 GetName());
46 // TODO(wwylele): Use correct error code
47 return ResultCode(-1);
48}
49
50ResultCode RomFS_FileSystem::CreateFile(const std::string& path, u64 size) const {
51 LOG_CRITICAL(Service_FS, "Attempted to create a file in an ROMFS archive ({}).", GetName());
52 // TODO(bunnei): Use correct error code
53 return ResultCode(-1);
54}
55
56ResultCode RomFS_FileSystem::CreateDirectory(const std::string& path) const {
57 LOG_CRITICAL(Service_FS, "Attempted to create a directory in an ROMFS archive ({}).",
58 GetName());
59 // TODO(wwylele): Use correct error code
60 return ResultCode(-1);
61}
62
63ResultCode RomFS_FileSystem::RenameDirectory(const Path& src_path, const Path& dest_path) const {
64 LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive ({}).", GetName());
65 // TODO(wwylele): Use correct error code
66 return ResultCode(-1);
67}
68
69ResultVal<std::unique_ptr<DirectoryBackend>> RomFS_FileSystem::OpenDirectory(
70 const std::string& path) const {
71 LOG_WARNING(Service_FS, "Opening Directory in a ROMFS archive");
72 return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<ROMFSDirectory>());
73}
74
75u64 RomFS_FileSystem::GetFreeSpaceSize() const {
76 LOG_WARNING(Service_FS, "Attempted to get the free space in an ROMFS archive");
77 return 0;
78}
79
80ResultVal<FileSys::EntryType> RomFS_FileSystem::GetEntryType(const std::string& path) const {
81 LOG_CRITICAL(Service_FS, "Called within an ROMFS archive (path {}).", path);
82 // TODO(wwylele): Use correct error code
83 return ResultCode(-1);
84}
85
86ResultVal<size_t> RomFS_Storage::Read(const u64 offset, const size_t length, u8* buffer) const {
87 LOG_TRACE(Service_FS, "called offset={}, length={}", offset, length);
88 romfs_file->Seek(data_offset + offset, SEEK_SET);
89 size_t read_length = (size_t)std::min((u64)length, data_size - offset);
90
91 return MakeResult<size_t>(romfs_file->ReadBytes(buffer, read_length));
92}
93
94ResultVal<size_t> RomFS_Storage::Write(const u64 offset, const size_t length, const bool flush,
95 const u8* buffer) const {
96 LOG_ERROR(Service_FS, "Attempted to write to ROMFS file");
97 // TODO(Subv): Find error code
98 return MakeResult<size_t>(0);
99}
100
101u64 RomFS_Storage::GetSize() const {
102 return data_size;
103}
104
105bool RomFS_Storage::SetSize(const u64 size) const {
106 LOG_ERROR(Service_FS, "Attempted to set the size of an ROMFS file");
107 return false;
108}
109
110} // namespace FileSys
diff --git a/src/core/file_sys/romfs_filesystem.h b/src/core/file_sys/romfs_filesystem.h
deleted file mode 100644
index ba9d85823..000000000
--- a/src/core/file_sys/romfs_filesystem.h
+++ /dev/null
@@ -1,85 +0,0 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <cstddef>
8#include <memory>
9#include <string>
10#include <vector>
11#include "common/common_types.h"
12#include "common/file_util.h"
13#include "core/file_sys/directory.h"
14#include "core/file_sys/filesystem.h"
15#include "core/file_sys/storage.h"
16#include "core/hle/result.h"
17
18namespace FileSys {
19
20/**
21 * Helper which implements an interface to deal with Switch .istorage ROMFS images used in some
22 * archives This should be subclassed by concrete archive types, which will provide the input data
23 * (load the raw ROMFS archive) and override any required methods
24 */
25class RomFS_FileSystem : public FileSystemBackend {
26public:
27 RomFS_FileSystem(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size)
28 : romfs_file(file), data_offset(offset), data_size(size) {}
29
30 std::string GetName() const override;
31
32 ResultVal<std::unique_ptr<StorageBackend>> OpenFile(const std::string& path,
33 Mode mode) const override;
34 ResultCode DeleteFile(const std::string& path) const override;
35 ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const override;
36 ResultCode DeleteDirectory(const Path& path) const override;
37 ResultCode DeleteDirectoryRecursively(const Path& path) const override;
38 ResultCode CreateFile(const std::string& path, u64 size) const override;
39 ResultCode CreateDirectory(const std::string& path) const override;
40 ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
41 ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(
42 const std::string& path) const override;
43 u64 GetFreeSpaceSize() const override;
44 ResultVal<EntryType> GetEntryType(const std::string& path) const override;
45
46protected:
47 std::shared_ptr<FileUtil::IOFile> romfs_file;
48 u64 data_offset;
49 u64 data_size;
50};
51
52class RomFS_Storage : public StorageBackend {
53public:
54 RomFS_Storage(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size)
55 : romfs_file(file), data_offset(offset), data_size(size) {}
56
57 ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
58 ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
59 u64 GetSize() const override;
60 bool SetSize(u64 size) const override;
61 bool Close() const override {
62 return false;
63 }
64 void Flush() const override {}
65
66private:
67 std::shared_ptr<FileUtil::IOFile> romfs_file;
68 u64 data_offset;
69 u64 data_size;
70};
71
72class ROMFSDirectory : public DirectoryBackend {
73public:
74 u64 Read(const u64 count, Entry* entries) override {
75 return 0;
76 }
77 u64 GetEntryCount() const override {
78 return 0;
79 }
80 bool Close() const override {
81 return false;
82 }
83};
84
85} // namespace FileSys
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index 3ad37b28c..6a53b2b10 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -6,7 +6,6 @@
6#include "common/common_types.h" 6#include "common/common_types.h"
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8#include "core/core.h" 8#include "core/core.h"
9#include "core/file_sys/disk_filesystem.h"
10#include "core/file_sys/savedata_factory.h" 9#include "core/file_sys/savedata_factory.h"
11#include "core/hle/kernel/process.h" 10#include "core/hle/kernel/process.h"
12 11
@@ -17,11 +16,9 @@ std::string SaveDataDescriptor::DebugInfo() {
17 static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id); 16 static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id);
18} 17}
19 18
20SaveDataFactory::SaveDataFactory(std::string nand_directory) 19SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {}
21 : nand_directory(std::move(nand_directory)) {}
22 20
23ResultVal<std::unique_ptr<FileSystemBackend>> SaveDataFactory::Open(SaveDataSpaceId space, 21ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescriptor meta) {
24 SaveDataDescriptor meta) {
25 if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) { 22 if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) {
26 if (meta.zero_1 != 0) { 23 if (meta.zero_1 != 0) {
27 LOG_WARNING(Service_FS, 24 LOG_WARNING(Service_FS,
@@ -56,28 +53,23 @@ ResultVal<std::unique_ptr<FileSystemBackend>> SaveDataFactory::Open(SaveDataSpac
56 // TODO(DarkLordZach): Try to not create when opening, there are dedicated create save methods. 53 // TODO(DarkLordZach): Try to not create when opening, there are dedicated create save methods.
57 // But, user_ids don't match so this works for now. 54 // But, user_ids don't match so this works for now.
58 55
59 if (!FileUtil::Exists(save_directory)) { 56 auto out = dir->GetDirectoryRelative(save_directory);
57
58 if (out == nullptr) {
60 // TODO(bunnei): This is a work-around to always create a save data directory if it does not 59 // TODO(bunnei): This is a work-around to always create a save data directory if it does not
61 // already exist. This is a hack, as we do not understand yet how this works on hardware. 60 // already exist. This is a hack, as we do not understand yet how this works on hardware.
62 // Without a save data directory, many games will assert on boot. This should not have any 61 // Without a save data directory, many games will assert on boot. This should not have any
63 // bad side-effects. 62 // bad side-effects.
64 FileUtil::CreateFullPath(save_directory); 63 out = dir->CreateDirectoryRelative(save_directory);
65 }
66
67 // TODO(DarkLordZach): For some reason, CreateFullPath doesn't create the last bit. Should be
68 // fixed with VFS.
69 if (!FileUtil::IsDirectory(save_directory)) {
70 FileUtil::CreateDir(save_directory);
71 } 64 }
72 65
73 // Return an error if the save data doesn't actually exist. 66 // Return an error if the save data doesn't actually exist.
74 if (!FileUtil::IsDirectory(save_directory)) { 67 if (out == nullptr) {
75 // TODO(Subv): Find out correct error code. 68 // TODO(Subv): Find out correct error code.
76 return ResultCode(-1); 69 return ResultCode(-1);
77 } 70 }
78 71
79 auto archive = std::make_unique<Disk_FileSystem>(save_directory); 72 return MakeResult<VirtualDir>(std::move(out));
80 return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
81} 73}
82 74
83std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, 75std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id,
@@ -87,14 +79,14 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
87 if (type == SaveDataType::SaveData && title_id == 0) 79 if (type == SaveDataType::SaveData && title_id == 0)
88 title_id = Core::CurrentProcess()->program_id; 80 title_id = Core::CurrentProcess()->program_id;
89 81
90 std::string prefix; 82 std::string out;
91 83
92 switch (space) { 84 switch (space) {
93 case SaveDataSpaceId::NandSystem: 85 case SaveDataSpaceId::NandSystem:
94 prefix = nand_directory + "system/save/"; 86 out = "/system/save/";
95 break; 87 break;
96 case SaveDataSpaceId::NandUser: 88 case SaveDataSpaceId::NandUser:
97 prefix = nand_directory + "user/save/"; 89 out = "/user/save/";
98 break; 90 break;
99 default: 91 default:
100 ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space)); 92 ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
@@ -102,9 +94,9 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
102 94
103 switch (type) { 95 switch (type) {
104 case SaveDataType::SystemSaveData: 96 case SaveDataType::SystemSaveData:
105 return fmt::format("{}{:016X}/{:016X}{:016X}", prefix, save_id, user_id[1], user_id[0]); 97 return fmt::format("{}{:016X}/{:016X}{:016X}", out, save_id, user_id[1], user_id[0]);
106 case SaveDataType::SaveData: 98 case SaveDataType::SaveData:
107 return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", prefix, 0, user_id[1], user_id[0], 99 return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
108 title_id); 100 title_id);
109 default: 101 default:
110 ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type)); 102 ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type));
diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h
index b96721ac0..53c69876f 100644
--- a/src/core/file_sys/savedata_factory.h
+++ b/src/core/file_sys/savedata_factory.h
@@ -7,7 +7,6 @@
7#include <memory> 7#include <memory>
8#include <string> 8#include <string>
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "core/file_sys/filesystem.h"
11#include "core/hle/result.h" 10#include "core/hle/result.h"
12 11
13namespace FileSys { 12namespace FileSys {
@@ -45,14 +44,12 @@ static_assert(sizeof(SaveDataDescriptor) == 0x40, "SaveDataDescriptor has incorr
45/// File system interface to the SaveData archive 44/// File system interface to the SaveData archive
46class SaveDataFactory { 45class SaveDataFactory {
47public: 46public:
48 explicit SaveDataFactory(std::string nand_directory); 47 explicit SaveDataFactory(VirtualDir dir);
49 48
50 ResultVal<std::unique_ptr<FileSystemBackend>> Open(SaveDataSpaceId space, 49 ResultVal<VirtualDir> Open(SaveDataSpaceId space, SaveDataDescriptor meta);
51 SaveDataDescriptor meta);
52 50
53private: 51private:
54 std::string nand_directory; 52 VirtualDir dir;
55 std::string sd_directory;
56 53
57 std::string GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, u128 user_id, 54 std::string GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, u128 user_id,
58 u64 save_id) const; 55 u64 save_id) const;
diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp
index ac6f2f971..c1edfcef3 100644
--- a/src/core/file_sys/sdmc_factory.cpp
+++ b/src/core/file_sys/sdmc_factory.cpp
@@ -3,25 +3,15 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <memory> 5#include <memory>
6#include "common/common_types.h"
7#include "common/logging/log.h"
8#include "common/string_util.h"
9#include "core/core.h" 6#include "core/core.h"
10#include "core/file_sys/disk_filesystem.h"
11#include "core/file_sys/sdmc_factory.h" 7#include "core/file_sys/sdmc_factory.h"
12 8
13namespace FileSys { 9namespace FileSys {
14 10
15SDMCFactory::SDMCFactory(std::string sd_directory) : sd_directory(std::move(sd_directory)) {} 11SDMCFactory::SDMCFactory(VirtualDir dir) : dir(std::move(dir)) {}
16 12
17ResultVal<std::unique_ptr<FileSystemBackend>> SDMCFactory::Open() { 13ResultVal<VirtualDir> SDMCFactory::Open() {
18 // Create the SD Card directory if it doesn't already exist. 14 return MakeResult<VirtualDir>(dir);
19 if (!FileUtil::IsDirectory(sd_directory)) {
20 FileUtil::CreateFullPath(sd_directory);
21 }
22
23 auto archive = std::make_unique<Disk_FileSystem>(sd_directory);
24 return MakeResult<std::unique_ptr<FileSystemBackend>>(std::move(archive));
25} 15}
26 16
27} // namespace FileSys 17} // namespace FileSys
diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h
index 09bec7fce..9f0c75e84 100644
--- a/src/core/file_sys/sdmc_factory.h
+++ b/src/core/file_sys/sdmc_factory.h
@@ -4,10 +4,6 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <memory>
8#include <string>
9#include "common/common_types.h"
10#include "core/file_sys/filesystem.h"
11#include "core/hle/result.h" 7#include "core/hle/result.h"
12 8
13namespace FileSys { 9namespace FileSys {
@@ -15,12 +11,12 @@ namespace FileSys {
15/// File system interface to the SDCard archive 11/// File system interface to the SDCard archive
16class SDMCFactory { 12class SDMCFactory {
17public: 13public:
18 explicit SDMCFactory(std::string sd_directory); 14 explicit SDMCFactory(VirtualDir dir);
19 15
20 ResultVal<std::unique_ptr<FileSystemBackend>> Open(); 16 ResultVal<VirtualDir> Open();
21 17
22private: 18private:
23 std::string sd_directory; 19 VirtualDir dir;
24}; 20};
25 21
26} // namespace FileSys 22} // namespace FileSys
diff --git a/src/core/file_sys/storage.h b/src/core/file_sys/storage.h
deleted file mode 100644
index 2a6811831..000000000
--- a/src/core/file_sys/storage.h
+++ /dev/null
@@ -1,63 +0,0 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <cstddef>
8#include "common/common_types.h"
9#include "core/hle/result.h"
10
11namespace FileSys {
12
13class StorageBackend : NonCopyable {
14public:
15 StorageBackend() {}
16 virtual ~StorageBackend() {}
17
18 /**
19 * Read data from the file
20 * @param offset Offset in bytes to start reading data from
21 * @param length Length in bytes of data to read from file
22 * @param buffer Buffer to read data into
23 * @return Number of bytes read, or error code
24 */
25 virtual ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const = 0;
26
27 /**
28 * Write data to the file
29 * @param offset Offset in bytes to start writing data to
30 * @param length Length in bytes of data to write to file
31 * @param flush The flush parameters (0 == do not flush)
32 * @param buffer Buffer to read data from
33 * @return Number of bytes written, or error code
34 */
35 virtual ResultVal<size_t> Write(u64 offset, size_t length, bool flush,
36 const u8* buffer) const = 0;
37
38 /**
39 * Flushes the file
40 */
41 virtual void Flush() const = 0;
42
43 /**
44 * Set the size of the file in bytes
45 * @param size New size of the file
46 * @return true if successful
47 */
48 virtual bool SetSize(u64 size) const = 0;
49
50 /**
51 * Get the size of the file in bytes
52 * @return Size of the file in bytes
53 */
54 virtual u64 GetSize() const = 0;
55
56 /**
57 * Close the file
58 * @return true if the file closed correctly
59 */
60 virtual bool Close() const = 0;
61};
62
63} // namespace FileSys
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
new file mode 100644
index 000000000..f859ef33f
--- /dev/null
+++ b/src/core/file_sys/vfs.cpp
@@ -0,0 +1,238 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <numeric>
7#include "common/file_util.h"
8#include "common/logging/backend.h"
9#include "core/file_sys/vfs.h"
10
11namespace FileSys {
12
13VfsFile::~VfsFile() = default;
14
15std::string VfsFile::GetExtension() const {
16 return FileUtil::GetExtensionFromFilename(GetName());
17}
18
19VfsDirectory::~VfsDirectory() = default;
20
21boost::optional<u8> VfsFile::ReadByte(size_t offset) const {
22 u8 out{};
23 size_t size = Read(&out, 1, offset);
24 if (size == 1)
25 return out;
26
27 return boost::none;
28}
29
30std::vector<u8> VfsFile::ReadBytes(size_t size, size_t offset) const {
31 std::vector<u8> out(size);
32 size_t read_size = Read(out.data(), size, offset);
33 out.resize(read_size);
34 return out;
35}
36
37std::vector<u8> VfsFile::ReadAllBytes() const {
38 return ReadBytes(GetSize());
39}
40
41bool VfsFile::WriteByte(u8 data, size_t offset) {
42 return Write(&data, 1, offset) == 1;
43}
44
45size_t VfsFile::WriteBytes(std::vector<u8> data, size_t offset) {
46 return Write(data.data(), data.size(), offset);
47}
48
49std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(const std::string& path) const {
50 auto vec = FileUtil::SplitPathComponents(path);
51 vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
52 vec.end());
53 if (vec.empty())
54 return nullptr;
55 if (vec.size() == 1)
56 return GetFile(vec[0]);
57 auto dir = GetSubdirectory(vec[0]);
58 for (size_t component = 1; component < vec.size() - 1; ++component) {
59 if (dir == nullptr)
60 return nullptr;
61 dir = dir->GetSubdirectory(vec[component]);
62 }
63 if (dir == nullptr)
64 return nullptr;
65 return dir->GetFile(vec.back());
66}
67
68std::shared_ptr<VfsFile> VfsDirectory::GetFileAbsolute(const std::string& path) const {
69 if (IsRoot())
70 return GetFileRelative(path);
71
72 return GetParentDirectory()->GetFileAbsolute(path);
73}
74
75std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(const std::string& path) const {
76 auto vec = FileUtil::SplitPathComponents(path);
77 vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
78 vec.end());
79 if (vec.empty())
80 // TODO(DarkLordZach): Return this directory if path is '/' or similar. Can't currently
81 // because of const-ness
82 return nullptr;
83 auto dir = GetSubdirectory(vec[0]);
84 for (size_t component = 1; component < vec.size(); ++component) {
85 if (dir == nullptr)
86 return nullptr;
87 dir = dir->GetSubdirectory(vec[component]);
88 }
89 return dir;
90}
91
92std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryAbsolute(const std::string& path) const {
93 if (IsRoot())
94 return GetDirectoryRelative(path);
95
96 return GetParentDirectory()->GetDirectoryAbsolute(path);
97}
98
99std::shared_ptr<VfsFile> VfsDirectory::GetFile(const std::string& name) const {
100 const auto& files = GetFiles();
101 const auto iter = std::find_if(files.begin(), files.end(),
102 [&name](const auto& file1) { return name == file1->GetName(); });
103 return iter == files.end() ? nullptr : *iter;
104}
105
106std::shared_ptr<VfsDirectory> VfsDirectory::GetSubdirectory(const std::string& name) const {
107 const auto& subs = GetSubdirectories();
108 const auto iter = std::find_if(subs.begin(), subs.end(),
109 [&name](const auto& file1) { return name == file1->GetName(); });
110 return iter == subs.end() ? nullptr : *iter;
111}
112
113bool VfsDirectory::IsRoot() const {
114 return GetParentDirectory() == nullptr;
115}
116
117size_t VfsDirectory::GetSize() const {
118 const auto& files = GetFiles();
119 const auto file_total =
120 std::accumulate(files.begin(), files.end(), 0ull,
121 [](const auto& f1, const auto& f2) { return f1 + f2->GetSize(); });
122
123 const auto& sub_dir = GetSubdirectories();
124 const auto subdir_total =
125 std::accumulate(sub_dir.begin(), sub_dir.end(), 0ull,
126 [](const auto& f1, const auto& f2) { return f1 + f2->GetSize(); });
127
128 return file_total + subdir_total;
129}
130
131std::shared_ptr<VfsFile> VfsDirectory::CreateFileRelative(const std::string& path) {
132 auto vec = FileUtil::SplitPathComponents(path);
133 vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
134 vec.end());
135 if (vec.empty())
136 return nullptr;
137 if (vec.size() == 1)
138 return CreateFile(vec[0]);
139 auto dir = GetSubdirectory(vec[0]);
140 if (dir == nullptr) {
141 dir = CreateSubdirectory(vec[0]);
142 if (dir == nullptr)
143 return nullptr;
144 }
145
146 return dir->CreateFileRelative(FileUtil::GetPathWithoutTop(path));
147}
148
149std::shared_ptr<VfsFile> VfsDirectory::CreateFileAbsolute(const std::string& path) {
150 if (IsRoot())
151 return CreateFileRelative(path);
152 return GetParentDirectory()->CreateFileAbsolute(path);
153}
154
155std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryRelative(const std::string& path) {
156 auto vec = FileUtil::SplitPathComponents(path);
157 vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }),
158 vec.end());
159 if (vec.empty())
160 return nullptr;
161 if (vec.size() == 1)
162 return CreateSubdirectory(vec[0]);
163 auto dir = GetSubdirectory(vec[0]);
164 if (dir == nullptr) {
165 dir = CreateSubdirectory(vec[0]);
166 if (dir == nullptr)
167 return nullptr;
168 }
169 return dir->CreateDirectoryRelative(FileUtil::GetPathWithoutTop(path));
170}
171
172std::shared_ptr<VfsDirectory> VfsDirectory::CreateDirectoryAbsolute(const std::string& path) {
173 if (IsRoot())
174 return CreateDirectoryRelative(path);
175 return GetParentDirectory()->CreateDirectoryAbsolute(path);
176}
177
178bool VfsDirectory::DeleteSubdirectoryRecursive(const std::string& name) {
179 auto dir = GetSubdirectory(name);
180 if (dir == nullptr)
181 return false;
182
183 bool success = true;
184 for (const auto& file : dir->GetFiles()) {
185 if (!DeleteFile(file->GetName()))
186 success = false;
187 }
188
189 for (const auto& sdir : dir->GetSubdirectories()) {
190 if (!dir->DeleteSubdirectoryRecursive(sdir->GetName()))
191 success = false;
192 }
193
194 return success;
195}
196
197bool VfsDirectory::Copy(const std::string& src, const std::string& dest) {
198 const auto f1 = GetFile(src);
199 auto f2 = CreateFile(dest);
200 if (f1 == nullptr || f2 == nullptr)
201 return false;
202
203 if (!f2->Resize(f1->GetSize())) {
204 DeleteFile(dest);
205 return false;
206 }
207
208 return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize();
209}
210
211bool ReadOnlyVfsDirectory::IsWritable() const {
212 return false;
213}
214
215bool ReadOnlyVfsDirectory::IsReadable() const {
216 return true;
217}
218
219std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateSubdirectory(const std::string& name) {
220 return nullptr;
221}
222
223std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFile(const std::string& name) {
224 return nullptr;
225}
226
227bool ReadOnlyVfsDirectory::DeleteSubdirectory(const std::string& name) {
228 return false;
229}
230
231bool ReadOnlyVfsDirectory::DeleteFile(const std::string& name) {
232 return false;
233}
234
235bool ReadOnlyVfsDirectory::Rename(const std::string& name) {
236 return false;
237}
238} // namespace FileSys
diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h
new file mode 100644
index 000000000..a5213e0cc
--- /dev/null
+++ b/src/core/file_sys/vfs.h
@@ -0,0 +1,237 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <string>
9#include <type_traits>
10#include <vector>
11#include "boost/optional.hpp"
12#include "common/common_types.h"
13#include "common/file_util.h"
14
15namespace FileSys {
16struct VfsFile;
17struct VfsDirectory;
18
19// Convenience typedefs to use VfsDirectory and VfsFile
20using VirtualDir = std::shared_ptr<FileSys::VfsDirectory>;
21using VirtualFile = std::shared_ptr<FileSys::VfsFile>;
22
23// A class representing a file in an abstract filesystem.
24struct VfsFile : NonCopyable {
25 virtual ~VfsFile();
26
27 // Retrieves the file name.
28 virtual std::string GetName() const = 0;
29 // Retrieves the extension of the file name.
30 virtual std::string GetExtension() const;
31 // Retrieves the size of the file.
32 virtual size_t GetSize() const = 0;
33 // Resizes the file to new_size. Returns whether or not the operation was successful.
34 virtual bool Resize(size_t new_size) = 0;
35 // Gets a pointer to the directory containing this file, returning nullptr if there is none.
36 virtual std::shared_ptr<VfsDirectory> GetContainingDirectory() const = 0;
37
38 // Returns whether or not the file can be written to.
39 virtual bool IsWritable() const = 0;
40 // Returns whether or not the file can be read from.
41 virtual bool IsReadable() const = 0;
42
43 // The primary method of reading from the file. Reads length bytes into data starting at offset
44 // into file. Returns number of bytes successfully read.
45 virtual size_t Read(u8* data, size_t length, size_t offset = 0) const = 0;
46 // The primary method of writing to the file. Writes length bytes from data starting at offset
47 // into file. Returns number of bytes successfully written.
48 virtual size_t Write(const u8* data, size_t length, size_t offset = 0) = 0;
49
50 // Reads exactly one byte at the offset provided, returning boost::none on error.
51 virtual boost::optional<u8> ReadByte(size_t offset = 0) const;
52 // Reads size bytes starting at offset in file into a vector.
53 virtual std::vector<u8> ReadBytes(size_t size, size_t offset = 0) const;
54 // Reads all the bytes from the file into a vector. Equivalent to 'file->Read(file->GetSize(),
55 // 0)'
56 virtual std::vector<u8> ReadAllBytes() const;
57
58 // Reads an array of type T, size number_elements starting at offset.
59 // Returns the number of bytes (sizeof(T)*number_elements) read successfully.
60 template <typename T>
61 size_t ReadArray(T* data, size_t number_elements, size_t offset = 0) const {
62 static_assert(std::is_trivially_copyable<T>::value,
63 "Data type must be trivially copyable.");
64
65 return Read(reinterpret_cast<u8*>(data), number_elements * sizeof(T), offset);
66 }
67
68 // Reads size bytes into the memory starting at data starting at offset into the file.
69 // Returns the number of bytes read successfully.
70 template <typename T>
71 size_t ReadBytes(T* data, size_t size, size_t offset = 0) const {
72 static_assert(std::is_trivially_copyable<T>::value,
73 "Data type must be trivially copyable.");
74 return Read(reinterpret_cast<u8*>(data), size, offset);
75 }
76
77 // Reads one object of type T starting at offset in file.
78 // Returns the number of bytes read successfully (sizeof(T)).
79 template <typename T>
80 size_t ReadObject(T* data, size_t offset = 0) const {
81 static_assert(std::is_trivially_copyable<T>::value,
82 "Data type must be trivially copyable.");
83 return Read(reinterpret_cast<u8*>(data), sizeof(T), offset);
84 }
85
86 // Writes exactly one byte to offset in file and retuns whether or not the byte was written
87 // successfully.
88 virtual bool WriteByte(u8 data, size_t offset = 0);
89 // Writes a vector of bytes to offset in file and returns the number of bytes successfully
90 // written.
91 virtual size_t WriteBytes(std::vector<u8> data, size_t offset = 0);
92
93 // Writes an array of type T, size number_elements to offset in file.
94 // Returns the number of bytes (sizeof(T)*number_elements) written successfully.
95 template <typename T>
96 size_t WriteArray(T* data, size_t number_elements, size_t offset = 0) {
97 static_assert(std::is_trivially_copyable<T>::value,
98 "Data type must be trivially copyable.");
99
100 return Write(data, number_elements * sizeof(T), offset);
101 }
102
103 // Writes size bytes starting at memory location data to offset in file.
104 // Returns the number of bytes written successfully.
105 template <typename T>
106 size_t WriteBytes(T* data, size_t size, size_t offset = 0) {
107 static_assert(std::is_trivially_copyable<T>::value,
108 "Data type must be trivially copyable.");
109 return Write(reinterpret_cast<u8*>(data), size, offset);
110 }
111
112 // Writes one object of type T to offset in file.
113 // Returns the number of bytes written successfully (sizeof(T)).
114 template <typename T>
115 size_t WriteObject(const T& data, size_t offset = 0) {
116 static_assert(std::is_trivially_copyable<T>::value,
117 "Data type must be trivially copyable.");
118 return Write(&data, sizeof(T), offset);
119 }
120
121 // Renames the file to name. Returns whether or not the operation was successsful.
122 virtual bool Rename(const std::string& name) = 0;
123};
124
125// A class representing a directory in an abstract filesystem.
126struct VfsDirectory : NonCopyable {
127 virtual ~VfsDirectory();
128
129 // Retrives the file located at path as if the current directory was root. Returns nullptr if
130 // not found.
131 virtual std::shared_ptr<VfsFile> GetFileRelative(const std::string& path) const;
132 // Calls GetFileRelative(path) on the root of the current directory.
133 virtual std::shared_ptr<VfsFile> GetFileAbsolute(const std::string& path) const;
134
135 // Retrives the directory located at path as if the current directory was root. Returns nullptr
136 // if not found.
137 virtual std::shared_ptr<VfsDirectory> GetDirectoryRelative(const std::string& path) const;
138 // Calls GetDirectoryRelative(path) on the root of the current directory.
139 virtual std::shared_ptr<VfsDirectory> GetDirectoryAbsolute(const std::string& path) const;
140
141 // Returns a vector containing all of the files in this directory.
142 virtual std::vector<std::shared_ptr<VfsFile>> GetFiles() const = 0;
143 // Returns the file with filename matching name. Returns nullptr if directory dosen't have a
144 // file with name.
145 virtual std::shared_ptr<VfsFile> GetFile(const std::string& name) const;
146
147 // Returns a vector containing all of the subdirectories in this directory.
148 virtual std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const = 0;
149 // Returns the directory with name matching name. Returns nullptr if directory dosen't have a
150 // directory with name.
151 virtual std::shared_ptr<VfsDirectory> GetSubdirectory(const std::string& name) const;
152
153 // Returns whether or not the directory can be written to.
154 virtual bool IsWritable() const = 0;
155 // Returns whether of not the directory can be read from.
156 virtual bool IsReadable() const = 0;
157
158 // Returns whether or not the directory is the root of the current file tree.
159 virtual bool IsRoot() const;
160
161 // Returns the name of the directory.
162 virtual std::string GetName() const = 0;
163 // Returns the total size of all files and subdirectories in this directory.
164 virtual size_t GetSize() const;
165 // Returns the parent directory of this directory. Returns nullptr if this directory is root or
166 // has no parent.
167 virtual std::shared_ptr<VfsDirectory> GetParentDirectory() const = 0;
168
169 // Creates a new subdirectory with name name. Returns a pointer to the new directory or nullptr
170 // if the operation failed.
171 virtual std::shared_ptr<VfsDirectory> CreateSubdirectory(const std::string& name) = 0;
172 // Creates a new file with name name. Returns a pointer to the new file or nullptr if the
173 // operation failed.
174 virtual std::shared_ptr<VfsFile> CreateFile(const std::string& name) = 0;
175
176 // Creates a new file at the path relative to this directory. Also creates directories if
177 // they do not exist and is supported by this implementation. Returns nullptr on any failure.
178 virtual std::shared_ptr<VfsFile> CreateFileRelative(const std::string& path);
179
180 // Creates a new file at the path relative to root of this directory. Also creates directories
181 // if they do not exist and is supported by this implementation. Returns nullptr on any failure.
182 virtual std::shared_ptr<VfsFile> CreateFileAbsolute(const std::string& path);
183
184 // Creates a new directory at the path relative to this directory. Also creates directories if
185 // they do not exist and is supported by this implementation. Returns nullptr on any failure.
186 virtual std::shared_ptr<VfsDirectory> CreateDirectoryRelative(const std::string& path);
187
188 // Creates a new directory at the path relative to root of this directory. Also creates
189 // directories if they do not exist and is supported by this implementation. Returns nullptr on
190 // any failure.
191 virtual std::shared_ptr<VfsDirectory> CreateDirectoryAbsolute(const std::string& path);
192
193 // Deletes the subdirectory with name and returns true on success.
194 virtual bool DeleteSubdirectory(const std::string& name) = 0;
195 // Deletes all subdirectories and files of subdirectory with name recirsively and then deletes
196 // the subdirectory. Returns true on success.
197 virtual bool DeleteSubdirectoryRecursive(const std::string& name);
198 // Returnes whether or not the file with name name was deleted successfully.
199 virtual bool DeleteFile(const std::string& name) = 0;
200
201 // Returns whether or not this directory was renamed to name.
202 virtual bool Rename(const std::string& name) = 0;
203
204 // Returns whether or not the file with name src was successfully copied to a new file with name
205 // dest.
206 virtual bool Copy(const std::string& src, const std::string& dest);
207
208 // Interprets the file with name file instead as a directory of type directory.
209 // The directory must have a constructor that takes a single argument of type
210 // std::shared_ptr<VfsFile>. Allows to reinterpret container files (i.e NCA, zip, XCI, etc) as a
211 // subdirectory in one call.
212 template <typename Directory>
213 bool InterpretAsDirectory(const std::string& file) {
214 auto file_p = GetFile(file);
215 if (file_p == nullptr)
216 return false;
217 return ReplaceFileWithSubdirectory(file, std::make_shared<Directory>(file_p));
218 }
219
220protected:
221 // Backend for InterpretAsDirectory.
222 // Removes all references to file and adds a reference to dir in the directory's implementation.
223 virtual bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) = 0;
224};
225
226// A convenience partial-implementation of VfsDirectory that stubs out methods that should only work
227// if writable. This is to avoid redundant empty methods everywhere.
228struct ReadOnlyVfsDirectory : public VfsDirectory {
229 bool IsWritable() const override;
230 bool IsReadable() const override;
231 std::shared_ptr<VfsDirectory> CreateSubdirectory(const std::string& name) override;
232 std::shared_ptr<VfsFile> CreateFile(const std::string& name) override;
233 bool DeleteSubdirectory(const std::string& name) override;
234 bool DeleteFile(const std::string& name) override;
235 bool Rename(const std::string& name) override;
236};
237} // namespace FileSys
diff --git a/src/core/file_sys/vfs_offset.cpp b/src/core/file_sys/vfs_offset.cpp
new file mode 100644
index 000000000..288499cb5
--- /dev/null
+++ b/src/core/file_sys/vfs_offset.cpp
@@ -0,0 +1,92 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "core/file_sys/vfs_offset.h"
6
7namespace FileSys {
8
9OffsetVfsFile::OffsetVfsFile(std::shared_ptr<VfsFile> file_, size_t size_, size_t offset_,
10 const std::string& name_)
11 : file(file_), offset(offset_), size(size_), name(name_) {}
12
13std::string OffsetVfsFile::GetName() const {
14 return name.empty() ? file->GetName() : name;
15}
16
17size_t OffsetVfsFile::GetSize() const {
18 return size;
19}
20
21bool OffsetVfsFile::Resize(size_t new_size) {
22 if (offset + new_size < file->GetSize()) {
23 size = new_size;
24 } else {
25 auto res = file->Resize(offset + new_size);
26 if (!res)
27 return false;
28 size = new_size;
29 }
30
31 return true;
32}
33
34std::shared_ptr<VfsDirectory> OffsetVfsFile::GetContainingDirectory() const {
35 return file->GetContainingDirectory();
36}
37
38bool OffsetVfsFile::IsWritable() const {
39 return file->IsWritable();
40}
41
42bool OffsetVfsFile::IsReadable() const {
43 return file->IsReadable();
44}
45
46size_t OffsetVfsFile::Read(u8* data, size_t length, size_t r_offset) const {
47 return file->Read(data, TrimToFit(length, r_offset), offset + r_offset);
48}
49
50size_t OffsetVfsFile::Write(const u8* data, size_t length, size_t r_offset) {
51 return file->Write(data, TrimToFit(length, r_offset), offset + r_offset);
52}
53
54boost::optional<u8> OffsetVfsFile::ReadByte(size_t r_offset) const {
55 if (r_offset < size)
56 return file->ReadByte(offset + r_offset);
57
58 return boost::none;
59}
60
61std::vector<u8> OffsetVfsFile::ReadBytes(size_t r_size, size_t r_offset) const {
62 return file->ReadBytes(TrimToFit(r_size, r_offset), offset + r_offset);
63}
64
65std::vector<u8> OffsetVfsFile::ReadAllBytes() const {
66 return file->ReadBytes(size, offset);
67}
68
69bool OffsetVfsFile::WriteByte(u8 data, size_t r_offset) {
70 if (r_offset < size)
71 return file->WriteByte(data, offset + r_offset);
72
73 return false;
74}
75
76size_t OffsetVfsFile::WriteBytes(std::vector<u8> data, size_t r_offset) {
77 return file->Write(data.data(), TrimToFit(data.size(), r_offset), offset + r_offset);
78}
79
80bool OffsetVfsFile::Rename(const std::string& name) {
81 return file->Rename(name);
82}
83
84size_t OffsetVfsFile::GetOffset() const {
85 return offset;
86}
87
88size_t OffsetVfsFile::TrimToFit(size_t r_size, size_t r_offset) const {
89 return std::max<size_t>(std::min<size_t>(size - r_offset, r_size), 0);
90}
91
92} // namespace FileSys
diff --git a/src/core/file_sys/vfs_offset.h b/src/core/file_sys/vfs_offset.h
new file mode 100644
index 000000000..adc615b38
--- /dev/null
+++ b/src/core/file_sys/vfs_offset.h
@@ -0,0 +1,46 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "core/file_sys/vfs.h"
8
9namespace FileSys {
10
11// An implementation of VfsFile that wraps around another VfsFile at a certain offset.
12// Similar to seeking to an offset.
13// If the file is writable, operations that would write past the end of the offset file will expand
14// the size of this wrapper.
15struct OffsetVfsFile : public VfsFile {
16 OffsetVfsFile(std::shared_ptr<VfsFile> file, size_t size, size_t offset = 0,
17 const std::string& new_name = "");
18
19 std::string GetName() const override;
20 size_t GetSize() const override;
21 bool Resize(size_t new_size) override;
22 std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
23 bool IsWritable() const override;
24 bool IsReadable() const override;
25 size_t Read(u8* data, size_t length, size_t offset) const override;
26 size_t Write(const u8* data, size_t length, size_t offset) override;
27 boost::optional<u8> ReadByte(size_t offset) const override;
28 std::vector<u8> ReadBytes(size_t size, size_t offset) const override;
29 std::vector<u8> ReadAllBytes() const override;
30 bool WriteByte(u8 data, size_t offset) override;
31 size_t WriteBytes(std::vector<u8> data, size_t offset) override;
32
33 bool Rename(const std::string& name) override;
34
35 size_t GetOffset() const;
36
37private:
38 size_t TrimToFit(size_t r_size, size_t r_offset) const;
39
40 std::shared_ptr<VfsFile> file;
41 size_t offset;
42 size_t size;
43 std::string name;
44};
45
46} // namespace FileSys
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp
new file mode 100644
index 000000000..22c858e0d
--- /dev/null
+++ b/src/core/file_sys/vfs_real.cpp
@@ -0,0 +1,177 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/common_paths.h"
6#include "common/logging/log.h"
7#include "core/file_sys/vfs_real.h"
8
9namespace FileSys {
10
11static std::string PermissionsToCharArray(Mode perms) {
12 std::string out;
13 switch (perms) {
14 case Mode::Read:
15 out += "r";
16 break;
17 case Mode::Write:
18 out += "r+";
19 break;
20 case Mode::Append:
21 out += "a";
22 break;
23 }
24 return out + "b";
25}
26
27RealVfsFile::RealVfsFile(const std::string& path_, Mode perms_)
28 : backing(path_, PermissionsToCharArray(perms_).c_str()), path(path_),
29 parent_path(FileUtil::GetParentPath(path_)),
30 path_components(FileUtil::SplitPathComponents(path_)),
31 parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)),
32 perms(perms_) {}
33
34std::string RealVfsFile::GetName() const {
35 return path_components.back();
36}
37
38size_t RealVfsFile::GetSize() const {
39 return backing.GetSize();
40}
41
42bool RealVfsFile::Resize(size_t new_size) {
43 return backing.Resize(new_size);
44}
45
46std::shared_ptr<VfsDirectory> RealVfsFile::GetContainingDirectory() const {
47 return std::make_shared<RealVfsDirectory>(parent_path, perms);
48}
49
50bool RealVfsFile::IsWritable() const {
51 return perms == Mode::Append || perms == Mode::Write;
52}
53
54bool RealVfsFile::IsReadable() const {
55 return perms == Mode::Read || perms == Mode::Write;
56}
57
58size_t RealVfsFile::Read(u8* data, size_t length, size_t offset) const {
59 if (!backing.Seek(offset, SEEK_SET))
60 return 0;
61 return backing.ReadBytes(data, length);
62}
63
64size_t RealVfsFile::Write(const u8* data, size_t length, size_t offset) {
65 if (!backing.Seek(offset, SEEK_SET))
66 return 0;
67 return backing.WriteBytes(data, length);
68}
69
70bool RealVfsFile::Rename(const std::string& name) {
71 const auto out = FileUtil::Rename(GetName(), name);
72 path = parent_path + DIR_SEP + name;
73 path_components = parent_components;
74 path_components.push_back(name);
75 backing = FileUtil::IOFile(path, PermissionsToCharArray(perms).c_str());
76 return out;
77}
78
79bool RealVfsFile::Close() {
80 return backing.Close();
81}
82
83RealVfsDirectory::RealVfsDirectory(const std::string& path_, Mode perms_)
84 : path(FileUtil::RemoveTrailingSlash(path_)), parent_path(FileUtil::GetParentPath(path)),
85 path_components(FileUtil::SplitPathComponents(path)),
86 parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)),
87 perms(perms_) {
88 if (!FileUtil::Exists(path) && (perms == Mode::Write || perms == Mode::Append))
89 FileUtil::CreateDir(path);
90 unsigned size;
91 if (perms == Mode::Append)
92 return;
93
94 FileUtil::ForeachDirectoryEntry(
95 &size, path,
96 [this](unsigned* entries_out, const std::string& directory, const std::string& filename) {
97 std::string full_path = directory + DIR_SEP + filename;
98 if (FileUtil::IsDirectory(full_path))
99 subdirectories.emplace_back(std::make_shared<RealVfsDirectory>(full_path, perms));
100 else
101 files.emplace_back(std::make_shared<RealVfsFile>(full_path, perms));
102 return true;
103 });
104}
105
106std::vector<std::shared_ptr<VfsFile>> RealVfsDirectory::GetFiles() const {
107 return std::vector<std::shared_ptr<VfsFile>>(files);
108}
109
110std::vector<std::shared_ptr<VfsDirectory>> RealVfsDirectory::GetSubdirectories() const {
111 return std::vector<std::shared_ptr<VfsDirectory>>(subdirectories);
112}
113
114bool RealVfsDirectory::IsWritable() const {
115 return perms == Mode::Write || perms == Mode::Append;
116}
117
118bool RealVfsDirectory::IsReadable() const {
119 return perms == Mode::Read || perms == Mode::Write;
120}
121
122std::string RealVfsDirectory::GetName() const {
123 return path_components.back();
124}
125
126std::shared_ptr<VfsDirectory> RealVfsDirectory::GetParentDirectory() const {
127 if (path_components.size() <= 1)
128 return nullptr;
129
130 return std::make_shared<RealVfsDirectory>(parent_path, perms);
131}
132
133std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateSubdirectory(const std::string& name) {
134 if (!FileUtil::CreateDir(path + DIR_SEP + name))
135 return nullptr;
136 subdirectories.emplace_back(std::make_shared<RealVfsDirectory>(path + DIR_SEP + name, perms));
137 return subdirectories.back();
138}
139
140std::shared_ptr<VfsFile> RealVfsDirectory::CreateFile(const std::string& name) {
141 if (!FileUtil::CreateEmptyFile(path + DIR_SEP + name))
142 return nullptr;
143 files.emplace_back(std::make_shared<RealVfsFile>(path + DIR_SEP + name, perms));
144 return files.back();
145}
146
147bool RealVfsDirectory::DeleteSubdirectory(const std::string& name) {
148 return FileUtil::DeleteDirRecursively(path + DIR_SEP + name);
149}
150
151bool RealVfsDirectory::DeleteFile(const std::string& name) {
152 auto file = GetFile(name);
153 if (file == nullptr)
154 return false;
155 files.erase(std::find(files.begin(), files.end(), file));
156 auto real_file = std::static_pointer_cast<RealVfsFile>(file);
157 real_file->Close();
158 return FileUtil::Delete(path + DIR_SEP + name);
159}
160
161bool RealVfsDirectory::Rename(const std::string& name) {
162 return FileUtil::Rename(path, parent_path + DIR_SEP + name);
163}
164
165bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
166 auto iter = std::find(files.begin(), files.end(), file);
167 if (iter == files.end())
168 return false;
169
170 files[iter - files.begin()] = files.back();
171 files.pop_back();
172
173 subdirectories.emplace_back(dir);
174
175 return true;
176}
177} // namespace FileSys
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h
new file mode 100644
index 000000000..5b765a552
--- /dev/null
+++ b/src/core/file_sys/vfs_real.h
@@ -0,0 +1,69 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "common/file_util.h"
8#include "core/file_sys/mode.h"
9#include "core/file_sys/vfs.h"
10
11namespace FileSys {
12
13// An implmentation of VfsFile that represents a file on the user's computer.
14struct RealVfsFile : public VfsFile {
15 friend struct RealVfsDirectory;
16
17 RealVfsFile(const std::string& name, Mode perms = Mode::Read);
18
19 std::string GetName() const override;
20 size_t GetSize() const override;
21 bool Resize(size_t new_size) override;
22 std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
23 bool IsWritable() const override;
24 bool IsReadable() const override;
25 size_t Read(u8* data, size_t length, size_t offset) const override;
26 size_t Write(const u8* data, size_t length, size_t offset) override;
27 bool Rename(const std::string& name) override;
28
29private:
30 bool Close();
31
32 FileUtil::IOFile backing;
33 std::string path;
34 std::string parent_path;
35 std::vector<std::string> path_components;
36 std::vector<std::string> parent_components;
37 Mode perms;
38};
39
40// An implementation of VfsDirectory that represents a directory on the user's computer.
41struct RealVfsDirectory : public VfsDirectory {
42 RealVfsDirectory(const std::string& path, Mode perms);
43
44 std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
45 std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
46 bool IsWritable() const override;
47 bool IsReadable() const override;
48 std::string GetName() const override;
49 std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
50 std::shared_ptr<VfsDirectory> CreateSubdirectory(const std::string& name) override;
51 std::shared_ptr<VfsFile> CreateFile(const std::string& name) override;
52 bool DeleteSubdirectory(const std::string& name) override;
53 bool DeleteFile(const std::string& name) override;
54 bool Rename(const std::string& name) override;
55
56protected:
57 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
58
59private:
60 std::string path;
61 std::string parent_path;
62 std::vector<std::string> path_components;
63 std::vector<std::string> parent_components;
64 Mode perms;
65 std::vector<std::shared_ptr<VfsFile>> files;
66 std::vector<std::shared_ptr<VfsDirectory>> subdirectories;
67};
68
69} // namespace FileSys