summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/common_paths.h2
-rw-r--r--src/common/file_util.cpp2
-rw-r--r--src/common/file_util.h2
-rw-r--r--src/core/CMakeLists.txt5
-rw-r--r--src/core/file_sys/bis_factory.cpp12
-rw-r--r--src/core/file_sys/bis_factory.h5
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.cpp372
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.h70
-rw-r--r--src/core/file_sys/patch_manager.cpp47
-rw-r--r--src/core/file_sys/patch_manager.h2
-rw-r--r--src/core/file_sys/registered_cache.cpp9
-rw-r--r--src/core/file_sys/registered_cache.h2
-rw-r--r--src/core/file_sys/romfs.cpp21
-rw-r--r--src/core/file_sys/romfs.h15
-rw-r--r--src/core/file_sys/vfs.cpp45
-rw-r--r--src/core/file_sys/vfs.h14
-rw-r--r--src/core/file_sys/vfs_concat.cpp38
-rw-r--r--src/core/file_sys/vfs_concat.h40
-rw-r--r--src/core/file_sys/vfs_layered.cpp131
-rw-r--r--src/core/file_sys/vfs_layered.h52
-rw-r--r--src/core/file_sys/vfs_real.cpp17
-rw-r--r--src/core/file_sys/vfs_real.h1
-rw-r--r--src/core/file_sys/vfs_static.h78
-rw-r--r--src/core/file_sys/vfs_vector.cpp54
-rw-r--r--src/core/file_sys/vfs_vector.h25
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp13
-rw-r--r--src/core/hle/service/filesystem/filesystem.h2
-rw-r--r--src/yuzu/game_list.cpp9
-rw-r--r--src/yuzu/game_list.h7
-rw-r--r--src/yuzu/main.cpp145
-rw-r--r--src/yuzu/main.h2
31 files changed, 1203 insertions, 36 deletions
diff --git a/src/common/common_paths.h b/src/common/common_paths.h
index df2ce80b1..4f88de768 100644
--- a/src/common/common_paths.h
+++ b/src/common/common_paths.h
@@ -33,6 +33,8 @@
33#define NAND_DIR "nand" 33#define NAND_DIR "nand"
34#define SYSDATA_DIR "sysdata" 34#define SYSDATA_DIR "sysdata"
35#define KEYS_DIR "keys" 35#define KEYS_DIR "keys"
36#define LOAD_DIR "load"
37#define DUMP_DIR "dump"
36#define LOG_DIR "log" 38#define LOG_DIR "log"
37 39
38// Filenames 40// Filenames
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 21a0b9738..548463787 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -705,6 +705,8 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
705#endif 705#endif
706 paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP); 706 paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP);
707 paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP); 707 paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP);
708 paths.emplace(UserPath::LoadDir, user_path + LOAD_DIR DIR_SEP);
709 paths.emplace(UserPath::DumpDir, user_path + DUMP_DIR DIR_SEP);
708 paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP); 710 paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP);
709 paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP); 711 paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP);
710 // TODO: Put the logs in a better location for each OS 712 // TODO: Put the logs in a better location for each OS
diff --git a/src/common/file_util.h b/src/common/file_util.h
index 24c1e413c..3d8fe6264 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -29,6 +29,8 @@ enum class UserPath {
29 NANDDir, 29 NANDDir,
30 RootDir, 30 RootDir,
31 SDMCDir, 31 SDMCDir,
32 LoadDir,
33 DumpDir,
32 SysDataDir, 34 SysDataDir,
33 UserDir, 35 UserDir,
34}; 36};
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 26f727d96..23fd6e920 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -32,6 +32,8 @@ add_library(core STATIC
32 file_sys/control_metadata.h 32 file_sys/control_metadata.h
33 file_sys/directory.h 33 file_sys/directory.h
34 file_sys/errors.h 34 file_sys/errors.h
35 file_sys/fsmitm_romfsbuild.cpp
36 file_sys/fsmitm_romfsbuild.h
35 file_sys/mode.h 37 file_sys/mode.h
36 file_sys/nca_metadata.cpp 38 file_sys/nca_metadata.cpp
37 file_sys/nca_metadata.h 39 file_sys/nca_metadata.h
@@ -59,10 +61,13 @@ add_library(core STATIC
59 file_sys/vfs.h 61 file_sys/vfs.h
60 file_sys/vfs_concat.cpp 62 file_sys/vfs_concat.cpp
61 file_sys/vfs_concat.h 63 file_sys/vfs_concat.h
64 file_sys/vfs_layered.cpp
65 file_sys/vfs_layered.h
62 file_sys/vfs_offset.cpp 66 file_sys/vfs_offset.cpp
63 file_sys/vfs_offset.h 67 file_sys/vfs_offset.h
64 file_sys/vfs_real.cpp 68 file_sys/vfs_real.cpp
65 file_sys/vfs_real.h 69 file_sys/vfs_real.h
70 file_sys/vfs_static.h
66 file_sys/vfs_vector.cpp 71 file_sys/vfs_vector.cpp
67 file_sys/vfs_vector.h 72 file_sys/vfs_vector.h
68 file_sys/xts_archive.cpp 73 file_sys/xts_archive.cpp
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index 205492897..6102ef476 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -2,13 +2,14 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <fmt/format.h>
5#include "core/file_sys/bis_factory.h" 6#include "core/file_sys/bis_factory.h"
6#include "core/file_sys/registered_cache.h" 7#include "core/file_sys/registered_cache.h"
7 8
8namespace FileSys { 9namespace FileSys {
9 10
10BISFactory::BISFactory(VirtualDir nand_root_) 11BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_)
11 : nand_root(std::move(nand_root_)), 12 : nand_root(std::move(nand_root_)), load_root(std::move(load_root_)),
12 sysnand_cache(std::make_shared<RegisteredCache>( 13 sysnand_cache(std::make_shared<RegisteredCache>(
13 GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))), 14 GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
14 usrnand_cache(std::make_shared<RegisteredCache>( 15 usrnand_cache(std::make_shared<RegisteredCache>(
@@ -24,4 +25,11 @@ std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const {
24 return usrnand_cache; 25 return usrnand_cache;
25} 26}
26 27
28VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const {
29 // LayeredFS doesn't work on updates and title id-less homebrew
30 if (title_id == 0 || (title_id & 0x800) > 0)
31 return nullptr;
32 return GetOrCreateDirectoryRelative(load_root, fmt::format("/{:016X}", title_id));
33}
34
27} // namespace FileSys 35} // namespace FileSys
diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h
index 9523dd864..c352e0925 100644
--- a/src/core/file_sys/bis_factory.h
+++ b/src/core/file_sys/bis_factory.h
@@ -17,14 +17,17 @@ class RegisteredCache;
17/// registered caches. 17/// registered caches.
18class BISFactory { 18class BISFactory {
19public: 19public:
20 explicit BISFactory(VirtualDir nand_root); 20 explicit BISFactory(VirtualDir nand_root, VirtualDir load_root);
21 ~BISFactory(); 21 ~BISFactory();
22 22
23 std::shared_ptr<RegisteredCache> GetSystemNANDContents() const; 23 std::shared_ptr<RegisteredCache> GetSystemNANDContents() const;
24 std::shared_ptr<RegisteredCache> GetUserNANDContents() const; 24 std::shared_ptr<RegisteredCache> GetUserNANDContents() const;
25 25
26 VirtualDir GetModificationLoadRoot(u64 title_id) const;
27
26private: 28private:
27 VirtualDir nand_root; 29 VirtualDir nand_root;
30 VirtualDir load_root;
28 31
29 std::shared_ptr<RegisteredCache> sysnand_cache; 32 std::shared_ptr<RegisteredCache> sysnand_cache;
30 std::shared_ptr<RegisteredCache> usrnand_cache; 33 std::shared_ptr<RegisteredCache> usrnand_cache;
diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp
new file mode 100644
index 000000000..21fc3d796
--- /dev/null
+++ b/src/core/file_sys/fsmitm_romfsbuild.cpp
@@ -0,0 +1,372 @@
1/*
2 * Copyright (c) 2018 Atmosphère-NX
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17/*
18 * Adapted by DarkLordZach for use/interaction with yuzu
19 *
20 * Modifications Copyright 2018 yuzu emulator team
21 * Licensed under GPLv2 or any later version
22 * Refer to the license.txt file included.
23 */
24
25#include <cstring>
26#include "common/assert.h"
27#include "core/file_sys/fsmitm_romfsbuild.h"
28#include "core/file_sys/vfs.h"
29#include "core/file_sys/vfs_vector.h"
30
31namespace FileSys {
32
33constexpr u64 FS_MAX_PATH = 0x301;
34
35constexpr u32 ROMFS_ENTRY_EMPTY = 0xFFFFFFFF;
36constexpr u32 ROMFS_FILEPARTITION_OFS = 0x200;
37
38// Types for building a RomFS.
39struct RomFSHeader {
40 u64 header_size;
41 u64 dir_hash_table_ofs;
42 u64 dir_hash_table_size;
43 u64 dir_table_ofs;
44 u64 dir_table_size;
45 u64 file_hash_table_ofs;
46 u64 file_hash_table_size;
47 u64 file_table_ofs;
48 u64 file_table_size;
49 u64 file_partition_ofs;
50};
51static_assert(sizeof(RomFSHeader) == 0x50, "RomFSHeader has incorrect size.");
52
53struct RomFSDirectoryEntry {
54 u32 parent;
55 u32 sibling;
56 u32 child;
57 u32 file;
58 u32 hash;
59 u32 name_size;
60};
61static_assert(sizeof(RomFSDirectoryEntry) == 0x18, "RomFSDirectoryEntry has incorrect size.");
62
63struct RomFSFileEntry {
64 u32 parent;
65 u32 sibling;
66 u64 offset;
67 u64 size;
68 u32 hash;
69 u32 name_size;
70};
71static_assert(sizeof(RomFSFileEntry) == 0x20, "RomFSFileEntry has incorrect size.");
72
73struct RomFSBuildFileContext;
74
75struct RomFSBuildDirectoryContext {
76 std::string path = "";
77 u32 cur_path_ofs = 0;
78 u32 path_len = 0;
79 u32 entry_offset = 0;
80 std::shared_ptr<RomFSBuildDirectoryContext> parent;
81 std::shared_ptr<RomFSBuildDirectoryContext> child;
82 std::shared_ptr<RomFSBuildDirectoryContext> sibling;
83 std::shared_ptr<RomFSBuildFileContext> file;
84};
85
86struct RomFSBuildFileContext {
87 std::string path = "";
88 u32 cur_path_ofs = 0;
89 u32 path_len = 0;
90 u32 entry_offset = 0;
91 u64 offset = 0;
92 u64 size = 0;
93 std::shared_ptr<RomFSBuildDirectoryContext> parent;
94 std::shared_ptr<RomFSBuildFileContext> sibling;
95 VirtualFile source = nullptr;
96
97 RomFSBuildFileContext() : path(""), cur_path_ofs(0), path_len(0) {}
98};
99
100static u32 romfs_calc_path_hash(u32 parent, std::string path, u32 start, size_t path_len) {
101 u32 hash = parent ^ 123456789;
102 for (u32 i = 0; i < path_len; i++) {
103 hash = (hash >> 5) | (hash << 27);
104 hash ^= path[start + i];
105 }
106
107 return hash;
108}
109
110static u32 romfs_get_hash_table_count(u32 num_entries) {
111 if (num_entries < 3) {
112 return 3;
113 } else if (num_entries < 19) {
114 return num_entries | 1;
115 }
116 u32 count = num_entries;
117 while (count % 2 == 0 || count % 3 == 0 || count % 5 == 0 || count % 7 == 0 ||
118 count % 11 == 0 || count % 13 == 0 || count % 17 == 0) {
119 count++;
120 }
121 return count;
122}
123
124void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
125 std::shared_ptr<RomFSBuildDirectoryContext> parent) {
126 std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> child_dirs;
127
128 VirtualDir dir;
129
130 if (parent->path_len == 0)
131 dir = root_romfs;
132 else
133 dir = root_romfs->GetDirectoryRelative(parent->path);
134
135 const auto entries = dir->GetEntries();
136
137 for (const auto& kv : entries) {
138 if (kv.second == VfsEntryType::Directory) {
139 const auto child = std::make_shared<RomFSBuildDirectoryContext>();
140 // Set child's path.
141 child->cur_path_ofs = parent->path_len + 1;
142 child->path_len = child->cur_path_ofs + kv.first.size();
143 child->path = parent->path + "/" + kv.first;
144
145 // Sanity check on path_len
146 ASSERT(child->path_len < FS_MAX_PATH);
147
148 if (AddDirectory(parent, child)) {
149 child_dirs.push_back(child);
150 }
151 } else {
152 const auto child = std::make_shared<RomFSBuildFileContext>();
153 // Set child's path.
154 child->cur_path_ofs = parent->path_len + 1;
155 child->path_len = child->cur_path_ofs + kv.first.size();
156 child->path = parent->path + "/" + kv.first;
157
158 // Sanity check on path_len
159 ASSERT(child->path_len < FS_MAX_PATH);
160
161 child->source = root_romfs->GetFileRelative(child->path);
162
163 child->size = child->source->GetSize();
164
165 AddFile(parent, child);
166 }
167 }
168
169 for (auto& child : child_dirs) {
170 this->VisitDirectory(root_romfs, child);
171 }
172}
173
174bool RomFSBuildContext::AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
175 std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx) {
176 // Check whether it's already in the known directories.
177 const auto existing = directories.find(dir_ctx->path);
178 if (existing != directories.end())
179 return false;
180
181 // Add a new directory.
182 num_dirs++;
183 dir_table_size +=
184 sizeof(RomFSDirectoryEntry) + ((dir_ctx->path_len - dir_ctx->cur_path_ofs + 3) & ~3);
185 dir_ctx->parent = parent_dir_ctx;
186 directories.emplace(dir_ctx->path, dir_ctx);
187
188 return true;
189}
190
191bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
192 std::shared_ptr<RomFSBuildFileContext> file_ctx) {
193 // Check whether it's already in the known files.
194 const auto existing = files.find(file_ctx->path);
195 if (existing != files.end()) {
196 return false;
197 }
198
199 // Add a new file.
200 num_files++;
201 file_table_size +=
202 sizeof(RomFSFileEntry) + ((file_ctx->path_len - file_ctx->cur_path_ofs + 3) & ~3);
203 file_ctx->parent = parent_dir_ctx;
204 files.emplace(file_ctx->path, file_ctx);
205
206 return true;
207}
208
209RomFSBuildContext::RomFSBuildContext(VirtualDir base_) : base(std::move(base_)) {
210 root = std::make_shared<RomFSBuildDirectoryContext>();
211 root->path = "\0";
212 directories.emplace(root->path, root);
213 num_dirs = 1;
214 dir_table_size = 0x18;
215
216 VisitDirectory(base, root);
217}
218
219RomFSBuildContext::~RomFSBuildContext() = default;
220
221std::map<u64, VirtualFile> RomFSBuildContext::Build() {
222 const auto dir_hash_table_entry_count = romfs_get_hash_table_count(num_dirs);
223 const auto file_hash_table_entry_count = romfs_get_hash_table_count(num_files);
224 dir_hash_table_size = 4 * dir_hash_table_entry_count;
225 file_hash_table_size = 4 * file_hash_table_entry_count;
226
227 // Assign metadata pointers
228 RomFSHeader header{};
229
230 std::vector<u32> dir_hash_table(dir_hash_table_entry_count, ROMFS_ENTRY_EMPTY);
231 std::vector<u32> file_hash_table(file_hash_table_entry_count, ROMFS_ENTRY_EMPTY);
232
233 std::vector<u8> dir_table(dir_table_size);
234 std::vector<u8> file_table(file_table_size);
235
236 // Clear out hash tables.
237 for (u32 i = 0; i < dir_hash_table_entry_count; i++)
238 dir_hash_table[i] = ROMFS_ENTRY_EMPTY;
239 for (u32 i = 0; i < file_hash_table_entry_count; i++)
240 file_hash_table[i] = ROMFS_ENTRY_EMPTY;
241
242 std::shared_ptr<RomFSBuildFileContext> cur_file;
243
244 // Determine file offsets.
245 u32 entry_offset = 0;
246 std::shared_ptr<RomFSBuildFileContext> prev_file = nullptr;
247 for (const auto& it : files) {
248 cur_file = it.second;
249 file_partition_size = (file_partition_size + 0xFULL) & ~0xFULL;
250 cur_file->offset = file_partition_size;
251 file_partition_size += cur_file->size;
252 cur_file->entry_offset = entry_offset;
253 entry_offset +=
254 sizeof(RomFSFileEntry) + ((cur_file->path_len - cur_file->cur_path_ofs + 3) & ~3);
255 prev_file = cur_file;
256 }
257 // Assign deferred parent/sibling ownership.
258 for (auto it = files.rbegin(); it != files.rend(); ++it) {
259 cur_file = it->second;
260 cur_file->sibling = cur_file->parent->file;
261 cur_file->parent->file = cur_file;
262 }
263
264 std::shared_ptr<RomFSBuildDirectoryContext> cur_dir;
265
266 // Determine directory offsets.
267 entry_offset = 0;
268 for (const auto& it : directories) {
269 cur_dir = it.second;
270 cur_dir->entry_offset = entry_offset;
271 entry_offset +=
272 sizeof(RomFSDirectoryEntry) + ((cur_dir->path_len - cur_dir->cur_path_ofs + 3) & ~3);
273 }
274 // Assign deferred parent/sibling ownership.
275 for (auto it = directories.rbegin(); it->second != root; ++it) {
276 cur_dir = it->second;
277 cur_dir->sibling = cur_dir->parent->child;
278 cur_dir->parent->child = cur_dir;
279 }
280
281 std::map<u64, VirtualFile> out;
282
283 // Populate file tables.
284 for (const auto& it : files) {
285 cur_file = it.second;
286 RomFSFileEntry cur_entry{};
287
288 cur_entry.parent = cur_file->parent->entry_offset;
289 cur_entry.sibling =
290 cur_file->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_file->sibling->entry_offset;
291 cur_entry.offset = cur_file->offset;
292 cur_entry.size = cur_file->size;
293
294 const auto name_size = cur_file->path_len - cur_file->cur_path_ofs;
295 const auto hash = romfs_calc_path_hash(cur_file->parent->entry_offset, cur_file->path,
296 cur_file->cur_path_ofs, name_size);
297 cur_entry.hash = file_hash_table[hash % file_hash_table_entry_count];
298 file_hash_table[hash % file_hash_table_entry_count] = cur_file->entry_offset;
299
300 cur_entry.name_size = name_size;
301
302 out.emplace(cur_file->offset + ROMFS_FILEPARTITION_OFS, cur_file->source);
303 std::memcpy(file_table.data() + cur_file->entry_offset, &cur_entry, sizeof(RomFSFileEntry));
304 std::memset(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry), 0,
305 (cur_entry.name_size + 3) & ~3);
306 std::memcpy(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry),
307 cur_file->path.data() + cur_file->cur_path_ofs, name_size);
308 }
309
310 // Populate dir tables.
311 for (const auto& it : directories) {
312 cur_dir = it.second;
313 RomFSDirectoryEntry cur_entry{};
314
315 cur_entry.parent = cur_dir == root ? 0 : cur_dir->parent->entry_offset;
316 cur_entry.sibling =
317 cur_dir->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->sibling->entry_offset;
318 cur_entry.child =
319 cur_dir->child == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->child->entry_offset;
320 cur_entry.file = cur_dir->file == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->file->entry_offset;
321
322 const auto name_size = cur_dir->path_len - cur_dir->cur_path_ofs;
323 const auto hash = romfs_calc_path_hash(cur_dir == root ? 0 : cur_dir->parent->entry_offset,
324 cur_dir->path, cur_dir->cur_path_ofs, name_size);
325 cur_entry.hash = dir_hash_table[hash % dir_hash_table_entry_count];
326 dir_hash_table[hash % dir_hash_table_entry_count] = cur_dir->entry_offset;
327
328 cur_entry.name_size = name_size;
329
330 std::memcpy(dir_table.data() + cur_dir->entry_offset, &cur_entry,
331 sizeof(RomFSDirectoryEntry));
332 std::memcpy(dir_table.data() + cur_dir->entry_offset, &cur_entry,
333 sizeof(RomFSDirectoryEntry));
334 std::memset(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry), 0,
335 (cur_entry.name_size + 3) & ~3);
336 std::memcpy(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry),
337 cur_dir->path.data() + cur_dir->cur_path_ofs, name_size);
338 }
339
340 // Set header fields.
341 header.header_size = sizeof(RomFSHeader);
342 header.file_hash_table_size = file_hash_table_size;
343 header.file_table_size = file_table_size;
344 header.dir_hash_table_size = dir_hash_table_size;
345 header.dir_table_size = dir_table_size;
346 header.file_partition_ofs = ROMFS_FILEPARTITION_OFS;
347 header.dir_hash_table_ofs = (header.file_partition_ofs + file_partition_size + 3ULL) & ~3ULL;
348 header.dir_table_ofs = header.dir_hash_table_ofs + header.dir_hash_table_size;
349 header.file_hash_table_ofs = header.dir_table_ofs + header.dir_table_size;
350 header.file_table_ofs = header.file_hash_table_ofs + header.file_hash_table_size;
351
352 std::vector<u8> header_data(sizeof(RomFSHeader));
353 std::memcpy(header_data.data(), &header, header_data.size());
354 out.emplace(0, std::make_shared<VectorVfsFile>(header_data));
355
356 std::vector<u8> metadata(file_hash_table_size + file_table_size + dir_hash_table_size +
357 dir_table_size);
358 auto index = 0;
359 std::memcpy(metadata.data(), dir_hash_table.data(), dir_hash_table.size() * sizeof(u32));
360 index += dir_hash_table.size() * sizeof(u32);
361 std::memcpy(metadata.data() + index, dir_table.data(), dir_table.size());
362 index += dir_table.size();
363 std::memcpy(metadata.data() + index, file_hash_table.data(),
364 file_hash_table.size() * sizeof(u32));
365 index += file_hash_table.size() * sizeof(u32);
366 std::memcpy(metadata.data() + index, file_table.data(), file_table.size());
367 out.emplace(header.dir_hash_table_ofs, std::make_shared<VectorVfsFile>(metadata));
368
369 return out;
370}
371
372} // namespace FileSys
diff --git a/src/core/file_sys/fsmitm_romfsbuild.h b/src/core/file_sys/fsmitm_romfsbuild.h
new file mode 100644
index 000000000..b0c3c123b
--- /dev/null
+++ b/src/core/file_sys/fsmitm_romfsbuild.h
@@ -0,0 +1,70 @@
1/*
2 * Copyright (c) 2018 Atmosphère-NX
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17/*
18 * Adapted by DarkLordZach for use/interaction with yuzu
19 *
20 * Modifications Copyright 2018 yuzu emulator team
21 * Licensed under GPLv2 or any later version
22 * Refer to the license.txt file included.
23 */
24
25#pragma once
26
27#include <map>
28#include <memory>
29#include <string>
30#include <boost/detail/container_fwd.hpp>
31#include "common/common_types.h"
32#include "core/file_sys/vfs.h"
33
34namespace FileSys {
35
36struct RomFSBuildDirectoryContext;
37struct RomFSBuildFileContext;
38struct RomFSDirectoryEntry;
39struct RomFSFileEntry;
40
41class RomFSBuildContext {
42public:
43 explicit RomFSBuildContext(VirtualDir base);
44 ~RomFSBuildContext();
45
46 // This finalizes the context.
47 std::map<u64, VirtualFile> Build();
48
49private:
50 VirtualDir base;
51 std::shared_ptr<RomFSBuildDirectoryContext> root;
52 std::map<std::string, std::shared_ptr<RomFSBuildDirectoryContext>, std::less<>> directories;
53 std::map<std::string, std::shared_ptr<RomFSBuildFileContext>, std::less<>> files;
54 u64 num_dirs = 0;
55 u64 num_files = 0;
56 u64 dir_table_size = 0;
57 u64 file_table_size = 0;
58 u64 dir_hash_table_size = 0;
59 u64 file_hash_table_size = 0;
60 u64 file_partition_size = 0;
61
62 void VisitDirectory(VirtualDir filesys, std::shared_ptr<RomFSBuildDirectoryContext> parent);
63
64 bool AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
65 std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx);
66 bool AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
67 std::shared_ptr<RomFSBuildFileContext> file_ctx);
68};
69
70} // namespace FileSys
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index aebc69d52..af3f9a78f 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -11,6 +11,7 @@
11#include "core/file_sys/patch_manager.h" 11#include "core/file_sys/patch_manager.h"
12#include "core/file_sys/registered_cache.h" 12#include "core/file_sys/registered_cache.h"
13#include "core/file_sys/romfs.h" 13#include "core/file_sys/romfs.h"
14#include "core/file_sys/vfs_layered.h"
14#include "core/hle/service/filesystem/filesystem.h" 15#include "core/hle/service/filesystem/filesystem.h"
15#include "core/loader/loader.h" 16#include "core/loader/loader.h"
16 17
@@ -31,8 +32,9 @@ std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
31 return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]); 32 return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
32} 33}
33 34
34constexpr std::array<const char*, 1> PATCH_TYPE_NAMES{ 35constexpr std::array<const char*, 2> PATCH_TYPE_NAMES{
35 "Update", 36 "Update",
37 "LayeredFS",
36}; 38};
37 39
38std::string FormatPatchTypeName(PatchType type) { 40std::string FormatPatchTypeName(PatchType type) {
@@ -66,6 +68,42 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
66 return exefs; 68 return exefs;
67} 69}
68 70
71static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
72 const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
73 if (type == ContentRecordType::Program && load_dir != nullptr && load_dir->GetSize() > 0) {
74 auto extracted = ExtractRomFS(romfs);
75
76 if (extracted != nullptr) {
77 auto patch_dirs = load_dir->GetSubdirectories();
78 std::sort(patch_dirs.begin(), patch_dirs.end(),
79 [](const VirtualDir& l, const VirtualDir& r) {
80 return l->GetName() < r->GetName();
81 });
82
83 std::vector<VirtualDir> layers;
84 layers.reserve(patch_dirs.size() + 1);
85 for (const auto& subdir : patch_dirs) {
86 auto romfs_dir = subdir->GetSubdirectory("romfs");
87 if (romfs_dir != nullptr)
88 layers.push_back(std::move(romfs_dir));
89 }
90
91 layers.push_back(std::move(extracted));
92
93 const auto layered = LayerDirectories(layers);
94
95 if (layered != nullptr) {
96 auto packed = CreateRomFS(layered);
97
98 if (packed != nullptr) {
99 LOG_INFO(Loader, " RomFS: LayeredFS patches applied successfully");
100 romfs = std::move(packed);
101 }
102 }
103 }
104 }
105}
106
69VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, 107VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
70 ContentRecordType type) const { 108 ContentRecordType type) const {
71 LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id, 109 LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id,
@@ -89,6 +127,9 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
89 } 127 }
90 } 128 }
91 129
130 // LayeredFS
131 ApplyLayeredFS(romfs, title_id, type);
132
92 return romfs; 133 return romfs;
93} 134}
94 135
@@ -114,6 +155,10 @@ std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const {
114 } 155 }
115 } 156 }
116 157
158 const auto lfs_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
159 if (lfs_dir != nullptr && lfs_dir->GetSize() > 0)
160 out.insert_or_assign(PatchType::LayeredFS, "");
161
117 return out; 162 return out;
118} 163}
119 164
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index 209cab1dc..464f17515 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -26,6 +26,7 @@ std::string FormatTitleVersion(u32 version,
26 26
27enum class PatchType { 27enum class PatchType {
28 Update, 28 Update,
29 LayeredFS,
29}; 30};
30 31
31std::string FormatPatchTypeName(PatchType type); 32std::string FormatPatchTypeName(PatchType type);
@@ -42,6 +43,7 @@ public:
42 43
43 // Currently tracked RomFS patches: 44 // Currently tracked RomFS patches:
44 // - Game Updates 45 // - Game Updates
46 // - LayeredFS
45 VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, 47 VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
46 ContentRecordType type = ContentRecordType::Program) const; 48 ContentRecordType type = ContentRecordType::Program) const;
47 49
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index dad7ae10b..653ef2e7b 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -18,6 +18,10 @@
18#include "core/loader/loader.h" 18#include "core/loader/loader.h"
19 19
20namespace FileSys { 20namespace FileSys {
21
22// The size of blocks to use when vfs raw copying into nand.
23constexpr size_t VFS_RC_LARGE_COPY_BLOCK = 0x400000;
24
21std::string RegisteredCacheEntry::DebugInfo() const { 25std::string RegisteredCacheEntry::DebugInfo() const {
22 return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type)); 26 return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type));
23} 27}
@@ -121,7 +125,7 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
121 if (concat.empty()) 125 if (concat.empty())
122 return nullptr; 126 return nullptr;
123 127
124 file = FileSys::ConcatenateFiles(concat); 128 file = FileSys::ConcatenateFiles(concat, concat.front()->GetName());
125 } 129 }
126 130
127 return file; 131 return file;
@@ -480,7 +484,8 @@ InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const Vfs
480 auto out = dir->CreateFileRelative(path); 484 auto out = dir->CreateFileRelative(path);
481 if (out == nullptr) 485 if (out == nullptr)
482 return InstallResult::ErrorCopyFailed; 486 return InstallResult::ErrorCopyFailed;
483 return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed; 487 return copy(in, out, VFS_RC_LARGE_COPY_BLOCK) ? InstallResult::Success
488 : InstallResult::ErrorCopyFailed;
484} 489}
485 490
486bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) { 491bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index f487b0cf0..c0cd59fc5 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -27,7 +27,7 @@ struct ContentRecord;
27 27
28using NcaID = std::array<u8, 0x10>; 28using NcaID = std::array<u8, 0x10>;
29using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; 29using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
30using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>; 30using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>;
31 31
32enum class InstallResult { 32enum class InstallResult {
33 Success, 33 Success,
diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp
index 9f6e41cdf..205284a4d 100644
--- a/src/core/file_sys/romfs.cpp
+++ b/src/core/file_sys/romfs.cpp
@@ -4,8 +4,10 @@
4 4
5#include "common/common_types.h" 5#include "common/common_types.h"
6#include "common/swap.h" 6#include "common/swap.h"
7#include "core/file_sys/fsmitm_romfsbuild.h"
7#include "core/file_sys/romfs.h" 8#include "core/file_sys/romfs.h"
8#include "core/file_sys/vfs.h" 9#include "core/file_sys/vfs.h"
10#include "core/file_sys/vfs_concat.h"
9#include "core/file_sys/vfs_offset.h" 11#include "core/file_sys/vfs_offset.h"
10#include "core/file_sys/vfs_vector.h" 12#include "core/file_sys/vfs_vector.h"
11 13
@@ -98,7 +100,7 @@ void ProcessDirectory(VirtualFile file, std::size_t dir_offset, std::size_t file
98 } 100 }
99} 101}
100 102
101VirtualDir ExtractRomFS(VirtualFile file) { 103VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
102 RomFSHeader header{}; 104 RomFSHeader header{};
103 if (file->ReadObject(&header) != sizeof(RomFSHeader)) 105 if (file->ReadObject(&header) != sizeof(RomFSHeader))
104 return nullptr; 106 return nullptr;
@@ -117,9 +119,22 @@ VirtualDir ExtractRomFS(VirtualFile file) {
117 119
118 VirtualDir out = std::move(root); 120 VirtualDir out = std::move(root);
119 121
120 while (out->GetSubdirectory("") != nullptr) 122 while (out->GetSubdirectories().size() == 1 && out->GetFiles().empty()) {
121 out = out->GetSubdirectory(""); 123 if (out->GetSubdirectories().front()->GetName() == "data" &&
124 type == RomFSExtractionType::Truncated)
125 break;
126 out = out->GetSubdirectories().front();
127 }
122 128
123 return out; 129 return out;
124} 130}
131
132VirtualFile CreateRomFS(VirtualDir dir) {
133 if (dir == nullptr)
134 return nullptr;
135
136 RomFSBuildContext ctx{dir};
137 return ConcatenateFiles<0>(ctx.Build(), dir->GetName());
138}
139
125} // namespace FileSys 140} // namespace FileSys
diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h
index e54a7d7a9..ecd1eb725 100644
--- a/src/core/file_sys/romfs.h
+++ b/src/core/file_sys/romfs.h
@@ -5,6 +5,7 @@
5#pragma once 5#pragma once
6 6
7#include <array> 7#include <array>
8#include <map>
8#include "common/common_funcs.h" 9#include "common/common_funcs.h"
9#include "common/common_types.h" 10#include "common/common_types.h"
10#include "common/swap.h" 11#include "common/swap.h"
@@ -12,6 +13,8 @@
12 13
13namespace FileSys { 14namespace FileSys {
14 15
16struct RomFSHeader;
17
15struct IVFCLevel { 18struct IVFCLevel {
16 u64_le offset; 19 u64_le offset;
17 u64_le size; 20 u64_le size;
@@ -29,8 +32,18 @@ struct IVFCHeader {
29}; 32};
30static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size."); 33static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
31 34
35enum class RomFSExtractionType {
36 Full, // Includes data directory
37 Truncated, // Traverses into data directory
38};
39
32// Converts a RomFS binary blob to VFS Filesystem 40// Converts a RomFS binary blob to VFS Filesystem
33// Returns nullptr on failure 41// Returns nullptr on failure
34VirtualDir ExtractRomFS(VirtualFile file); 42VirtualDir ExtractRomFS(VirtualFile file,
43 RomFSExtractionType type = RomFSExtractionType::Truncated);
44
45// Converts a VFS filesystem into a RomFS binary
46// Returns nullptr on failure
47VirtualFile CreateRomFS(VirtualDir dir);
35 48
36} // namespace FileSys 49} // namespace FileSys
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
index d7b52abfd..5fbea1739 100644
--- a/src/core/file_sys/vfs.cpp
+++ b/src/core/file_sys/vfs.cpp
@@ -399,6 +399,15 @@ bool VfsDirectory::Copy(std::string_view src, std::string_view dest) {
399 return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize(); 399 return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize();
400} 400}
401 401
402std::map<std::string, VfsEntryType, std::less<>> VfsDirectory::GetEntries() const {
403 std::map<std::string, VfsEntryType, std::less<>> out;
404 for (const auto& dir : GetSubdirectories())
405 out.emplace(dir->GetName(), VfsEntryType::Directory);
406 for (const auto& file : GetFiles())
407 out.emplace(file->GetName(), VfsEntryType::File);
408 return out;
409}
410
402std::string VfsDirectory::GetFullPath() const { 411std::string VfsDirectory::GetFullPath() const {
403 if (IsRoot()) 412 if (IsRoot())
404 return GetName(); 413 return GetName();
@@ -454,13 +463,41 @@ bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t
454 return true; 463 return true;
455} 464}
456 465
457bool VfsRawCopy(VirtualFile src, VirtualFile dest) { 466bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, size_t block_size) {
458 if (src == nullptr || dest == nullptr) 467 if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
459 return false; 468 return false;
460 if (!dest->Resize(src->GetSize())) 469 if (!dest->Resize(src->GetSize()))
461 return false; 470 return false;
462 std::vector<u8> data = src->ReadAllBytes(); 471
463 return dest->WriteBytes(data, 0) == data.size(); 472 std::vector<u8> temp(std::min(block_size, src->GetSize()));
473 for (size_t i = 0; i < src->GetSize(); i += block_size) {
474 const auto read = std::min(block_size, src->GetSize() - i);
475 const auto block = src->Read(temp.data(), read, i);
476
477 if (dest->Write(temp.data(), read, i) != read)
478 return false;
479 }
480
481 return true;
482}
483
484bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, size_t block_size) {
485 if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
486 return false;
487
488 for (const auto& file : src->GetFiles()) {
489 const auto out = dest->CreateFile(file->GetName());
490 if (!VfsRawCopy(file, out, block_size))
491 return false;
492 }
493
494 for (const auto& dir : src->GetSubdirectories()) {
495 const auto out = dest->CreateSubdirectory(dir->GetName());
496 if (!VfsRawCopyD(dir, out, block_size))
497 return false;
498 }
499
500 return true;
464} 501}
465 502
466VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) { 503VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) {
diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h
index 74489b452..cea4aa8b8 100644
--- a/src/core/file_sys/vfs.h
+++ b/src/core/file_sys/vfs.h
@@ -4,6 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <map>
7#include <memory> 8#include <memory>
8#include <string> 9#include <string>
9#include <string_view> 10#include <string_view>
@@ -265,6 +266,10 @@ public:
265 // dest. 266 // dest.
266 virtual bool Copy(std::string_view src, std::string_view dest); 267 virtual bool Copy(std::string_view src, std::string_view dest);
267 268
269 // Gets all of the entries directly in the directory (files and dirs), returning a map between
270 // item name -> type.
271 virtual std::map<std::string, VfsEntryType, std::less<>> GetEntries() const;
272
268 // Interprets the file with name file instead as a directory of type directory. 273 // Interprets the file with name file instead as a directory of type directory.
269 // The directory must have a constructor that takes a single argument of type 274 // The directory must have a constructor that takes a single argument of type
270 // std::shared_ptr<VfsFile>. Allows to reinterpret container files (i.e NCA, zip, XCI, etc) as a 275 // std::shared_ptr<VfsFile>. Allows to reinterpret container files (i.e NCA, zip, XCI, etc) as a
@@ -311,12 +316,17 @@ public:
311}; 316};
312 317
313// Compare the two files, byte-for-byte, in increments specificed by block_size 318// Compare the two files, byte-for-byte, in increments specificed by block_size
314bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t block_size = 0x200); 319bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size = 0x1000);
315 320
316// A method that copies the raw data between two different implementations of VirtualFile. If you 321// A method that copies the raw data between two different implementations of VirtualFile. If you
317// are using the same implementation, it is probably better to use the Copy method in the parent 322// are using the same implementation, it is probably better to use the Copy method in the parent
318// directory of src/dest. 323// directory of src/dest.
319bool VfsRawCopy(VirtualFile src, VirtualFile dest); 324bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, size_t block_size = 0x1000);
325
326// A method that performs a similar function to VfsRawCopy above, but instead copies entire
327// directories. It suffers the same performance penalties as above and an implementation-specific
328// Copy should always be preferred.
329bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, size_t block_size = 0x1000);
320 330
321// Checks if the directory at path relative to rel exists. If it does, returns that. If it does not 331// Checks if the directory at path relative to rel exists. If it does, returns that. If it does not
322// it attempts to create it and returns the new dir or nullptr on failure. 332// it attempts to create it and returns the new dir or nullptr on failure.
diff --git a/src/core/file_sys/vfs_concat.cpp b/src/core/file_sys/vfs_concat.cpp
index dc7a279a9..d9f9911da 100644
--- a/src/core/file_sys/vfs_concat.cpp
+++ b/src/core/file_sys/vfs_concat.cpp
@@ -5,10 +5,23 @@
5#include <algorithm> 5#include <algorithm>
6#include <utility> 6#include <utility>
7 7
8#include "common/assert.h"
8#include "core/file_sys/vfs_concat.h" 9#include "core/file_sys/vfs_concat.h"
9 10
10namespace FileSys { 11namespace FileSys {
11 12
13static bool VerifyConcatenationMapContinuity(const std::map<u64, VirtualFile>& map) {
14 const auto last_valid = --map.end();
15 for (auto iter = map.begin(); iter != last_valid;) {
16 const auto old = iter++;
17 if (old->first + old->second->GetSize() != iter->first) {
18 return false;
19 }
20 }
21
22 return map.begin()->first == 0;
23}
24
12VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) { 25VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) {
13 if (files.empty()) 26 if (files.empty())
14 return nullptr; 27 return nullptr;
@@ -27,6 +40,11 @@ ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::s
27 } 40 }
28} 41}
29 42
43ConcatenatedVfsFile::ConcatenatedVfsFile(std::map<u64, VirtualFile> files_, std::string name)
44 : files(std::move(files_)), name(std::move(name)) {
45 ASSERT(VerifyConcatenationMapContinuity(files));
46}
47
30ConcatenatedVfsFile::~ConcatenatedVfsFile() = default; 48ConcatenatedVfsFile::~ConcatenatedVfsFile() = default;
31 49
32std::string ConcatenatedVfsFile::GetName() const { 50std::string ConcatenatedVfsFile::GetName() const {
@@ -62,7 +80,7 @@ bool ConcatenatedVfsFile::IsReadable() const {
62} 80}
63 81
64std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { 82std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
65 auto entry = files.end(); 83 auto entry = --files.end();
66 for (auto iter = files.begin(); iter != files.end(); ++iter) { 84 for (auto iter = files.begin(); iter != files.end(); ++iter) {
67 if (iter->first > offset) { 85 if (iter->first > offset) {
68 entry = --iter; 86 entry = --iter;
@@ -70,20 +88,17 @@ std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t
70 } 88 }
71 } 89 }
72 90
73 // Check if the entry should be the last one. The loop above will make it end(). 91 if (entry->first + entry->second->GetSize() <= offset)
74 if (entry == files.end() && offset < files.rbegin()->first + files.rbegin()->second->GetSize())
75 --entry;
76
77 if (entry == files.end())
78 return 0; 92 return 0;
79 93
80 const auto remaining = entry->second->GetSize() + offset - entry->first; 94 const auto read_in =
81 if (length > remaining) { 95 std::min<u64>(entry->first + entry->second->GetSize() - offset, entry->second->GetSize());
82 return entry->second->Read(data, remaining, offset - entry->first) + 96 if (length > read_in) {
83 Read(data + remaining, length - remaining, offset + remaining); 97 return entry->second->Read(data, read_in, offset - entry->first) +
98 Read(data + read_in, length - read_in, offset + read_in);
84 } 99 }
85 100
86 return entry->second->Read(data, length, offset - entry->first); 101 return entry->second->Read(data, std::min<u64>(read_in, length), offset - entry->first);
87} 102}
88 103
89std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { 104std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
@@ -93,4 +108,5 @@ std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::
93bool ConcatenatedVfsFile::Rename(std::string_view name) { 108bool ConcatenatedVfsFile::Rename(std::string_view name) {
94 return false; 109 return false;
95} 110}
111
96} // namespace FileSys 112} // namespace FileSys
diff --git a/src/core/file_sys/vfs_concat.h b/src/core/file_sys/vfs_concat.h
index 717d04bdc..76211d38a 100644
--- a/src/core/file_sys/vfs_concat.h
+++ b/src/core/file_sys/vfs_concat.h
@@ -4,22 +4,25 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <map>
7#include <memory> 8#include <memory>
8#include <string_view> 9#include <string_view>
9#include <boost/container/flat_map.hpp> 10#include <boost/container/flat_map.hpp>
10#include "core/file_sys/vfs.h" 11#include "core/file_sys/vfs.h"
12#include "core/file_sys/vfs_static.h"
11 13
12namespace FileSys { 14namespace FileSys {
13 15
14// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
15VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name = "");
16
17// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently 16// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently
18// read-only. 17// read-only.
19class ConcatenatedVfsFile : public VfsFile { 18class ConcatenatedVfsFile : public VfsFile {
20 friend VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name); 19 friend VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name);
21 20
21 template <u8 filler_byte>
22 friend VirtualFile ConcatenateFiles(std::map<u64, VirtualFile> files, std::string name);
23
22 ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name); 24 ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name);
25 ConcatenatedVfsFile(std::map<u64, VirtualFile> files, std::string name);
23 26
24public: 27public:
25 ~ConcatenatedVfsFile() override; 28 ~ConcatenatedVfsFile() override;
@@ -36,8 +39,37 @@ public:
36 39
37private: 40private:
38 // Maps starting offset to file -- more efficient. 41 // Maps starting offset to file -- more efficient.
39 boost::container::flat_map<u64, VirtualFile> files; 42 std::map<u64, VirtualFile> files;
40 std::string name; 43 std::string name;
41}; 44};
42 45
46// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
47VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name);
48
49// Convenience function that turns a map of offsets to files into a concatenated file, filling gaps
50// with template parameter.
51template <u8 filler_byte>
52VirtualFile ConcatenateFiles(std::map<u64, VirtualFile> files, std::string name) {
53 if (files.empty())
54 return nullptr;
55 if (files.size() == 1)
56 return files.begin()->second;
57
58 const auto last_valid = --files.end();
59 for (auto iter = files.begin(); iter != last_valid;) {
60 const auto old = iter++;
61 if (old->first + old->second->GetSize() != iter->first) {
62 files.emplace(old->first + old->second->GetSize(),
63 std::make_shared<StaticVfsFile<filler_byte>>(iter->first - old->first -
64 old->second->GetSize()));
65 }
66 }
67
68 // Ensure the map starts at offset 0 (start of file), otherwise pad to fill.
69 if (files.begin()->first != 0)
70 files.emplace(0, std::make_shared<StaticVfsFile<filler_byte>>(files.begin()->first));
71
72 return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
73}
74
43} // namespace FileSys 75} // namespace FileSys
diff --git a/src/core/file_sys/vfs_layered.cpp b/src/core/file_sys/vfs_layered.cpp
new file mode 100644
index 000000000..45563d7ae
--- /dev/null
+++ b/src/core/file_sys/vfs_layered.cpp
@@ -0,0 +1,131 @@
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 <utility>
7#include "core/file_sys/vfs_layered.h"
8
9namespace FileSys {
10
11VirtualDir LayerDirectories(std::vector<VirtualDir> dirs, std::string name) {
12 if (dirs.empty())
13 return nullptr;
14 if (dirs.size() == 1)
15 return dirs[0];
16
17 return std::shared_ptr<VfsDirectory>(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
18}
19
20LayeredVfsDirectory::LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name)
21 : dirs(std::move(dirs)), name(std::move(name)) {}
22
23LayeredVfsDirectory::~LayeredVfsDirectory() = default;
24
25std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
26 for (const auto& layer : dirs) {
27 const auto file = layer->GetFileRelative(path);
28 if (file != nullptr)
29 return file;
30 }
31
32 return nullptr;
33}
34
35std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetDirectoryRelative(
36 std::string_view path) const {
37 std::vector<VirtualDir> out;
38 for (const auto& layer : dirs) {
39 auto dir = layer->GetDirectoryRelative(path);
40 if (dir != nullptr)
41 out.push_back(std::move(dir));
42 }
43
44 return LayerDirectories(std::move(out));
45}
46
47std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFile(std::string_view name) const {
48 return GetFileRelative(name);
49}
50
51std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetSubdirectory(std::string_view name) const {
52 return GetDirectoryRelative(name);
53}
54
55std::string LayeredVfsDirectory::GetFullPath() const {
56 return dirs[0]->GetFullPath();
57}
58
59std::vector<std::shared_ptr<VfsFile>> LayeredVfsDirectory::GetFiles() const {
60 std::vector<VirtualFile> out;
61 for (const auto& layer : dirs) {
62 for (const auto& file : layer->GetFiles()) {
63 if (std::find_if(out.begin(), out.end(), [&file](const VirtualFile& comp) {
64 return comp->GetName() == file->GetName();
65 }) == out.end()) {
66 out.push_back(file);
67 }
68 }
69 }
70
71 return out;
72}
73
74std::vector<std::shared_ptr<VfsDirectory>> LayeredVfsDirectory::GetSubdirectories() const {
75 std::vector<std::string> names;
76 for (const auto& layer : dirs) {
77 for (const auto& sd : layer->GetSubdirectories()) {
78 if (std::find(names.begin(), names.end(), sd->GetName()) == names.end())
79 names.push_back(sd->GetName());
80 }
81 }
82
83 std::vector<VirtualDir> out;
84 out.reserve(names.size());
85 for (const auto& subdir : names)
86 out.push_back(GetSubdirectory(subdir));
87
88 return out;
89}
90
91bool LayeredVfsDirectory::IsWritable() const {
92 return false;
93}
94
95bool LayeredVfsDirectory::IsReadable() const {
96 return true;
97}
98
99std::string LayeredVfsDirectory::GetName() const {
100 return name.empty() ? dirs[0]->GetName() : name;
101}
102
103std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetParentDirectory() const {
104 return dirs[0]->GetParentDirectory();
105}
106
107std::shared_ptr<VfsDirectory> LayeredVfsDirectory::CreateSubdirectory(std::string_view name) {
108 return nullptr;
109}
110
111std::shared_ptr<VfsFile> LayeredVfsDirectory::CreateFile(std::string_view name) {
112 return nullptr;
113}
114
115bool LayeredVfsDirectory::DeleteSubdirectory(std::string_view name) {
116 return false;
117}
118
119bool LayeredVfsDirectory::DeleteFile(std::string_view name) {
120 return false;
121}
122
123bool LayeredVfsDirectory::Rename(std::string_view name_) {
124 name = name_;
125 return true;
126}
127
128bool LayeredVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
129 return false;
130}
131} // namespace FileSys
diff --git a/src/core/file_sys/vfs_layered.h b/src/core/file_sys/vfs_layered.h
new file mode 100644
index 000000000..4f6e341ab
--- /dev/null
+++ b/src/core/file_sys/vfs_layered.h
@@ -0,0 +1,52 @@
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 "core/file_sys/vfs.h"
9
10namespace FileSys {
11
12// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases.
13VirtualDir LayerDirectories(std::vector<VirtualDir> dirs, std::string name = "");
14
15// Class that stacks multiple VfsDirectories on top of each other, attempting to read from the first
16// one and falling back to the one after. The highest priority directory (overwrites all others)
17// should be element 0 in the dirs vector.
18class LayeredVfsDirectory : public VfsDirectory {
19 friend VirtualDir LayerDirectories(std::vector<VirtualDir> dirs, std::string name);
20
21 LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name);
22
23public:
24 ~LayeredVfsDirectory() override;
25
26 std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override;
27 std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override;
28 std::shared_ptr<VfsFile> GetFile(std::string_view name) const override;
29 std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const override;
30 std::string GetFullPath() const override;
31
32 std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
33 std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
34 bool IsWritable() const override;
35 bool IsReadable() const override;
36 std::string GetName() const override;
37 std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
38 std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
39 std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
40 bool DeleteSubdirectory(std::string_view name) override;
41 bool DeleteFile(std::string_view name) override;
42 bool Rename(std::string_view name) override;
43
44protected:
45 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
46
47private:
48 std::vector<VirtualDir> dirs;
49 std::string name;
50};
51
52} // namespace FileSys
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp
index 5e242e20f..9defad04c 100644
--- a/src/core/file_sys/vfs_real.cpp
+++ b/src/core/file_sys/vfs_real.cpp
@@ -413,6 +413,23 @@ std::string RealVfsDirectory::GetFullPath() const {
413 return out; 413 return out;
414} 414}
415 415
416std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries() const {
417 if (perms == Mode::Append)
418 return {};
419
420 std::map<std::string, VfsEntryType, std::less<>> out;
421 FileUtil::ForeachDirectoryEntry(
422 nullptr, path,
423 [&out](u64* entries_out, const std::string& directory, const std::string& filename) {
424 const std::string full_path = directory + DIR_SEP + filename;
425 out.emplace(filename, FileUtil::IsDirectory(full_path) ? VfsEntryType::Directory
426 : VfsEntryType::File);
427 return true;
428 });
429
430 return out;
431}
432
416bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { 433bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
417 return false; 434 return false;
418} 435}
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h
index 681c43e82..5b61db90d 100644
--- a/src/core/file_sys/vfs_real.h
+++ b/src/core/file_sys/vfs_real.h
@@ -98,6 +98,7 @@ public:
98 bool DeleteFile(std::string_view name) override; 98 bool DeleteFile(std::string_view name) override;
99 bool Rename(std::string_view name) override; 99 bool Rename(std::string_view name) override;
100 std::string GetFullPath() const override; 100 std::string GetFullPath() const override;
101 std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override;
101 102
102protected: 103protected:
103 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; 104 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
diff --git a/src/core/file_sys/vfs_static.h b/src/core/file_sys/vfs_static.h
new file mode 100644
index 000000000..4dd47ffcc
--- /dev/null
+++ b/src/core/file_sys/vfs_static.h
@@ -0,0 +1,78 @@
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 <algorithm>
8#include <memory>
9#include <string_view>
10
11#include "core/file_sys/vfs.h"
12
13namespace FileSys {
14
15template <u8 value>
16class StaticVfsFile : public VfsFile {
17public:
18 explicit StaticVfsFile(size_t size = 0, std::string name = "", VirtualDir parent = nullptr)
19 : size(size), name(std::move(name)), parent(std::move(parent)) {}
20
21 std::string GetName() const override {
22 return name;
23 }
24
25 size_t GetSize() const override {
26 return size;
27 }
28
29 bool Resize(size_t new_size) override {
30 size = new_size;
31 return true;
32 }
33
34 std::shared_ptr<VfsDirectory> GetContainingDirectory() const override {
35 return parent;
36 }
37
38 bool IsWritable() const override {
39 return false;
40 }
41
42 bool IsReadable() const override {
43 return true;
44 }
45
46 size_t Read(u8* data, size_t length, size_t offset) const override {
47 const auto read = std::min(length, size - offset);
48 std::fill(data, data + read, value);
49 return read;
50 }
51
52 size_t Write(const u8* data, size_t length, size_t offset) override {
53 return 0;
54 }
55
56 boost::optional<u8> ReadByte(size_t offset) const override {
57 if (offset < size)
58 return value;
59 return boost::none;
60 }
61
62 std::vector<u8> ReadBytes(size_t length, size_t offset) const override {
63 const auto read = std::min(length, size - offset);
64 return std::vector<u8>(read, value);
65 }
66
67 bool Rename(std::string_view new_name) override {
68 name = new_name;
69 return true;
70 }
71
72private:
73 size_t size;
74 std::string name;
75 VirtualDir parent;
76};
77
78} // namespace FileSys
diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp
index ec7f735b5..7033e2c88 100644
--- a/src/core/file_sys/vfs_vector.cpp
+++ b/src/core/file_sys/vfs_vector.cpp
@@ -3,10 +3,64 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm> 5#include <algorithm>
6#include <cstring>
6#include <utility> 7#include <utility>
7#include "core/file_sys/vfs_vector.h" 8#include "core/file_sys/vfs_vector.h"
8 9
9namespace FileSys { 10namespace FileSys {
11VectorVfsFile::VectorVfsFile(std::vector<u8> initial_data, std::string name, VirtualDir parent)
12 : data(std::move(initial_data)), name(std::move(name)), parent(std::move(parent)) {}
13
14VectorVfsFile::~VectorVfsFile() = default;
15
16std::string VectorVfsFile::GetName() const {
17 return name;
18}
19
20size_t VectorVfsFile::GetSize() const {
21 return data.size();
22}
23
24bool VectorVfsFile::Resize(size_t new_size) {
25 data.resize(new_size);
26 return true;
27}
28
29std::shared_ptr<VfsDirectory> VectorVfsFile::GetContainingDirectory() const {
30 return parent;
31}
32
33bool VectorVfsFile::IsWritable() const {
34 return true;
35}
36
37bool VectorVfsFile::IsReadable() const {
38 return true;
39}
40
41size_t VectorVfsFile::Read(u8* data_, size_t length, size_t offset) const {
42 const auto read = std::min(length, data.size() - offset);
43 std::memcpy(data_, data.data() + offset, read);
44 return read;
45}
46
47size_t VectorVfsFile::Write(const u8* data_, size_t length, size_t offset) {
48 if (offset + length > data.size())
49 data.resize(offset + length);
50 const auto write = std::min(length, data.size() - offset);
51 std::memcpy(data.data(), data_, write);
52 return write;
53}
54
55bool VectorVfsFile::Rename(std::string_view name_) {
56 name = name_;
57 return true;
58}
59
60void VectorVfsFile::Assign(std::vector<u8> new_data) {
61 data = std::move(new_data);
62}
63
10VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_, 64VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
11 std::vector<VirtualDir> dirs_, std::string name_, 65 std::vector<VirtualDir> dirs_, std::string name_,
12 VirtualDir parent_) 66 VirtualDir parent_)
diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h
index cba44a7a6..115c3ae95 100644
--- a/src/core/file_sys/vfs_vector.h
+++ b/src/core/file_sys/vfs_vector.h
@@ -8,6 +8,31 @@
8 8
9namespace FileSys { 9namespace FileSys {
10 10
11// An implementation of VfsFile that is backed by a vector optionally supplied upon construction
12class VectorVfsFile : public VfsFile {
13public:
14 explicit VectorVfsFile(std::vector<u8> initial_data = {}, std::string name = "",
15 VirtualDir parent = nullptr);
16 ~VectorVfsFile() override;
17
18 std::string GetName() const override;
19 size_t GetSize() const override;
20 bool Resize(size_t new_size) override;
21 std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
22 bool IsWritable() const override;
23 bool IsReadable() const override;
24 size_t Read(u8* data, size_t length, size_t offset) const override;
25 size_t Write(const u8* data, size_t length, size_t offset) override;
26 bool Rename(std::string_view name) override;
27
28 virtual void Assign(std::vector<u8> new_data);
29
30private:
31 std::vector<u8> data;
32 VirtualDir parent;
33 std::string name;
34};
35
11// An implementation of VfsDirectory that maintains two vectors for subdirectories and files. 36// An implementation of VfsDirectory that maintains two vectors for subdirectories and files.
12// Vector data is supplied upon construction. 37// Vector data is supplied upon construction.
13class VectorVfsDirectory : public VfsDirectory { 38class VectorVfsDirectory : public VfsDirectory {
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index d349ee686..aed2abb71 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -343,6 +343,15 @@ std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents() {
343 return sdmc_factory->GetSDMCContents(); 343 return sdmc_factory->GetSDMCContents();
344} 344}
345 345
346FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) {
347 LOG_TRACE(Service_FS, "Opening mod load root for tid={:016X}", title_id);
348
349 if (bis_factory == nullptr)
350 return nullptr;
351
352 return bis_factory->GetModificationLoadRoot(title_id);
353}
354
346void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) { 355void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) {
347 if (overwrite) { 356 if (overwrite) {
348 bis_factory = nullptr; 357 bis_factory = nullptr;
@@ -354,9 +363,11 @@ void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) {
354 FileSys::Mode::ReadWrite); 363 FileSys::Mode::ReadWrite);
355 auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), 364 auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
356 FileSys::Mode::ReadWrite); 365 FileSys::Mode::ReadWrite);
366 auto load_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
367 FileSys::Mode::ReadWrite);
357 368
358 if (bis_factory == nullptr) 369 if (bis_factory == nullptr)
359 bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory); 370 bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory, load_directory);
360 if (save_data_factory == nullptr) 371 if (save_data_factory == nullptr)
361 save_data_factory = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory)); 372 save_data_factory = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));
362 if (sdmc_factory == nullptr) 373 if (sdmc_factory == nullptr)
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index aab65a2b8..7039a2247 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -52,6 +52,8 @@ std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents();
52std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents(); 52std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents();
53std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents(); 53std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents();
54 54
55FileSys::VirtualDir GetModificationLoadRoot(u64 title_id);
56
55// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function 57// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
56// above is called. 58// above is called.
57void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite = true); 59void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite = true);
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index e8b2f720a..991ae10cd 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -318,9 +318,14 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
318 int row = item_model->itemFromIndex(item)->row(); 318 int row = item_model->itemFromIndex(item)->row();
319 QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); 319 QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME);
320 u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong(); 320 u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong();
321 std::string path = child_file->data(GameListItemPath::FullPathRole).toString().toStdString();
321 322
322 QMenu context_menu; 323 QMenu context_menu;
323 QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); 324 QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
325 QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location"));
326 context_menu.addSeparator();
327 QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
328 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
324 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); 329 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
325 330
326 open_save_location->setEnabled(program_id != 0); 331 open_save_location->setEnabled(program_id != 0);
@@ -329,6 +334,10 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
329 334
330 connect(open_save_location, &QAction::triggered, 335 connect(open_save_location, &QAction::triggered,
331 [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); }); 336 [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); });
337 connect(open_lfs_location, &QAction::triggered,
338 [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); });
339 connect(dump_romfs, &QAction::triggered, [&]() { emit DumpRomFSRequested(program_id, path); });
340 connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); });
332 connect(navigate_to_gamedb_entry, &QAction::triggered, 341 connect(navigate_to_gamedb_entry, &QAction::triggered,
333 [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); 342 [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); });
334 343
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 2713e7b54..3bf51870e 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -28,7 +28,10 @@ namespace FileSys {
28class VfsFilesystem; 28class VfsFilesystem;
29} 29}
30 30
31enum class GameListOpenTarget { SaveData }; 31enum class GameListOpenTarget {
32 SaveData,
33 ModData,
34};
32 35
33class GameList : public QWidget { 36class GameList : public QWidget {
34 Q_OBJECT 37 Q_OBJECT
@@ -89,6 +92,8 @@ signals:
89 void GameChosen(QString game_path); 92 void GameChosen(QString game_path);
90 void ShouldCancelWorker(); 93 void ShouldCancelWorker();
91 void OpenFolderRequested(u64 program_id, GameListOpenTarget target); 94 void OpenFolderRequested(u64 program_id, GameListOpenTarget target);
95 void DumpRomFSRequested(u64 program_id, const std::string& game_path);
96 void CopyTIDRequested(u64 program_id);
92 void NavigateToGamedbEntryRequested(u64 program_id, 97 void NavigateToGamedbEntryRequested(u64 program_id,
93 const CompatibilityList& compatibility_list); 98 const CompatibilityList& compatibility_list);
94 99
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 45bb1d1d1..dc8b5407d 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -7,6 +7,22 @@
7#include <memory> 7#include <memory>
8#include <thread> 8#include <thread>
9 9
10// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
11#include "core/file_sys/vfs.h"
12#include "core/file_sys/vfs_real.h"
13
14// These are wrappers to avoid the calls to CreateDirectory and CreateFile becuase of the Windows
15// defines.
16static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper(
17 const FileSys::VirtualFilesystem& vfs, const std::string& path, FileSys::Mode mode) {
18 return vfs->CreateDirectory(path, mode);
19}
20
21static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::VirtualDir& dir,
22 const std::string& path) {
23 return dir->CreateFile(path);
24}
25
10#include <fmt/ostream.h> 26#include <fmt/ostream.h>
11#include <glad/glad.h> 27#include <glad/glad.h>
12 28
@@ -30,16 +46,18 @@
30#include "common/telemetry.h" 46#include "common/telemetry.h"
31#include "core/core.h" 47#include "core/core.h"
32#include "core/crypto/key_manager.h" 48#include "core/crypto/key_manager.h"
49#include "core/file_sys/bis_factory.h"
33#include "core/file_sys/card_image.h" 50#include "core/file_sys/card_image.h"
34#include "core/file_sys/content_archive.h" 51#include "core/file_sys/content_archive.h"
35#include "core/file_sys/control_metadata.h" 52#include "core/file_sys/control_metadata.h"
36#include "core/file_sys/patch_manager.h" 53#include "core/file_sys/patch_manager.h"
37#include "core/file_sys/registered_cache.h" 54#include "core/file_sys/registered_cache.h"
55#include "core/file_sys/romfs.h"
38#include "core/file_sys/savedata_factory.h" 56#include "core/file_sys/savedata_factory.h"
39#include "core/file_sys/submission_package.h" 57#include "core/file_sys/submission_package.h"
40#include "core/file_sys/vfs_real.h"
41#include "core/hle/kernel/process.h" 58#include "core/hle/kernel/process.h"
42#include "core/hle/service/filesystem/filesystem.h" 59#include "core/hle/service/filesystem/filesystem.h"
60#include "core/hle/service/filesystem/fsp_ldr.h"
43#include "core/loader/loader.h" 61#include "core/loader/loader.h"
44#include "core/perf_stats.h" 62#include "core/perf_stats.h"
45#include "core/settings.h" 63#include "core/settings.h"
@@ -362,6 +380,8 @@ void GMainWindow::RestoreUIState() {
362void GMainWindow::ConnectWidgetEvents() { 380void GMainWindow::ConnectWidgetEvents() {
363 connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); 381 connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
364 connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); 382 connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
383 connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
384 connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
365 connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, 385 connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
366 &GMainWindow::OnGameListNavigateToGamedbEntry); 386 &GMainWindow::OnGameListNavigateToGamedbEntry);
367 387
@@ -713,6 +733,12 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
713 program_id, user_id, 0); 733 program_id, user_id, 0);
714 break; 734 break;
715 } 735 }
736 case GameListOpenTarget::ModData: {
737 open_target = "Mod Data";
738 const auto load_dir = FileUtil::GetUserPath(FileUtil::UserPath::LoadDir);
739 path = fmt::format("{}{:016X}", load_dir, program_id);
740 break;
741 }
716 default: 742 default:
717 UNIMPLEMENTED(); 743 UNIMPLEMENTED();
718 } 744 }
@@ -730,6 +756,120 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
730 QDesktopServices::openUrl(QUrl::fromLocalFile(qpath)); 756 QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
731} 757}
732 758
759void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) {
760 const auto path = fmt::format("{}{:016X}/romfs",
761 FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id);
762
763 auto failed = [this, &path]() {
764 QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
765 tr("There was an error copying the RomFS files or the user "
766 "cancelled the operation."));
767 vfs->DeleteDirectory(path);
768 };
769
770 const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read));
771 if (loader == nullptr) {
772 failed();
773 return;
774 }
775
776 FileSys::VirtualFile file;
777 if (loader->ReadRomFS(file) != Loader::ResultStatus::Success) {
778 failed();
779 return;
780 }
781
782 const auto romfs =
783 loader->IsRomFSUpdatable()
784 ? FileSys::PatchManager(program_id).PatchRomFS(file, loader->ReadRomFSIVFCOffset())
785 : file;
786
787 const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
788 if (extracted == nullptr) {
789 failed();
790 return;
791 }
792
793 const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite);
794
795 if (out == nullptr) {
796 failed();
797 return;
798 }
799
800 bool ok;
801 const auto res = QInputDialog::getItem(
802 this, tr("Select RomFS Dump Mode"),
803 tr("Please select the how you would like the RomFS dumped.<br>Full will copy all of the "
804 "files into the new directory while <br>skeleton will only create the directory "
805 "structure."),
806 {"Full", "Skeleton"}, 0, false, &ok);
807 if (!ok)
808 failed();
809
810 const auto full = res == "Full";
811
812 const static std::function<size_t(const FileSys::VirtualDir&, bool)> calculate_entry_size =
813 [](const FileSys::VirtualDir& dir, bool full) {
814 size_t out = 0;
815 for (const auto& subdir : dir->GetSubdirectories())
816 out += 1 + calculate_entry_size(subdir, full);
817 return out + full ? dir->GetFiles().size() : 0;
818 };
819 const auto entry_size = calculate_entry_size(extracted, full);
820
821 QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, entry_size, this);
822 progress.setWindowModality(Qt::WindowModal);
823 progress.setMinimumDuration(100);
824
825 const static std::function<bool(QProgressDialog&, const FileSys::VirtualDir&,
826 const FileSys::VirtualDir&, size_t, bool)>
827 qt_raw_copy = [](QProgressDialog& dialog, const FileSys::VirtualDir& src,
828 const FileSys::VirtualDir& dest, size_t block_size, bool full) {
829 if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
830 return false;
831 if (dialog.wasCanceled())
832 return false;
833
834 if (full) {
835 for (const auto& file : src->GetFiles()) {
836 const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName());
837 if (!FileSys::VfsRawCopy(file, out, block_size))
838 return false;
839 dialog.setValue(dialog.value() + 1);
840 if (dialog.wasCanceled())
841 return false;
842 }
843 }
844
845 for (const auto& dir : src->GetSubdirectories()) {
846 const auto out = dest->CreateSubdirectory(dir->GetName());
847 if (!qt_raw_copy(dialog, dir, out, block_size, full))
848 return false;
849 dialog.setValue(dialog.value() + 1);
850 if (dialog.wasCanceled())
851 return false;
852 }
853
854 return true;
855 };
856
857 if (qt_raw_copy(progress, extracted, out, 0x400000, full)) {
858 progress.close();
859 QMessageBox::information(this, tr("RomFS Extraction Succeeded!"),
860 tr("The operation completed successfully."));
861 QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(path)));
862 } else {
863 progress.close();
864 failed();
865 }
866}
867
868void GMainWindow::OnGameListCopyTID(u64 program_id) {
869 QClipboard* clipboard = QGuiApplication::clipboard();
870 clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id)));
871}
872
733void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, 873void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
734 const CompatibilityList& compatibility_list) { 874 const CompatibilityList& compatibility_list) {
735 const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); 875 const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
@@ -790,7 +930,8 @@ void GMainWindow::OnMenuInstallToNAND() {
790 return; 930 return;
791 } 931 }
792 932
793 const auto qt_raw_copy = [this](FileSys::VirtualFile src, FileSys::VirtualFile dest) { 933 const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
934 const FileSys::VirtualFile& dest, size_t block_size) {
794 if (src == nullptr || dest == nullptr) 935 if (src == nullptr || dest == nullptr)
795 return false; 936 return false;
796 if (!dest->Resize(src->GetSize())) 937 if (!dest->Resize(src->GetSize()))
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 552e3e61c..8ee9242b1 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -138,6 +138,8 @@ private slots:
138 /// Called whenever a user selects a game in the game list widget. 138 /// Called whenever a user selects a game in the game list widget.
139 void OnGameListLoadFile(QString game_path); 139 void OnGameListLoadFile(QString game_path);
140 void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); 140 void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);
141 void OnGameListDumpRomFS(u64 program_id, const std::string& game_path);
142 void OnGameListCopyTID(u64 program_id);
141 void OnGameListNavigateToGamedbEntry(u64 program_id, 143 void OnGameListNavigateToGamedbEntry(u64 program_id,
142 const CompatibilityList& compatibility_list); 144 const CompatibilityList& compatibility_list);
143 void OnMenuLoadFile(); 145 void OnMenuLoadFile();