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/core.cpp2
-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.cpp367
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.h70
-rw-r--r--src/core/file_sys/patch_manager.cpp49
-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.h17
-rw-r--r--src/core/file_sys/vfs_concat.cpp77
-rw-r--r--src/core/file_sys/vfs_concat.h18
-rw-r--r--src/core/file_sys/vfs_layered.cpp132
-rw-r--r--src/core/file_sys/vfs_layered.h50
-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.h79
-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/video_core/engines/maxwell_3d.h12
-rw-r--r--src/video_core/engines/maxwell_compute.cpp19
-rw-r--r--src/video_core/engines/maxwell_compute.h36
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp22
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h6
-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
37 files changed, 1303 insertions, 50 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/core.cpp b/src/core/core.cpp
index 50f0a42fb..7666354dc 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -64,7 +64,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
64 if (concat.empty()) 64 if (concat.empty())
65 return nullptr; 65 return nullptr;
66 66
67 return FileSys::ConcatenateFiles(concat, dir->GetName()); 67 return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName());
68 } 68 }
69 69
70 return vfs->OpenFile(path, FileSys::Mode::Read); 70 return vfs->OpenFile(path, FileSys::Mode::Read);
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..07b074817
--- /dev/null
+++ b/src/core/file_sys/fsmitm_romfsbuild.cpp
@@ -0,0 +1,367 @@
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;
96};
97
98static u32 romfs_calc_path_hash(u32 parent, std::string path, u32 start, std::size_t path_len) {
99 u32 hash = parent ^ 123456789;
100 for (u32 i = 0; i < path_len; i++) {
101 hash = (hash >> 5) | (hash << 27);
102 hash ^= path[start + i];
103 }
104
105 return hash;
106}
107
108static u64 romfs_get_hash_table_count(u64 num_entries) {
109 if (num_entries < 3) {
110 return 3;
111 }
112
113 if (num_entries < 19) {
114 return num_entries | 1;
115 }
116
117 u64 count = num_entries;
118 while (count % 2 == 0 || count % 3 == 0 || count % 5 == 0 || count % 7 == 0 ||
119 count % 11 == 0 || count % 13 == 0 || count % 17 == 0) {
120 count++;
121 }
122 return count;
123}
124
125void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
126 std::shared_ptr<RomFSBuildDirectoryContext> parent) {
127 std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> child_dirs;
128
129 VirtualDir dir;
130
131 if (parent->path_len == 0)
132 dir = root_romfs;
133 else
134 dir = root_romfs->GetDirectoryRelative(parent->path);
135
136 const auto entries = dir->GetEntries();
137
138 for (const auto& kv : entries) {
139 if (kv.second == VfsEntryType::Directory) {
140 const auto child = std::make_shared<RomFSBuildDirectoryContext>();
141 // Set child's path.
142 child->cur_path_ofs = parent->path_len + 1;
143 child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
144 child->path = parent->path + "/" + kv.first;
145
146 // Sanity check on path_len
147 ASSERT(child->path_len < FS_MAX_PATH);
148
149 if (AddDirectory(parent, child)) {
150 child_dirs.push_back(child);
151 }
152 } else {
153 const auto child = std::make_shared<RomFSBuildFileContext>();
154 // Set child's path.
155 child->cur_path_ofs = parent->path_len + 1;
156 child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
157 child->path = parent->path + "/" + kv.first;
158
159 // Sanity check on path_len
160 ASSERT(child->path_len < FS_MAX_PATH);
161
162 child->source = root_romfs->GetFileRelative(child->path);
163
164 child->size = child->source->GetSize();
165
166 AddFile(parent, child);
167 }
168 }
169
170 for (auto& child : child_dirs) {
171 this->VisitDirectory(root_romfs, child);
172 }
173}
174
175bool RomFSBuildContext::AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
176 std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx) {
177 // Check whether it's already in the known directories.
178 const auto existing = directories.find(dir_ctx->path);
179 if (existing != directories.end())
180 return false;
181
182 // Add a new directory.
183 num_dirs++;
184 dir_table_size +=
185 sizeof(RomFSDirectoryEntry) + ((dir_ctx->path_len - dir_ctx->cur_path_ofs + 3) & ~3);
186 dir_ctx->parent = parent_dir_ctx;
187 directories.emplace(dir_ctx->path, dir_ctx);
188
189 return true;
190}
191
192bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
193 std::shared_ptr<RomFSBuildFileContext> file_ctx) {
194 // Check whether it's already in the known files.
195 const auto existing = files.find(file_ctx->path);
196 if (existing != files.end()) {
197 return false;
198 }
199
200 // Add a new file.
201 num_files++;
202 file_table_size +=
203 sizeof(RomFSFileEntry) + ((file_ctx->path_len - file_ctx->cur_path_ofs + 3) & ~3);
204 file_ctx->parent = parent_dir_ctx;
205 files.emplace(file_ctx->path, file_ctx);
206
207 return true;
208}
209
210RomFSBuildContext::RomFSBuildContext(VirtualDir base_) : base(std::move(base_)) {
211 root = std::make_shared<RomFSBuildDirectoryContext>();
212 root->path = "\0";
213 directories.emplace(root->path, root);
214 num_dirs = 1;
215 dir_table_size = 0x18;
216
217 VisitDirectory(base, root);
218}
219
220RomFSBuildContext::~RomFSBuildContext() = default;
221
222std::map<u64, VirtualFile> RomFSBuildContext::Build() {
223 const u64 dir_hash_table_entry_count = romfs_get_hash_table_count(num_dirs);
224 const u64 file_hash_table_entry_count = romfs_get_hash_table_count(num_files);
225 dir_hash_table_size = 4 * dir_hash_table_entry_count;
226 file_hash_table_size = 4 * file_hash_table_entry_count;
227
228 // Assign metadata pointers
229 RomFSHeader header{};
230
231 std::vector<u32> dir_hash_table(dir_hash_table_entry_count, ROMFS_ENTRY_EMPTY);
232 std::vector<u32> file_hash_table(file_hash_table_entry_count, ROMFS_ENTRY_EMPTY);
233
234 std::vector<u8> dir_table(dir_table_size);
235 std::vector<u8> file_table(file_table_size);
236
237 std::shared_ptr<RomFSBuildFileContext> cur_file;
238
239 // Determine file offsets.
240 u32 entry_offset = 0;
241 std::shared_ptr<RomFSBuildFileContext> prev_file = nullptr;
242 for (const auto& it : files) {
243 cur_file = it.second;
244 file_partition_size = (file_partition_size + 0xFULL) & ~0xFULL;
245 cur_file->offset = file_partition_size;
246 file_partition_size += cur_file->size;
247 cur_file->entry_offset = entry_offset;
248 entry_offset +=
249 sizeof(RomFSFileEntry) + ((cur_file->path_len - cur_file->cur_path_ofs + 3) & ~3);
250 prev_file = cur_file;
251 }
252 // Assign deferred parent/sibling ownership.
253 for (auto it = files.rbegin(); it != files.rend(); ++it) {
254 cur_file = it->second;
255 cur_file->sibling = cur_file->parent->file;
256 cur_file->parent->file = cur_file;
257 }
258
259 std::shared_ptr<RomFSBuildDirectoryContext> cur_dir;
260
261 // Determine directory offsets.
262 entry_offset = 0;
263 for (const auto& it : directories) {
264 cur_dir = it.second;
265 cur_dir->entry_offset = entry_offset;
266 entry_offset +=
267 sizeof(RomFSDirectoryEntry) + ((cur_dir->path_len - cur_dir->cur_path_ofs + 3) & ~3);
268 }
269 // Assign deferred parent/sibling ownership.
270 for (auto it = directories.rbegin(); it->second != root; ++it) {
271 cur_dir = it->second;
272 cur_dir->sibling = cur_dir->parent->child;
273 cur_dir->parent->child = cur_dir;
274 }
275
276 std::map<u64, VirtualFile> out;
277
278 // Populate file tables.
279 for (const auto& it : files) {
280 cur_file = it.second;
281 RomFSFileEntry cur_entry{};
282
283 cur_entry.parent = cur_file->parent->entry_offset;
284 cur_entry.sibling =
285 cur_file->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_file->sibling->entry_offset;
286 cur_entry.offset = cur_file->offset;
287 cur_entry.size = cur_file->size;
288
289 const auto name_size = cur_file->path_len - cur_file->cur_path_ofs;
290 const auto hash = romfs_calc_path_hash(cur_file->parent->entry_offset, cur_file->path,
291 cur_file->cur_path_ofs, name_size);
292 cur_entry.hash = file_hash_table[hash % file_hash_table_entry_count];
293 file_hash_table[hash % file_hash_table_entry_count] = cur_file->entry_offset;
294
295 cur_entry.name_size = name_size;
296
297 out.emplace(cur_file->offset + ROMFS_FILEPARTITION_OFS, cur_file->source);
298 std::memcpy(file_table.data() + cur_file->entry_offset, &cur_entry, sizeof(RomFSFileEntry));
299 std::memset(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry), 0,
300 (cur_entry.name_size + 3) & ~3);
301 std::memcpy(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry),
302 cur_file->path.data() + cur_file->cur_path_ofs, name_size);
303 }
304
305 // Populate dir tables.
306 for (const auto& it : directories) {
307 cur_dir = it.second;
308 RomFSDirectoryEntry cur_entry{};
309
310 cur_entry.parent = cur_dir == root ? 0 : cur_dir->parent->entry_offset;
311 cur_entry.sibling =
312 cur_dir->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->sibling->entry_offset;
313 cur_entry.child =
314 cur_dir->child == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->child->entry_offset;
315 cur_entry.file = cur_dir->file == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->file->entry_offset;
316
317 const auto name_size = cur_dir->path_len - cur_dir->cur_path_ofs;
318 const auto hash = romfs_calc_path_hash(cur_dir == root ? 0 : cur_dir->parent->entry_offset,
319 cur_dir->path, cur_dir->cur_path_ofs, name_size);
320 cur_entry.hash = dir_hash_table[hash % dir_hash_table_entry_count];
321 dir_hash_table[hash % dir_hash_table_entry_count] = cur_dir->entry_offset;
322
323 cur_entry.name_size = name_size;
324
325 std::memcpy(dir_table.data() + cur_dir->entry_offset, &cur_entry,
326 sizeof(RomFSDirectoryEntry));
327 std::memcpy(dir_table.data() + cur_dir->entry_offset, &cur_entry,
328 sizeof(RomFSDirectoryEntry));
329 std::memset(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry), 0,
330 (cur_entry.name_size + 3) & ~3);
331 std::memcpy(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry),
332 cur_dir->path.data() + cur_dir->cur_path_ofs, name_size);
333 }
334
335 // Set header fields.
336 header.header_size = sizeof(RomFSHeader);
337 header.file_hash_table_size = file_hash_table_size;
338 header.file_table_size = file_table_size;
339 header.dir_hash_table_size = dir_hash_table_size;
340 header.dir_table_size = dir_table_size;
341 header.file_partition_ofs = ROMFS_FILEPARTITION_OFS;
342 header.dir_hash_table_ofs = (header.file_partition_ofs + file_partition_size + 3ULL) & ~3ULL;
343 header.dir_table_ofs = header.dir_hash_table_ofs + header.dir_hash_table_size;
344 header.file_hash_table_ofs = header.dir_table_ofs + header.dir_table_size;
345 header.file_table_ofs = header.file_hash_table_ofs + header.file_hash_table_size;
346
347 std::vector<u8> header_data(sizeof(RomFSHeader));
348 std::memcpy(header_data.data(), &header, header_data.size());
349 out.emplace(0, std::make_shared<VectorVfsFile>(header_data));
350
351 std::vector<u8> metadata(file_hash_table_size + file_table_size + dir_hash_table_size +
352 dir_table_size);
353 std::size_t index = 0;
354 std::memcpy(metadata.data(), dir_hash_table.data(), dir_hash_table.size() * sizeof(u32));
355 index += dir_hash_table.size() * sizeof(u32);
356 std::memcpy(metadata.data() + index, dir_table.data(), dir_table.size());
357 index += dir_table.size();
358 std::memcpy(metadata.data() + index, file_hash_table.data(),
359 file_hash_table.size() * sizeof(u32));
360 index += file_hash_table.size() * sizeof(u32);
361 std::memcpy(metadata.data() + index, file_table.data(), file_table.size());
362 out.emplace(header.dir_hash_table_ofs, std::make_shared<VectorVfsFile>(metadata));
363
364 return out;
365}
366
367} // 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..4b3b5e665 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,44 @@ 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 return;
75 }
76
77 auto extracted = ExtractRomFS(romfs);
78 if (extracted == nullptr) {
79 return;
80 }
81
82 auto patch_dirs = load_dir->GetSubdirectories();
83 std::sort(patch_dirs.begin(), patch_dirs.end(),
84 [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
85
86 std::vector<VirtualDir> layers;
87 layers.reserve(patch_dirs.size() + 1);
88 for (const auto& subdir : patch_dirs) {
89 auto romfs_dir = subdir->GetSubdirectory("romfs");
90 if (romfs_dir != nullptr)
91 layers.push_back(std::move(romfs_dir));
92 }
93 layers.push_back(std::move(extracted));
94
95 auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers));
96 if (layered == nullptr) {
97 return;
98 }
99
100 auto packed = CreateRomFS(std::move(layered));
101 if (packed == nullptr) {
102 return;
103 }
104
105 LOG_INFO(Loader, " RomFS: LayeredFS patches applied successfully");
106 romfs = std::move(packed);
107}
108
69VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, 109VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
70 ContentRecordType type) const { 110 ContentRecordType type) const {
71 LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id, 111 LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id,
@@ -89,6 +129,9 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
89 } 129 }
90 } 130 }
91 131
132 // LayeredFS
133 ApplyLayeredFS(romfs, title_id, type);
134
92 return romfs; 135 return romfs;
93} 136}
94 137
@@ -114,6 +157,10 @@ std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const {
114 } 157 }
115 } 158 }
116 159
160 const auto lfs_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
161 if (lfs_dir != nullptr && lfs_dir->GetSize() > 0)
162 out.insert_or_assign(PatchType::LayeredFS, "");
163
117 return out; 164 return out;
118} 165}
119 166
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..e9b040689 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 = ConcatenatedVfsFile::MakeConcatenatedFile(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..5910f7046 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 ConcatenatedVfsFile::MakeConcatenatedFile(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..bfe50da73 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, std::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 (std::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, std::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..270291631 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
@@ -310,13 +315,19 @@ public:
310 bool Rename(std::string_view name) override; 315 bool Rename(std::string_view name) override;
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 specified 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,
320 std::size_t block_size = 0x1000);
315 321
316// A method that copies the raw data between two different implementations of VirtualFile. If you 322// 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 323// are using the same implementation, it is probably better to use the Copy method in the parent
318// directory of src/dest. 324// directory of src/dest.
319bool VfsRawCopy(VirtualFile src, VirtualFile dest); 325bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t block_size = 0x1000);
326
327// A method that performs a similar function to VfsRawCopy above, but instead copies entire
328// directories. It suffers the same performance penalties as above and an implementation-specific
329// Copy should always be preferred.
330bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, std::size_t block_size = 0x1000);
320 331
321// Checks if the directory at path relative to rel exists. If it does, returns that. If it does not 332// 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. 333// 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..16d801c0c 100644
--- a/src/core/file_sys/vfs_concat.cpp
+++ b/src/core/file_sys/vfs_concat.cpp
@@ -5,17 +5,22 @@
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"
10#include "core/file_sys/vfs_static.h"
9 11
10namespace FileSys { 12namespace FileSys {
11 13
12VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) { 14static bool VerifyConcatenationMapContinuity(const std::map<u64, VirtualFile>& map) {
13 if (files.empty()) 15 const auto last_valid = --map.end();
14 return nullptr; 16 for (auto iter = map.begin(); iter != last_valid;) {
15 if (files.size() == 1) 17 const auto old = iter++;
16 return files[0]; 18 if (old->first + old->second->GetSize() != iter->first) {
19 return false;
20 }
21 }
17 22
18 return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name))); 23 return map.begin()->first == 0;
19} 24}
20 25
21ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name) 26ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name)
@@ -27,8 +32,48 @@ ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::s
27 } 32 }
28} 33}
29 34
35ConcatenatedVfsFile::ConcatenatedVfsFile(std::map<u64, VirtualFile> files_, std::string name)
36 : files(std::move(files_)), name(std::move(name)) {
37 ASSERT(VerifyConcatenationMapContinuity(files));
38}
39
30ConcatenatedVfsFile::~ConcatenatedVfsFile() = default; 40ConcatenatedVfsFile::~ConcatenatedVfsFile() = default;
31 41
42VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(std::vector<VirtualFile> files,
43 std::string name) {
44 if (files.empty())
45 return nullptr;
46 if (files.size() == 1)
47 return files[0];
48
49 return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
50}
51
52VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte,
53 std::map<u64, VirtualFile> files,
54 std::string name) {
55 if (files.empty())
56 return nullptr;
57 if (files.size() == 1)
58 return files.begin()->second;
59
60 const auto last_valid = --files.end();
61 for (auto iter = files.begin(); iter != last_valid;) {
62 const auto old = iter++;
63 if (old->first + old->second->GetSize() != iter->first) {
64 files.emplace(old->first + old->second->GetSize(),
65 std::make_shared<StaticVfsFile>(filler_byte, iter->first - old->first -
66 old->second->GetSize()));
67 }
68 }
69
70 // Ensure the map starts at offset 0 (start of file), otherwise pad to fill.
71 if (files.begin()->first != 0)
72 files.emplace(0, std::make_shared<StaticVfsFile>(filler_byte, files.begin()->first));
73
74 return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
75}
76
32std::string ConcatenatedVfsFile::GetName() const { 77std::string ConcatenatedVfsFile::GetName() const {
33 if (files.empty()) 78 if (files.empty())
34 return ""; 79 return "";
@@ -62,7 +107,7 @@ bool ConcatenatedVfsFile::IsReadable() const {
62} 107}
63 108
64std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { 109std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
65 auto entry = files.end(); 110 auto entry = --files.end();
66 for (auto iter = files.begin(); iter != files.end(); ++iter) { 111 for (auto iter = files.begin(); iter != files.end(); ++iter) {
67 if (iter->first > offset) { 112 if (iter->first > offset) {
68 entry = --iter; 113 entry = --iter;
@@ -70,20 +115,17 @@ std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t
70 } 115 }
71 } 116 }
72 117
73 // Check if the entry should be the last one. The loop above will make it end(). 118 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; 119 return 0;
79 120
80 const auto remaining = entry->second->GetSize() + offset - entry->first; 121 const auto read_in =
81 if (length > remaining) { 122 std::min<u64>(entry->first + entry->second->GetSize() - offset, entry->second->GetSize());
82 return entry->second->Read(data, remaining, offset - entry->first) + 123 if (length > read_in) {
83 Read(data + remaining, length - remaining, offset + remaining); 124 return entry->second->Read(data, read_in, offset - entry->first) +
125 Read(data + read_in, length - read_in, offset + read_in);
84 } 126 }
85 127
86 return entry->second->Read(data, length, offset - entry->first); 128 return entry->second->Read(data, std::min<u64>(read_in, length), offset - entry->first);
87} 129}
88 130
89std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { 131std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
@@ -93,4 +135,5 @@ std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::
93bool ConcatenatedVfsFile::Rename(std::string_view name) { 135bool ConcatenatedVfsFile::Rename(std::string_view name) {
94 return false; 136 return false;
95} 137}
138
96} // namespace FileSys 139} // namespace FileSys
diff --git a/src/core/file_sys/vfs_concat.h b/src/core/file_sys/vfs_concat.h
index 717d04bdc..c90f9d5d1 100644
--- a/src/core/file_sys/vfs_concat.h
+++ b/src/core/file_sys/vfs_concat.h
@@ -4,26 +4,30 @@
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 "core/file_sys/vfs.h" 10#include "core/file_sys/vfs.h"
11 11
12namespace FileSys { 12namespace FileSys {
13 13
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 14// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently
18// read-only. 15// read-only.
19class ConcatenatedVfsFile : public VfsFile { 16class ConcatenatedVfsFile : public VfsFile {
20 friend VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name);
21
22 ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name); 17 ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name);
18 ConcatenatedVfsFile(std::map<u64, VirtualFile> files, std::string name);
23 19
24public: 20public:
25 ~ConcatenatedVfsFile() override; 21 ~ConcatenatedVfsFile() override;
26 22
23 /// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
24 static VirtualFile MakeConcatenatedFile(std::vector<VirtualFile> files, std::string name);
25
26 /// Convenience function that turns a map of offsets to files into a concatenated file, filling
27 /// gaps with a given filler byte.
28 static VirtualFile MakeConcatenatedFile(u8 filler_byte, std::map<u64, VirtualFile> files,
29 std::string name);
30
27 std::string GetName() const override; 31 std::string GetName() const override;
28 std::size_t GetSize() const override; 32 std::size_t GetSize() const override;
29 bool Resize(std::size_t new_size) override; 33 bool Resize(std::size_t new_size) override;
@@ -36,7 +40,7 @@ public:
36 40
37private: 41private:
38 // Maps starting offset to file -- more efficient. 42 // Maps starting offset to file -- more efficient.
39 boost::container::flat_map<u64, VirtualFile> files; 43 std::map<u64, VirtualFile> files;
40 std::string name; 44 std::string name;
41}; 45};
42 46
diff --git a/src/core/file_sys/vfs_layered.cpp b/src/core/file_sys/vfs_layered.cpp
new file mode 100644
index 000000000..bfee01725
--- /dev/null
+++ b/src/core/file_sys/vfs_layered.cpp
@@ -0,0 +1,132 @@
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
11LayeredVfsDirectory::LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name)
12 : dirs(std::move(dirs)), name(std::move(name)) {}
13
14LayeredVfsDirectory::~LayeredVfsDirectory() = default;
15
16VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(std::vector<VirtualDir> dirs,
17 std::string name) {
18 if (dirs.empty())
19 return nullptr;
20 if (dirs.size() == 1)
21 return dirs[0];
22
23 return std::shared_ptr<VfsDirectory>(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
24}
25
26std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
27 for (const auto& layer : dirs) {
28 const auto file = layer->GetFileRelative(path);
29 if (file != nullptr)
30 return file;
31 }
32
33 return nullptr;
34}
35
36std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetDirectoryRelative(
37 std::string_view path) const {
38 std::vector<VirtualDir> out;
39 for (const auto& layer : dirs) {
40 auto dir = layer->GetDirectoryRelative(path);
41 if (dir != nullptr)
42 out.push_back(std::move(dir));
43 }
44
45 return MakeLayeredDirectory(std::move(out));
46}
47
48std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFile(std::string_view name) const {
49 return GetFileRelative(name);
50}
51
52std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetSubdirectory(std::string_view name) const {
53 return GetDirectoryRelative(name);
54}
55
56std::string LayeredVfsDirectory::GetFullPath() const {
57 return dirs[0]->GetFullPath();
58}
59
60std::vector<std::shared_ptr<VfsFile>> LayeredVfsDirectory::GetFiles() const {
61 std::vector<VirtualFile> out;
62 for (const auto& layer : dirs) {
63 for (const auto& file : layer->GetFiles()) {
64 if (std::find_if(out.begin(), out.end(), [&file](const VirtualFile& comp) {
65 return comp->GetName() == file->GetName();
66 }) == out.end()) {
67 out.push_back(file);
68 }
69 }
70 }
71
72 return out;
73}
74
75std::vector<std::shared_ptr<VfsDirectory>> LayeredVfsDirectory::GetSubdirectories() const {
76 std::vector<std::string> names;
77 for (const auto& layer : dirs) {
78 for (const auto& sd : layer->GetSubdirectories()) {
79 if (std::find(names.begin(), names.end(), sd->GetName()) == names.end())
80 names.push_back(sd->GetName());
81 }
82 }
83
84 std::vector<VirtualDir> out;
85 out.reserve(names.size());
86 for (const auto& subdir : names)
87 out.push_back(GetSubdirectory(subdir));
88
89 return out;
90}
91
92bool LayeredVfsDirectory::IsWritable() const {
93 return false;
94}
95
96bool LayeredVfsDirectory::IsReadable() const {
97 return true;
98}
99
100std::string LayeredVfsDirectory::GetName() const {
101 return name.empty() ? dirs[0]->GetName() : name;
102}
103
104std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetParentDirectory() const {
105 return dirs[0]->GetParentDirectory();
106}
107
108std::shared_ptr<VfsDirectory> LayeredVfsDirectory::CreateSubdirectory(std::string_view name) {
109 return nullptr;
110}
111
112std::shared_ptr<VfsFile> LayeredVfsDirectory::CreateFile(std::string_view name) {
113 return nullptr;
114}
115
116bool LayeredVfsDirectory::DeleteSubdirectory(std::string_view name) {
117 return false;
118}
119
120bool LayeredVfsDirectory::DeleteFile(std::string_view name) {
121 return false;
122}
123
124bool LayeredVfsDirectory::Rename(std::string_view name_) {
125 name = name_;
126 return true;
127}
128
129bool LayeredVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
130 return false;
131}
132} // 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..d85310f57
--- /dev/null
+++ b/src/core/file_sys/vfs_layered.h
@@ -0,0 +1,50 @@
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// Class that stacks multiple VfsDirectories on top of each other, attempting to read from the first
13// one and falling back to the one after. The highest priority directory (overwrites all others)
14// should be element 0 in the dirs vector.
15class LayeredVfsDirectory : public VfsDirectory {
16 LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name);
17
18public:
19 ~LayeredVfsDirectory() override;
20
21 /// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases.
22 static VirtualDir MakeLayeredDirectory(std::vector<VirtualDir> dirs, std::string name = "");
23
24 std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override;
25 std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override;
26 std::shared_ptr<VfsFile> GetFile(std::string_view name) const override;
27 std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const override;
28 std::string GetFullPath() const override;
29
30 std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
31 std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
32 bool IsWritable() const override;
33 bool IsReadable() const override;
34 std::string GetName() const override;
35 std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
36 std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
37 std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
38 bool DeleteSubdirectory(std::string_view name) override;
39 bool DeleteFile(std::string_view name) override;
40 bool Rename(std::string_view name) override;
41
42protected:
43 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
44
45private:
46 std::vector<VirtualDir> dirs;
47 std::string name;
48};
49
50} // 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..44fab51d1
--- /dev/null
+++ b/src/core/file_sys/vfs_static.h
@@ -0,0 +1,79 @@
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
15class StaticVfsFile : public VfsFile {
16public:
17 explicit StaticVfsFile(u8 value, std::size_t size = 0, std::string name = "",
18 VirtualDir parent = nullptr)
19 : value{value}, size{size}, name{std::move(name)}, parent{std::move(parent)} {}
20
21 std::string GetName() const override {
22 return name;
23 }
24
25 std::size_t GetSize() const override {
26 return size;
27 }
28
29 bool Resize(std::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 std::size_t Read(u8* data, std::size_t length, std::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 std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override {
53 return 0;
54 }
55
56 boost::optional<u8> ReadByte(std::size_t offset) const override {
57 if (offset < size)
58 return value;
59 return boost::none;
60 }
61
62 std::vector<u8> ReadBytes(std::size_t length, std::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 u8 value;
74 std::size_t size;
75 std::string name;
76 VirtualDir parent;
77};
78
79} // namespace FileSys
diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp
index ec7f735b5..389c7e003 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)), parent(std::move(parent)), name(std::move(name)) {}
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
41std::size_t VectorVfsFile::Read(u8* data_, std::size_t length, std::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
47std::size_t VectorVfsFile::Write(const u8* data_, std::size_t length, std::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..48a414c98 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 std::size_t GetSize() const override;
20 bool Resize(std::size_t new_size) override;
21 std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
22 bool IsWritable() const override;
23 bool IsReadable() const override;
24 std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
25 std::size_t Write(const u8* data, std::size_t length, std::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/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index b81b0723d..16cdfc7e2 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -461,7 +461,11 @@ public:
461 u32 entry; 461 u32 entry;
462 } macros; 462 } macros;
463 463
464 INSERT_PADDING_WORDS(0x1B8); 464 INSERT_PADDING_WORDS(0x189);
465
466 u32 tfb_enabled;
467
468 INSERT_PADDING_WORDS(0x2E);
465 469
466 RenderTargetConfig rt[NumRenderTargets]; 470 RenderTargetConfig rt[NumRenderTargets];
467 471
@@ -594,7 +598,9 @@ public:
594 598
595 u32 depth_write_enabled; 599 u32 depth_write_enabled;
596 600
597 INSERT_PADDING_WORDS(0x7); 601 u32 alpha_test_enabled;
602
603 INSERT_PADDING_WORDS(0x6);
598 604
599 u32 d3d_cull_mode; 605 u32 d3d_cull_mode;
600 606
@@ -977,6 +983,7 @@ private:
977 "Field " #field_name " has invalid position") 983 "Field " #field_name " has invalid position")
978 984
979ASSERT_REG_POSITION(macros, 0x45); 985ASSERT_REG_POSITION(macros, 0x45);
986ASSERT_REG_POSITION(tfb_enabled, 0x1D1);
980ASSERT_REG_POSITION(rt, 0x200); 987ASSERT_REG_POSITION(rt, 0x200);
981ASSERT_REG_POSITION(viewport_transform[0], 0x280); 988ASSERT_REG_POSITION(viewport_transform[0], 0x280);
982ASSERT_REG_POSITION(viewport, 0x300); 989ASSERT_REG_POSITION(viewport, 0x300);
@@ -996,6 +1003,7 @@ ASSERT_REG_POSITION(zeta_height, 0x48b);
996ASSERT_REG_POSITION(depth_test_enable, 0x4B3); 1003ASSERT_REG_POSITION(depth_test_enable, 0x4B3);
997ASSERT_REG_POSITION(independent_blend_enable, 0x4B9); 1004ASSERT_REG_POSITION(independent_blend_enable, 0x4B9);
998ASSERT_REG_POSITION(depth_write_enabled, 0x4BA); 1005ASSERT_REG_POSITION(depth_write_enabled, 0x4BA);
1006ASSERT_REG_POSITION(alpha_test_enabled, 0x4BB);
999ASSERT_REG_POSITION(d3d_cull_mode, 0x4C2); 1007ASSERT_REG_POSITION(d3d_cull_mode, 0x4C2);
1000ASSERT_REG_POSITION(depth_test_func, 0x4C3); 1008ASSERT_REG_POSITION(depth_test_func, 0x4C3);
1001ASSERT_REG_POSITION(blend, 0x4CF); 1009ASSERT_REG_POSITION(blend, 0x4CF);
diff --git a/src/video_core/engines/maxwell_compute.cpp b/src/video_core/engines/maxwell_compute.cpp
index e4e5f9e5e..59e28b22d 100644
--- a/src/video_core/engines/maxwell_compute.cpp
+++ b/src/video_core/engines/maxwell_compute.cpp
@@ -2,12 +2,29 @@
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 "common/logging/log.h"
6#include "core/core.h"
5#include "video_core/engines/maxwell_compute.h" 7#include "video_core/engines/maxwell_compute.h"
6 8
7namespace Tegra { 9namespace Tegra {
8namespace Engines { 10namespace Engines {
9 11
10void MaxwellCompute::WriteReg(u32 method, u32 value) {} 12void MaxwellCompute::WriteReg(u32 method, u32 value) {
13 ASSERT_MSG(method < Regs::NUM_REGS,
14 "Invalid MaxwellCompute register, increase the size of the Regs structure");
15
16 regs.reg_array[method] = value;
17
18 switch (method) {
19 case MAXWELL_COMPUTE_REG_INDEX(compute): {
20 LOG_CRITICAL(HW_GPU, "Compute shaders are not implemented");
21 UNREACHABLE();
22 break;
23 }
24 default:
25 break;
26 }
27}
11 28
12} // namespace Engines 29} // namespace Engines
13} // namespace Tegra 30} // namespace Tegra
diff --git a/src/video_core/engines/maxwell_compute.h b/src/video_core/engines/maxwell_compute.h
index 2b3e4ced6..6ea934fb9 100644
--- a/src/video_core/engines/maxwell_compute.h
+++ b/src/video_core/engines/maxwell_compute.h
@@ -4,17 +4,53 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <array>
8#include "common/assert.h"
9#include "common/bit_field.h"
10#include "common/common_funcs.h"
7#include "common/common_types.h" 11#include "common/common_types.h"
8 12
9namespace Tegra::Engines { 13namespace Tegra::Engines {
10 14
15#define MAXWELL_COMPUTE_REG_INDEX(field_name) \
16 (offsetof(Tegra::Engines::MaxwellCompute::Regs, field_name) / sizeof(u32))
17
11class MaxwellCompute final { 18class MaxwellCompute final {
12public: 19public:
13 MaxwellCompute() = default; 20 MaxwellCompute() = default;
14 ~MaxwellCompute() = default; 21 ~MaxwellCompute() = default;
15 22
23 struct Regs {
24 static constexpr std::size_t NUM_REGS = 0xCF8;
25
26 union {
27 struct {
28 INSERT_PADDING_WORDS(0x281);
29
30 union {
31 u32 compute_end;
32 BitField<0, 1, u32> unknown;
33 } compute;
34
35 INSERT_PADDING_WORDS(0xA76);
36 };
37 std::array<u32, NUM_REGS> reg_array;
38 };
39 } regs{};
40
41 static_assert(sizeof(Regs) == Regs::NUM_REGS * sizeof(u32),
42 "MaxwellCompute Regs has wrong size");
43
16 /// Write the value to the register identified by method. 44 /// Write the value to the register identified by method.
17 void WriteReg(u32 method, u32 value); 45 void WriteReg(u32 method, u32 value);
18}; 46};
19 47
48#define ASSERT_REG_POSITION(field_name, position) \
49 static_assert(offsetof(MaxwellCompute::Regs, field_name) == position * 4, \
50 "Field " #field_name " has invalid position")
51
52ASSERT_REG_POSITION(compute, 0x281);
53
54#undef ASSERT_REG_POSITION
55
20} // namespace Tegra::Engines 56} // namespace Tegra::Engines
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 70fb54507..44850d193 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -450,6 +450,8 @@ void RasterizerOpenGL::DrawArrays() {
450 SyncBlendState(); 450 SyncBlendState();
451 SyncLogicOpState(); 451 SyncLogicOpState();
452 SyncCullMode(); 452 SyncCullMode();
453 SyncAlphaTest();
454 SyncTransformFeedback();
453 455
454 // TODO(bunnei): Sync framebuffer_scale uniform here 456 // TODO(bunnei): Sync framebuffer_scale uniform here
455 // TODO(bunnei): Sync scissorbox uniform(s) here 457 // TODO(bunnei): Sync scissorbox uniform(s) here
@@ -883,4 +885,24 @@ void RasterizerOpenGL::SyncLogicOpState() {
883 state.logic_op.operation = MaxwellToGL::LogicOp(regs.logic_op.operation); 885 state.logic_op.operation = MaxwellToGL::LogicOp(regs.logic_op.operation);
884} 886}
885 887
888void RasterizerOpenGL::SyncAlphaTest() {
889 const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
890
891 // TODO(Rodrigo): Alpha testing is a legacy OpenGL feature, but it can be
892 // implemented with a test+discard in fragment shaders.
893 if (regs.alpha_test_enabled != 0) {
894 LOG_CRITICAL(Render_OpenGL, "Alpha testing is not implemented");
895 UNREACHABLE();
896 }
897}
898
899void RasterizerOpenGL::SyncTransformFeedback() {
900 const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
901
902 if (regs.tfb_enabled != 0) {
903 LOG_CRITICAL(Render_OpenGL, "Transform feedbacks are not implemented");
904 UNREACHABLE();
905 }
906}
907
886} // namespace OpenGL 908} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index bf9560bdc..c3f1e14bf 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -158,6 +158,12 @@ private:
158 /// Syncs the LogicOp state to match the guest state 158 /// Syncs the LogicOp state to match the guest state
159 void SyncLogicOpState(); 159 void SyncLogicOpState();
160 160
161 /// Syncs the alpha test state to match the guest state
162 void SyncAlphaTest();
163
164 /// Syncs the transform feedback state to match the guest state
165 void SyncTransformFeedback();
166
161 bool has_ARB_direct_state_access = false; 167 bool has_ARB_direct_state_access = false;
162 bool has_ARB_multi_bind = false; 168 bool has_ARB_multi_bind = false;
163 bool has_ARB_separate_shader_objects = false; 169 bool has_ARB_separate_shader_objects = false;
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..d74489935 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
759static std::size_t CalculateRomFSEntrySize(const FileSys::VirtualDir& dir, bool full) {
760 std::size_t out = 0;
761
762 for (const auto& subdir : dir->GetSubdirectories()) {
763 out += 1 + CalculateRomFSEntrySize(subdir, full);
764 }
765
766 return out + (full ? dir->GetFiles().size() : 0);
767}
768
769static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src,
770 const FileSys::VirtualDir& dest, std::size_t block_size, bool full) {
771 if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
772 return false;
773 if (dialog.wasCanceled())
774 return false;
775
776 if (full) {
777 for (const auto& file : src->GetFiles()) {
778 const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName());
779 if (!FileSys::VfsRawCopy(file, out, block_size))
780 return false;
781 dialog.setValue(dialog.value() + 1);
782 if (dialog.wasCanceled())
783 return false;
784 }
785 }
786
787 for (const auto& dir : src->GetSubdirectories()) {
788 const auto out = dest->CreateSubdirectory(dir->GetName());
789 if (!RomFSRawCopy(dialog, dir, out, block_size, full))
790 return false;
791 dialog.setValue(dialog.value() + 1);
792 if (dialog.wasCanceled())
793 return false;
794 }
795
796 return true;
797}
798
799void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) {
800 const auto path = fmt::format("{}{:016X}/romfs",
801 FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id);
802
803 const auto failed = [this, &path] {
804 QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
805 tr("There was an error copying the RomFS files or the user "
806 "cancelled the operation."));
807 vfs->DeleteDirectory(path);
808 };
809
810 const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read));
811 if (loader == nullptr) {
812 failed();
813 return;
814 }
815
816 FileSys::VirtualFile file;
817 if (loader->ReadRomFS(file) != Loader::ResultStatus::Success) {
818 failed();
819 return;
820 }
821
822 const auto romfs =
823 loader->IsRomFSUpdatable()
824 ? FileSys::PatchManager(program_id).PatchRomFS(file, loader->ReadRomFSIVFCOffset())
825 : file;
826
827 const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
828 if (extracted == nullptr) {
829 failed();
830 return;
831 }
832
833 const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite);
834
835 if (out == nullptr) {
836 failed();
837 return;
838 }
839
840 bool ok;
841 const auto res = QInputDialog::getItem(
842 this, tr("Select RomFS Dump Mode"),
843 tr("Please select the how you would like the RomFS dumped.<br>Full will copy all of the "
844 "files into the new directory while <br>skeleton will only create the directory "
845 "structure."),
846 {"Full", "Skeleton"}, 0, false, &ok);
847 if (!ok)
848 failed();
849
850 const auto full = res == "Full";
851 const auto entry_size = CalculateRomFSEntrySize(extracted, full);
852
853 QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, entry_size, this);
854 progress.setWindowModality(Qt::WindowModal);
855 progress.setMinimumDuration(100);
856
857 if (RomFSRawCopy(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, std::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();