summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/audio_core/audio_renderer.cpp4
-rw-r--r--src/audio_core/audio_renderer.h1
-rw-r--r--src/audio_core/stream.cpp5
-rw-r--r--src/audio_core/stream.h3
-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/common/logging/backend.cpp1
-rw-r--r--src/common/logging/log.h1
-rw-r--r--src/common/thread.h8
-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/kernel/svc.cpp14
-rw-r--r--src/core/hle/service/audio/audren_u.cpp9
-rw-r--r--src/core/hle/service/fatal/fatal.cpp141
-rw-r--r--src/core/hle/service/fatal/fatal.h1
-rw-r--r--src/core/hle/service/fatal/fatal_u.cpp2
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp13
-rw-r--r--src/core/hle/service/filesystem/filesystem.h2
-rw-r--r--src/core/hle/service/hid/irs.cpp158
-rw-r--r--src/core/hle/service/hid/irs.h27
-rw-r--r--src/core/hle/service/ssl/ssl.cpp6
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp7
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.h21
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp2
-rw-r--r--src/video_core/utils.h22
-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
50 files changed, 1593 insertions, 79 deletions
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp
index 83b75e61f..521b19ff7 100644
--- a/src/audio_core/audio_renderer.cpp
+++ b/src/audio_core/audio_renderer.cpp
@@ -79,6 +79,10 @@ u32 AudioRenderer::GetMixBufferCount() const {
79 return worker_params.mix_buffer_count; 79 return worker_params.mix_buffer_count;
80} 80}
81 81
82u32 AudioRenderer::GetState() const {
83 return stream->GetState();
84}
85
82std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) { 86std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) {
83 // Copy UpdateDataHeader struct 87 // Copy UpdateDataHeader struct
84 UpdateDataHeader config{}; 88 UpdateDataHeader config{};
diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h
index 2c4f5ab75..be923ee65 100644
--- a/src/audio_core/audio_renderer.h
+++ b/src/audio_core/audio_renderer.h
@@ -170,6 +170,7 @@ public:
170 u32 GetSampleRate() const; 170 u32 GetSampleRate() const;
171 u32 GetSampleCount() const; 171 u32 GetSampleCount() const;
172 u32 GetMixBufferCount() const; 172 u32 GetMixBufferCount() const;
173 u32 GetState() const;
173 174
174private: 175private:
175 class VoiceState; 176 class VoiceState;
diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp
index 449db2416..ee4aa98af 100644
--- a/src/audio_core/stream.cpp
+++ b/src/audio_core/stream.cpp
@@ -49,9 +49,14 @@ void Stream::Play() {
49} 49}
50 50
51void Stream::Stop() { 51void Stream::Stop() {
52 state = State::Stopped;
52 ASSERT_MSG(false, "Unimplemented"); 53 ASSERT_MSG(false, "Unimplemented");
53} 54}
54 55
56u32 Stream::GetState() const {
57 return static_cast<u32>(state);
58}
59
55s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const { 60s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const {
56 const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()}; 61 const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()};
57 return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate); 62 return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate);
diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h
index 27db1112f..43eca74e1 100644
--- a/src/audio_core/stream.h
+++ b/src/audio_core/stream.h
@@ -72,6 +72,9 @@ public:
72 /// Gets the number of channels 72 /// Gets the number of channels
73 u32 GetNumChannels() const; 73 u32 GetNumChannels() const;
74 74
75 /// Get the state
76 u32 GetState() const;
77
75private: 78private:
76 /// Current state of the stream 79 /// Current state of the stream
77 enum class State { 80 enum class State {
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/common/logging/backend.cpp b/src/common/logging/backend.cpp
index efd776db6..9f5918851 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -183,6 +183,7 @@ void FileBackend::Write(const Entry& entry) {
183 SUB(Service, FS) \ 183 SUB(Service, FS) \
184 SUB(Service, GRC) \ 184 SUB(Service, GRC) \
185 SUB(Service, HID) \ 185 SUB(Service, HID) \
186 SUB(Service, IRS) \
186 SUB(Service, LBL) \ 187 SUB(Service, LBL) \
187 SUB(Service, LDN) \ 188 SUB(Service, LDN) \
188 SUB(Service, LDR) \ 189 SUB(Service, LDR) \
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 4d577524f..abbd056ee 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -70,6 +70,7 @@ enum class Class : ClassType {
70 Service_FS, ///< The FS (Filesystem) service 70 Service_FS, ///< The FS (Filesystem) service
71 Service_GRC, ///< The game recording service 71 Service_GRC, ///< The game recording service
72 Service_HID, ///< The HID (Human interface device) service 72 Service_HID, ///< The HID (Human interface device) service
73 Service_IRS, ///< The IRS service
73 Service_LBL, ///< The LBL (LCD backlight) service 74 Service_LBL, ///< The LBL (LCD backlight) service
74 Service_LDN, ///< The LDN (Local domain network) service 75 Service_LDN, ///< The LDN (Local domain network) service
75 Service_LDR, ///< The loader service 76 Service_LDR, ///< The loader service
diff --git a/src/common/thread.h b/src/common/thread.h
index 12a1c095c..6cbdb96a3 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -87,14 +87,6 @@ private:
87 87
88void SleepCurrentThread(int ms); 88void SleepCurrentThread(int ms);
89void SwitchCurrentThread(); // On Linux, this is equal to sleep 1ms 89void SwitchCurrentThread(); // On Linux, this is equal to sleep 1ms
90
91// Use this function during a spin-wait to make the current thread
92// relax while another thread is working. This may be more efficient
93// than using events because event functions use kernel calls.
94inline void YieldCPU() {
95 std::this_thread::yield();
96}
97
98void SetCurrentThreadName(const char* name); 90void SetCurrentThreadName(const char* name);
99 91
100} // namespace Common 92} // namespace Common
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/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 0bc407098..c9d212a4c 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -1017,7 +1017,7 @@ static const FunctionDef SVC_Table[] = {
1017 {0x2B, nullptr, "FlushDataCache"}, 1017 {0x2B, nullptr, "FlushDataCache"},
1018 {0x2C, nullptr, "MapPhysicalMemory"}, 1018 {0x2C, nullptr, "MapPhysicalMemory"},
1019 {0x2D, nullptr, "UnmapPhysicalMemory"}, 1019 {0x2D, nullptr, "UnmapPhysicalMemory"},
1020 {0x2E, nullptr, "GetNextThreadInfo"}, 1020 {0x2E, nullptr, "GetFutureThreadInfo"},
1021 {0x2F, nullptr, "GetLastThreadInfo"}, 1021 {0x2F, nullptr, "GetLastThreadInfo"},
1022 {0x30, nullptr, "GetResourceLimitLimitValue"}, 1022 {0x30, nullptr, "GetResourceLimitLimitValue"},
1023 {0x31, nullptr, "GetResourceLimitCurrentValue"}, 1023 {0x31, nullptr, "GetResourceLimitCurrentValue"},
@@ -1043,11 +1043,11 @@ static const FunctionDef SVC_Table[] = {
1043 {0x45, nullptr, "CreateEvent"}, 1043 {0x45, nullptr, "CreateEvent"},
1044 {0x46, nullptr, "Unknown"}, 1044 {0x46, nullptr, "Unknown"},
1045 {0x47, nullptr, "Unknown"}, 1045 {0x47, nullptr, "Unknown"},
1046 {0x48, nullptr, "AllocateUnsafeMemory"}, 1046 {0x48, nullptr, "MapPhysicalMemoryUnsafe"},
1047 {0x49, nullptr, "FreeUnsafeMemory"}, 1047 {0x49, nullptr, "UnmapPhysicalMemoryUnsafe"},
1048 {0x4A, nullptr, "SetUnsafeAllocationLimit"}, 1048 {0x4A, nullptr, "SetUnsafeLimit"},
1049 {0x4B, nullptr, "CreateJitMemory"}, 1049 {0x4B, nullptr, "CreateCodeMemory"},
1050 {0x4C, nullptr, "MapJitMemory"}, 1050 {0x4C, nullptr, "ControlCodeMemory"},
1051 {0x4D, nullptr, "SleepSystem"}, 1051 {0x4D, nullptr, "SleepSystem"},
1052 {0x4E, nullptr, "ReadWriteRegister"}, 1052 {0x4E, nullptr, "ReadWriteRegister"},
1053 {0x4F, nullptr, "SetProcessActivity"}, 1053 {0x4F, nullptr, "SetProcessActivity"},
@@ -1082,7 +1082,7 @@ static const FunctionDef SVC_Table[] = {
1082 {0x6C, nullptr, "SetHardwareBreakPoint"}, 1082 {0x6C, nullptr, "SetHardwareBreakPoint"},
1083 {0x6D, nullptr, "GetDebugThreadParam"}, 1083 {0x6D, nullptr, "GetDebugThreadParam"},
1084 {0x6E, nullptr, "Unknown"}, 1084 {0x6E, nullptr, "Unknown"},
1085 {0x6F, nullptr, "GetMemoryInfo"}, 1085 {0x6F, nullptr, "GetSystemInfo"},
1086 {0x70, nullptr, "CreatePort"}, 1086 {0x70, nullptr, "CreatePort"},
1087 {0x71, nullptr, "ManageNamedPort"}, 1087 {0x71, nullptr, "ManageNamedPort"},
1088 {0x72, nullptr, "ConnectToPort"}, 1088 {0x72, nullptr, "ConnectToPort"},
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index 06ac6372d..80ed4b152 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -25,7 +25,7 @@ public:
25 {0, &IAudioRenderer::GetAudioRendererSampleRate, "GetAudioRendererSampleRate"}, 25 {0, &IAudioRenderer::GetAudioRendererSampleRate, "GetAudioRendererSampleRate"},
26 {1, &IAudioRenderer::GetAudioRendererSampleCount, "GetAudioRendererSampleCount"}, 26 {1, &IAudioRenderer::GetAudioRendererSampleCount, "GetAudioRendererSampleCount"},
27 {2, &IAudioRenderer::GetAudioRendererMixBufferCount, "GetAudioRendererMixBufferCount"}, 27 {2, &IAudioRenderer::GetAudioRendererMixBufferCount, "GetAudioRendererMixBufferCount"},
28 {3, nullptr, "GetAudioRendererState"}, 28 {3, &IAudioRenderer::GetAudioRendererState, "GetAudioRendererState"},
29 {4, &IAudioRenderer::RequestUpdateAudioRenderer, "RequestUpdateAudioRenderer"}, 29 {4, &IAudioRenderer::RequestUpdateAudioRenderer, "RequestUpdateAudioRenderer"},
30 {5, &IAudioRenderer::StartAudioRenderer, "StartAudioRenderer"}, 30 {5, &IAudioRenderer::StartAudioRenderer, "StartAudioRenderer"},
31 {6, &IAudioRenderer::StopAudioRenderer, "StopAudioRenderer"}, 31 {6, &IAudioRenderer::StopAudioRenderer, "StopAudioRenderer"},
@@ -62,6 +62,13 @@ private:
62 LOG_DEBUG(Service_Audio, "called"); 62 LOG_DEBUG(Service_Audio, "called");
63 } 63 }
64 64
65 void GetAudioRendererState(Kernel::HLERequestContext& ctx) {
66 IPC::ResponseBuilder rb{ctx, 3};
67 rb.Push(RESULT_SUCCESS);
68 rb.Push<u32>(renderer->GetState());
69 LOG_DEBUG(Service_Audio, "called");
70 }
71
65 void GetAudioRendererMixBufferCount(Kernel::HLERequestContext& ctx) { 72 void GetAudioRendererMixBufferCount(Kernel::HLERequestContext& ctx) {
66 IPC::ResponseBuilder rb{ctx, 3}; 73 IPC::ResponseBuilder rb{ctx, 3};
67 rb.Push(RESULT_SUCCESS); 74 rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp
index b436ce4e6..6de7edf9e 100644
--- a/src/core/hle/service/fatal/fatal.cpp
+++ b/src/core/hle/service/fatal/fatal.cpp
@@ -2,8 +2,17 @@
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 <array>
6#include <cstring>
7#include <ctime>
8#include <fmt/time.h>
9#include "common/common_paths.h"
10#include "common/file_util.h"
5#include "common/logging/log.h" 11#include "common/logging/log.h"
12#include "common/scm_rev.h"
13#include "common/swap.h"
6#include "core/hle/ipc_helpers.h" 14#include "core/hle/ipc_helpers.h"
15#include "core/hle/kernel/process.h"
7#include "core/hle/service/fatal/fatal.h" 16#include "core/hle/service/fatal/fatal.h"
8#include "core/hle/service/fatal/fatal_p.h" 17#include "core/hle/service/fatal/fatal_p.h"
9#include "core/hle/service/fatal/fatal_u.h" 18#include "core/hle/service/fatal/fatal_u.h"
@@ -15,16 +24,142 @@ Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
15 24
16Module::Interface::~Interface() = default; 25Module::Interface::~Interface() = default;
17 26
27struct FatalInfo {
28 std::array<u64_le, 31> registers{}; // TODO(ogniK): See if this actually is registers or
29 // not(find a game which has non zero valeus)
30 u64_le unk0{};
31 u64_le unk1{};
32 u64_le unk2{};
33 u64_le unk3{};
34 u64_le unk4{};
35 u64_le unk5{};
36 u64_le unk6{};
37
38 std::array<u64_le, 32> backtrace{};
39 u64_le unk7{};
40 u64_le unk8{};
41 u32_le backtrace_size{};
42 u32_le unk9{};
43 u32_le unk10{}; // TODO(ogniK): Is this even used or is it just padding?
44};
45static_assert(sizeof(FatalInfo) == 0x250, "FatalInfo is an invalid size");
46
47enum class FatalType : u32 {
48 ErrorReportAndScreen = 0,
49 ErrorReport = 1,
50 ErrorScreen = 2,
51};
52
53static void GenerateErrorReport(ResultCode error_code, const FatalInfo& info) {
54 const auto title_id = Core::CurrentProcess()->program_id;
55 std::string crash_report =
56 fmt::format("Yuzu {}-{} crash report\n"
57 "Title ID: {:016x}\n"
58 "Result: 0x{:X} ({:04}-{:04d})\n"
59 "\n",
60 Common::g_scm_branch, Common::g_scm_desc, title_id, error_code.raw,
61 2000 + static_cast<u32>(error_code.module.Value()),
62 static_cast<u32>(error_code.description.Value()), info.unk8, info.unk7);
63 if (info.backtrace_size != 0x0) {
64 crash_report += "Registers:\n";
65 // TODO(ogniK): This is just a guess, find a game which actually has non zero values
66 for (size_t i = 0; i < info.registers.size(); i++) {
67 crash_report +=
68 fmt::format(" X[{:02d}]: {:016x}\n", i, info.registers[i]);
69 }
70 crash_report += fmt::format(" Unknown 0: {:016x}\n", info.unk0);
71 crash_report += fmt::format(" Unknown 1: {:016x}\n", info.unk1);
72 crash_report += fmt::format(" Unknown 2: {:016x}\n", info.unk2);
73 crash_report += fmt::format(" Unknown 3: {:016x}\n", info.unk3);
74 crash_report += fmt::format(" Unknown 4: {:016x}\n", info.unk4);
75 crash_report += fmt::format(" Unknown 5: {:016x}\n", info.unk5);
76 crash_report += fmt::format(" Unknown 6: {:016x}\n", info.unk6);
77 crash_report += "\nBacktrace:\n";
78 for (size_t i = 0; i < info.backtrace_size; i++) {
79 crash_report +=
80 fmt::format(" Backtrace[{:02d}]: {:016x}\n", i, info.backtrace[i]);
81 }
82 crash_report += fmt::format("\nUnknown 7: 0x{:016x}\n", info.unk7);
83 crash_report += fmt::format("Unknown 8: 0x{:016x}\n", info.unk8);
84 crash_report += fmt::format("Unknown 9: 0x{:016x}\n", info.unk9);
85 crash_report += fmt::format("Unknown 10: 0x{:016x}\n", info.unk10);
86 }
87
88 LOG_ERROR(Service_Fatal, "{}", crash_report);
89
90 const std::string crashreport_dir =
91 FileUtil::GetUserPath(FileUtil::UserPath::LogDir) + "crash_logs";
92
93 if (!FileUtil::CreateFullPath(crashreport_dir)) {
94 LOG_ERROR(
95 Service_Fatal,
96 "Unable to create crash report directory. Possible log directory permissions issue.");
97 return;
98 }
99
100 const std::time_t t = std::time(nullptr);
101 const std::string crashreport_filename =
102 fmt::format("{}/{:016x}-{:%F-%H%M%S}.log", crashreport_dir, title_id, *std::localtime(&t));
103
104 auto file = FileUtil::IOFile(crashreport_filename, "wb");
105 if (file.IsOpen()) {
106 file.WriteString(crash_report);
107 LOG_ERROR(Service_Fatal, "Saving error report to {}", crashreport_filename);
108 } else {
109 LOG_ERROR(Service_Fatal, "Failed to save error report to {}", crashreport_filename);
110 }
111}
112
113static void ThrowFatalError(ResultCode error_code, FatalType fatal_type, const FatalInfo& info) {
114 LOG_ERROR(Service_Fatal, "Threw fatal error type {}", static_cast<u32>(fatal_type));
115 switch (fatal_type) {
116 case FatalType::ErrorReportAndScreen:
117 GenerateErrorReport(error_code, info);
118 [[fallthrough]];
119 case FatalType::ErrorScreen:
120 // Since we have no fatal:u error screen. We should just kill execution instead
121 ASSERT(false);
122 break;
123 // Should not throw a fatal screen but should generate an error report
124 case FatalType::ErrorReport:
125 GenerateErrorReport(error_code, info);
126 break;
127 };
128}
129
130void Module::Interface::ThrowFatal(Kernel::HLERequestContext& ctx) {
131 LOG_ERROR(Service_Fatal, "called");
132 IPC::RequestParser rp{ctx};
133 auto error_code = rp.Pop<ResultCode>();
134
135 ThrowFatalError(error_code, FatalType::ErrorScreen, {});
136 IPC::ResponseBuilder rb{ctx, 2};
137 rb.Push(RESULT_SUCCESS);
138}
139
18void Module::Interface::ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx) { 140void Module::Interface::ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx) {
141 LOG_ERROR(Service_Fatal, "called");
19 IPC::RequestParser rp(ctx); 142 IPC::RequestParser rp(ctx);
20 u32 error_code = rp.Pop<u32>(); 143 auto error_code = rp.Pop<ResultCode>();
21 LOG_WARNING(Service_Fatal, "(STUBBED) called, error_code=0x{:X}", error_code); 144 auto fatal_type = rp.PopEnum<FatalType>();
145
146 ThrowFatalError(error_code, fatal_type, {}); // No info is passed with ThrowFatalWithPolicy
22 IPC::ResponseBuilder rb{ctx, 2}; 147 IPC::ResponseBuilder rb{ctx, 2};
23 rb.Push(RESULT_SUCCESS); 148 rb.Push(RESULT_SUCCESS);
24} 149}
25 150
26void Module::Interface::ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx) { 151void Module::Interface::ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx) {
27 LOG_WARNING(Service_Fatal, "(STUBBED) called"); 152 LOG_ERROR(Service_Fatal, "called");
153 IPC::RequestParser rp(ctx);
154 auto error_code = rp.Pop<ResultCode>();
155 auto fatal_type = rp.PopEnum<FatalType>();
156 auto fatal_info = ctx.ReadBuffer();
157 FatalInfo info{};
158
159 ASSERT_MSG(fatal_info.size() == sizeof(FatalInfo), "Invalid fatal info buffer size!");
160 std::memcpy(&info, fatal_info.data(), sizeof(FatalInfo));
161
162 ThrowFatalError(error_code, fatal_type, info);
28 IPC::ResponseBuilder rb{ctx, 2}; 163 IPC::ResponseBuilder rb{ctx, 2};
29 rb.Push(RESULT_SUCCESS); 164 rb.Push(RESULT_SUCCESS);
30} 165}
diff --git a/src/core/hle/service/fatal/fatal.h b/src/core/hle/service/fatal/fatal.h
index 4d9a5be52..09371ff7f 100644
--- a/src/core/hle/service/fatal/fatal.h
+++ b/src/core/hle/service/fatal/fatal.h
@@ -15,6 +15,7 @@ public:
15 explicit Interface(std::shared_ptr<Module> module, const char* name); 15 explicit Interface(std::shared_ptr<Module> module, const char* name);
16 ~Interface() override; 16 ~Interface() override;
17 17
18 void ThrowFatal(Kernel::HLERequestContext& ctx);
18 void ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx); 19 void ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx);
19 void ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx); 20 void ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx);
20 21
diff --git a/src/core/hle/service/fatal/fatal_u.cpp b/src/core/hle/service/fatal/fatal_u.cpp
index befc307cf..1572a2051 100644
--- a/src/core/hle/service/fatal/fatal_u.cpp
+++ b/src/core/hle/service/fatal/fatal_u.cpp
@@ -8,7 +8,7 @@ namespace Service::Fatal {
8 8
9Fatal_U::Fatal_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "fatal:u") { 9Fatal_U::Fatal_U(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "fatal:u") {
10 static const FunctionInfo functions[] = { 10 static const FunctionInfo functions[] = {
11 {0, nullptr, "ThrowFatal"}, 11 {0, &Fatal_U::ThrowFatal, "ThrowFatal"},
12 {1, &Fatal_U::ThrowFatalWithPolicy, "ThrowFatalWithPolicy"}, 12 {1, &Fatal_U::ThrowFatalWithPolicy, "ThrowFatalWithPolicy"},
13 {2, &Fatal_U::ThrowFatalWithCpuContext, "ThrowFatalWithCpuContext"}, 13 {2, &Fatal_U::ThrowFatalWithCpuContext, "ThrowFatalWithCpuContext"},
14 }; 14 };
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/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index e587ad0d8..872e3c344 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -2,6 +2,11 @@
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/swap.h"
6#include "core/core.h"
7#include "core/core_timing.h"
8#include "core/hle/ipc_helpers.h"
9#include "core/hle/kernel/shared_memory.h"
5#include "core/hle/service/hid/irs.h" 10#include "core/hle/service/hid/irs.h"
6 11
7namespace Service::HID { 12namespace Service::HID {
@@ -9,28 +14,145 @@ namespace Service::HID {
9IRS::IRS() : ServiceFramework{"irs"} { 14IRS::IRS() : ServiceFramework{"irs"} {
10 // clang-format off 15 // clang-format off
11 static const FunctionInfo functions[] = { 16 static const FunctionInfo functions[] = {
12 {302, nullptr, "ActivateIrsensor"}, 17 {302, &IRS::ActivateIrsensor, "ActivateIrsensor"},
13 {303, nullptr, "DeactivateIrsensor"}, 18 {303, &IRS::DeactivateIrsensor, "DeactivateIrsensor"},
14 {304, nullptr, "GetIrsensorSharedMemoryHandle"}, 19 {304, &IRS::GetIrsensorSharedMemoryHandle, "GetIrsensorSharedMemoryHandle"},
15 {305, nullptr, "StopImageProcessor"}, 20 {305, &IRS::StopImageProcessor, "StopImageProcessor"},
16 {306, nullptr, "RunMomentProcessor"}, 21 {306, &IRS::RunMomentProcessor, "RunMomentProcessor"},
17 {307, nullptr, "RunClusteringProcessor"}, 22 {307, &IRS::RunClusteringProcessor, "RunClusteringProcessor"},
18 {308, nullptr, "RunImageTransferProcessor"}, 23 {308, &IRS::RunImageTransferProcessor, "RunImageTransferProcessor"},
19 {309, nullptr, "GetImageTransferProcessorState"}, 24 {309, &IRS::GetImageTransferProcessorState, "GetImageTransferProcessorState"},
20 {310, nullptr, "RunTeraPluginProcessor"}, 25 {310, &IRS::RunTeraPluginProcessor, "RunTeraPluginProcessor"},
21 {311, nullptr, "GetNpadIrCameraHandle"}, 26 {311, &IRS::GetNpadIrCameraHandle, "GetNpadIrCameraHandle"},
22 {312, nullptr, "RunPointingProcessor"}, 27 {312, &IRS::RunPointingProcessor, "RunPointingProcessor"},
23 {313, nullptr, "SuspendImageProcessor"}, 28 {313, &IRS::SuspendImageProcessor, "SuspendImageProcessor"},
24 {314, nullptr, "CheckFirmwareVersion"}, 29 {314, &IRS::CheckFirmwareVersion, "CheckFirmwareVersion"},
25 {315, nullptr, "SetFunctionLevel"}, 30 {315, &IRS::SetFunctionLevel, "SetFunctionLevel"},
26 {316, nullptr, "RunImageTransferExProcessor"}, 31 {316, &IRS::RunImageTransferExProcessor, "RunImageTransferExProcessor"},
27 {317, nullptr, "RunIrLedProcessor"}, 32 {317, &IRS::RunIrLedProcessor, "RunIrLedProcessor"},
28 {318, nullptr, "StopImageProcessorAsync"}, 33 {318, &IRS::StopImageProcessorAsync, "StopImageProcessorAsync"},
29 {319, nullptr, "ActivateIrsensorWithFunctionLevel"}, 34 {319, &IRS::ActivateIrsensorWithFunctionLevel, "ActivateIrsensorWithFunctionLevel"},
30 }; 35 };
31 // clang-format on 36 // clang-format on
32 37
33 RegisterHandlers(functions); 38 RegisterHandlers(functions);
39
40 auto& kernel = Core::System::GetInstance().Kernel();
41 shared_mem = Kernel::SharedMemory::Create(
42 kernel, nullptr, 0x8000, Kernel::MemoryPermission::ReadWrite,
43 Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "IRS:SharedMemory");
44}
45
46void IRS::ActivateIrsensor(Kernel::HLERequestContext& ctx) {
47 IPC::ResponseBuilder rb{ctx, 2};
48 rb.Push(RESULT_SUCCESS);
49 LOG_WARNING(Service_IRS, "(STUBBED) called");
50}
51
52void IRS::DeactivateIrsensor(Kernel::HLERequestContext& ctx) {
53 IPC::ResponseBuilder rb{ctx, 2};
54 rb.Push(RESULT_SUCCESS);
55 LOG_WARNING(Service_IRS, "(STUBBED) called");
56}
57
58void IRS::GetIrsensorSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
59 IPC::ResponseBuilder rb{ctx, 2, 1};
60 rb.Push(RESULT_SUCCESS);
61 rb.PushCopyObjects(shared_mem);
62 LOG_DEBUG(Service_IRS, "called");
63}
64
65void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) {
66 IPC::ResponseBuilder rb{ctx, 2};
67 rb.Push(RESULT_SUCCESS);
68 LOG_WARNING(Service_IRS, "(STUBBED) called");
69}
70
71void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) {
72 IPC::ResponseBuilder rb{ctx, 2};
73 rb.Push(RESULT_SUCCESS);
74 LOG_WARNING(Service_IRS, "(STUBBED) called");
75}
76
77void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) {
78 IPC::ResponseBuilder rb{ctx, 2};
79 rb.Push(RESULT_SUCCESS);
80 LOG_WARNING(Service_IRS, "(STUBBED) called");
81}
82
83void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) {
84 IPC::ResponseBuilder rb{ctx, 2};
85 rb.Push(RESULT_SUCCESS);
86 LOG_WARNING(Service_IRS, "(STUBBED) called");
87}
88
89void IRS::GetImageTransferProcessorState(Kernel::HLERequestContext& ctx) {
90 IPC::ResponseBuilder rb{ctx, 5};
91 rb.Push(RESULT_SUCCESS);
92 rb.PushRaw<u64>(CoreTiming::GetTicks());
93 rb.PushRaw<u32>(0);
94 LOG_WARNING(Service_IRS, "(STUBBED) called");
95}
96
97void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) {
98 IPC::ResponseBuilder rb{ctx, 2};
99 rb.Push(RESULT_SUCCESS);
100 LOG_WARNING(Service_IRS, "(STUBBED) called");
101}
102
103void IRS::GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx) {
104 IPC::ResponseBuilder rb{ctx, 3};
105 rb.Push(RESULT_SUCCESS);
106 rb.PushRaw<u32>(device_handle);
107 LOG_WARNING(Service_IRS, "(STUBBED) called");
108}
109
110void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) {
111 IPC::ResponseBuilder rb{ctx, 2};
112 rb.Push(RESULT_SUCCESS);
113 LOG_WARNING(Service_IRS, "(STUBBED) called");
114}
115
116void IRS::SuspendImageProcessor(Kernel::HLERequestContext& ctx) {
117 IPC::ResponseBuilder rb{ctx, 2};
118 rb.Push(RESULT_SUCCESS);
119 LOG_WARNING(Service_IRS, "(STUBBED) called");
120}
121
122void IRS::CheckFirmwareVersion(Kernel::HLERequestContext& ctx) {
123 IPC::ResponseBuilder rb{ctx, 2};
124 rb.Push(RESULT_SUCCESS);
125 LOG_WARNING(Service_IRS, "(STUBBED) called");
126}
127
128void IRS::SetFunctionLevel(Kernel::HLERequestContext& ctx) {
129 IPC::ResponseBuilder rb{ctx, 2};
130 rb.Push(RESULT_SUCCESS);
131 LOG_WARNING(Service_IRS, "(STUBBED) called");
132}
133
134void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) {
135 IPC::ResponseBuilder rb{ctx, 2};
136 rb.Push(RESULT_SUCCESS);
137 LOG_WARNING(Service_IRS, "(STUBBED) called");
138}
139
140void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) {
141 IPC::ResponseBuilder rb{ctx, 2};
142 rb.Push(RESULT_SUCCESS);
143 LOG_WARNING(Service_IRS, "(STUBBED) called");
144}
145
146void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) {
147 IPC::ResponseBuilder rb{ctx, 2};
148 rb.Push(RESULT_SUCCESS);
149 LOG_WARNING(Service_IRS, "(STUBBED) called");
150}
151
152void IRS::ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx) {
153 IPC::ResponseBuilder rb{ctx, 2};
154 rb.Push(RESULT_SUCCESS);
155 LOG_WARNING(Service_IRS, "(STUBBED) called");
34} 156}
35 157
36IRS::~IRS() = default; 158IRS::~IRS() = default;
diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h
index 6fb16b45d..12de6bfb3 100644
--- a/src/core/hle/service/hid/irs.h
+++ b/src/core/hle/service/hid/irs.h
@@ -4,14 +4,41 @@
4 4
5#pragma once 5#pragma once
6 6
7#include "core/hle/kernel/object.h"
7#include "core/hle/service/service.h" 8#include "core/hle/service/service.h"
8 9
10namespace Kernel {
11class SharedMemory;
12}
13
9namespace Service::HID { 14namespace Service::HID {
10 15
11class IRS final : public ServiceFramework<IRS> { 16class IRS final : public ServiceFramework<IRS> {
12public: 17public:
13 explicit IRS(); 18 explicit IRS();
14 ~IRS() override; 19 ~IRS() override;
20
21private:
22 void ActivateIrsensor(Kernel::HLERequestContext& ctx);
23 void DeactivateIrsensor(Kernel::HLERequestContext& ctx);
24 void GetIrsensorSharedMemoryHandle(Kernel::HLERequestContext& ctx);
25 void StopImageProcessor(Kernel::HLERequestContext& ctx);
26 void RunMomentProcessor(Kernel::HLERequestContext& ctx);
27 void RunClusteringProcessor(Kernel::HLERequestContext& ctx);
28 void RunImageTransferProcessor(Kernel::HLERequestContext& ctx);
29 void GetImageTransferProcessorState(Kernel::HLERequestContext& ctx);
30 void RunTeraPluginProcessor(Kernel::HLERequestContext& ctx);
31 void GetNpadIrCameraHandle(Kernel::HLERequestContext& ctx);
32 void RunPointingProcessor(Kernel::HLERequestContext& ctx);
33 void SuspendImageProcessor(Kernel::HLERequestContext& ctx);
34 void CheckFirmwareVersion(Kernel::HLERequestContext& ctx);
35 void SetFunctionLevel(Kernel::HLERequestContext& ctx);
36 void RunImageTransferExProcessor(Kernel::HLERequestContext& ctx);
37 void RunIrLedProcessor(Kernel::HLERequestContext& ctx);
38 void StopImageProcessorAsync(Kernel::HLERequestContext& ctx);
39 void ActivateIrsensorWithFunctionLevel(Kernel::HLERequestContext& ctx);
40 Kernel::SharedPtr<Kernel::SharedMemory> shared_mem;
41 const u32 device_handle{0xABCD};
15}; 42};
16 43
17class IRS_SYS final : public ServiceFramework<IRS_SYS> { 44class IRS_SYS final : public ServiceFramework<IRS_SYS> {
diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp
index fe0a318ee..bc4f7a437 100644
--- a/src/core/hle/service/ssl/ssl.cpp
+++ b/src/core/hle/service/ssl/ssl.cpp
@@ -103,6 +103,7 @@ public:
103 } 103 }
104 104
105private: 105private:
106 u32 ssl_version{};
106 void CreateContext(Kernel::HLERequestContext& ctx) { 107 void CreateContext(Kernel::HLERequestContext& ctx) {
107 LOG_WARNING(Service_SSL, "(STUBBED) called"); 108 LOG_WARNING(Service_SSL, "(STUBBED) called");
108 109
@@ -112,10 +113,9 @@ private:
112 } 113 }
113 114
114 void SetInterfaceVersion(Kernel::HLERequestContext& ctx) { 115 void SetInterfaceVersion(Kernel::HLERequestContext& ctx) {
115 LOG_WARNING(Service_SSL, "(STUBBED) called"); 116 LOG_DEBUG(Service_SSL, "called");
116 IPC::RequestParser rp{ctx}; 117 IPC::RequestParser rp{ctx};
117 u32 unk1 = rp.Pop<u32>(); // Probably minor/major? 118 ssl_version = rp.Pop<u32>();
118 u32 unk2 = rp.Pop<u32>(); // TODO(ogniK): Figure out what this does
119 119
120 IPC::ResponseBuilder rb{ctx, 2}; 120 IPC::ResponseBuilder rb{ctx, 2};
121 rb.Push(RESULT_SUCCESS); 121 rb.Push(RESULT_SUCCESS);
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 86682d7cb..24a540258 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -141,8 +141,8 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
141 {GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, 141 {GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
142 true}, // BC7U 142 true}, // BC7U
143 {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, 143 {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB, GL_RGB, GL_UNSIGNED_INT_8_8_8_8,
144 ComponentType::UNorm, true}, // BC6H_UF16 144 ComponentType::Float, true}, // BC6H_UF16
145 {GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, 145 {GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::Float,
146 true}, // BC6H_SF16 146 true}, // BC6H_SF16
147 {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4 147 {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4
148 {GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // G8R8U 148 {GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // G8R8U
@@ -501,6 +501,9 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
501 glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MIN_FILTER, GL_LINEAR); 501 glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MIN_FILTER, GL_LINEAR);
502 glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 502 glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
503 glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 503 glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
504
505 VideoCore::LabelGLObject(GL_TEXTURE, texture.handle, params.addr,
506 SurfaceParams::SurfaceTargetName(params.target));
504} 507}
505 508
506static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) { 509static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
index d7a4bc37f..80c5f324b 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -137,6 +137,27 @@ struct SurfaceParams {
137 } 137 }
138 } 138 }
139 139
140 static std::string SurfaceTargetName(SurfaceTarget target) {
141 switch (target) {
142 case SurfaceTarget::Texture1D:
143 return "Texture1D";
144 case SurfaceTarget::Texture2D:
145 return "Texture2D";
146 case SurfaceTarget::Texture3D:
147 return "Texture3D";
148 case SurfaceTarget::Texture1DArray:
149 return "Texture1DArray";
150 case SurfaceTarget::Texture2DArray:
151 return "Texture2DArray";
152 case SurfaceTarget::TextureCubemap:
153 return "TextureCubemap";
154 default:
155 LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target));
156 UNREACHABLE();
157 return fmt::format("TextureUnknown({})", static_cast<u32>(target));
158 }
159 }
160
140 /** 161 /**
141 * Gets the compression factor for the specified PixelFormat. This applies to just the 162 * Gets the compression factor for the specified PixelFormat. This applies to just the
142 * "compressed width" and "compressed height", not the overall compression factor of a 163 * "compressed width" and "compressed height", not the overall compression factor of a
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 894fe6eae..7cd8f91e4 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -8,6 +8,7 @@
8#include "video_core/engines/maxwell_3d.h" 8#include "video_core/engines/maxwell_3d.h"
9#include "video_core/renderer_opengl/gl_shader_cache.h" 9#include "video_core/renderer_opengl/gl_shader_cache.h"
10#include "video_core/renderer_opengl/gl_shader_manager.h" 10#include "video_core/renderer_opengl/gl_shader_manager.h"
11#include "video_core/utils.h"
11 12
12namespace OpenGL { 13namespace OpenGL {
13 14
@@ -83,6 +84,7 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)
83 shader.Create(program_result.first.c_str(), gl_type); 84 shader.Create(program_result.first.c_str(), gl_type);
84 program.Create(true, shader.handle); 85 program.Create(true, shader.handle);
85 SetShaderUniformBlockBindings(program.handle); 86 SetShaderUniformBlockBindings(program.handle);
87 VideoCore::LabelGLObject(GL_PROGRAM, program.handle, addr);
86} 88}
87 89
88GLuint CachedShader::GetProgramResourceIndex(const GLShader::ConstBufferEntry& buffer) { 90GLuint CachedShader::GetProgramResourceIndex(const GLShader::ConstBufferEntry& buffer) {
diff --git a/src/video_core/utils.h b/src/video_core/utils.h
index e0a14d48f..681919ae3 100644
--- a/src/video_core/utils.h
+++ b/src/video_core/utils.h
@@ -161,4 +161,26 @@ static inline void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixe
161 } 161 }
162} 162}
163 163
164static void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr,
165 std::string extra_info = "") {
166 if (!GLAD_GL_KHR_debug) {
167 return; // We don't need to throw an error as this is just for debugging
168 }
169 const std::string nice_addr = fmt::format("0x{:016x}", addr);
170 std::string object_label;
171
172 switch (identifier) {
173 case GL_TEXTURE:
174 object_label = extra_info + "@" + nice_addr;
175 break;
176 case GL_PROGRAM:
177 object_label = "ShaderProgram@" + nice_addr;
178 break;
179 default:
180 object_label = fmt::format("Object(0x{:x})@{}", identifier, nice_addr);
181 break;
182 }
183 glObjectLabel(identifier, handle, -1, static_cast<const GLchar*>(object_label.c_str()));
184}
185
164} // namespace VideoCore 186} // namespace VideoCore
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();