diff options
Diffstat (limited to 'src')
31 files changed, 1203 insertions, 36 deletions
diff --git a/src/common/common_paths.h b/src/common/common_paths.h index df2ce80b1..4f88de768 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h | |||
| @@ -33,6 +33,8 @@ | |||
| 33 | #define NAND_DIR "nand" | 33 | #define NAND_DIR "nand" |
| 34 | #define SYSDATA_DIR "sysdata" | 34 | #define SYSDATA_DIR "sysdata" |
| 35 | #define KEYS_DIR "keys" | 35 | #define KEYS_DIR "keys" |
| 36 | #define LOAD_DIR "load" | ||
| 37 | #define DUMP_DIR "dump" | ||
| 36 | #define LOG_DIR "log" | 38 | #define LOG_DIR "log" |
| 37 | 39 | ||
| 38 | // Filenames | 40 | // Filenames |
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 21a0b9738..548463787 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp | |||
| @@ -705,6 +705,8 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) { | |||
| 705 | #endif | 705 | #endif |
| 706 | paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP); | 706 | paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP); |
| 707 | paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP); | 707 | paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP); |
| 708 | paths.emplace(UserPath::LoadDir, user_path + LOAD_DIR DIR_SEP); | ||
| 709 | paths.emplace(UserPath::DumpDir, user_path + DUMP_DIR DIR_SEP); | ||
| 708 | paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP); | 710 | paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP); |
| 709 | paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP); | 711 | paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP); |
| 710 | // TODO: Put the logs in a better location for each OS | 712 | // TODO: Put the logs in a better location for each OS |
diff --git a/src/common/file_util.h b/src/common/file_util.h index 24c1e413c..3d8fe6264 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h | |||
| @@ -29,6 +29,8 @@ enum class UserPath { | |||
| 29 | NANDDir, | 29 | NANDDir, |
| 30 | RootDir, | 30 | RootDir, |
| 31 | SDMCDir, | 31 | SDMCDir, |
| 32 | LoadDir, | ||
| 33 | DumpDir, | ||
| 32 | SysDataDir, | 34 | SysDataDir, |
| 33 | UserDir, | 35 | UserDir, |
| 34 | }; | 36 | }; |
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 26f727d96..23fd6e920 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -32,6 +32,8 @@ add_library(core STATIC | |||
| 32 | file_sys/control_metadata.h | 32 | file_sys/control_metadata.h |
| 33 | file_sys/directory.h | 33 | file_sys/directory.h |
| 34 | file_sys/errors.h | 34 | file_sys/errors.h |
| 35 | file_sys/fsmitm_romfsbuild.cpp | ||
| 36 | file_sys/fsmitm_romfsbuild.h | ||
| 35 | file_sys/mode.h | 37 | file_sys/mode.h |
| 36 | file_sys/nca_metadata.cpp | 38 | file_sys/nca_metadata.cpp |
| 37 | file_sys/nca_metadata.h | 39 | file_sys/nca_metadata.h |
| @@ -59,10 +61,13 @@ add_library(core STATIC | |||
| 59 | file_sys/vfs.h | 61 | file_sys/vfs.h |
| 60 | file_sys/vfs_concat.cpp | 62 | file_sys/vfs_concat.cpp |
| 61 | file_sys/vfs_concat.h | 63 | file_sys/vfs_concat.h |
| 64 | file_sys/vfs_layered.cpp | ||
| 65 | file_sys/vfs_layered.h | ||
| 62 | file_sys/vfs_offset.cpp | 66 | file_sys/vfs_offset.cpp |
| 63 | file_sys/vfs_offset.h | 67 | file_sys/vfs_offset.h |
| 64 | file_sys/vfs_real.cpp | 68 | file_sys/vfs_real.cpp |
| 65 | file_sys/vfs_real.h | 69 | file_sys/vfs_real.h |
| 70 | file_sys/vfs_static.h | ||
| 66 | file_sys/vfs_vector.cpp | 71 | file_sys/vfs_vector.cpp |
| 67 | file_sys/vfs_vector.h | 72 | file_sys/vfs_vector.h |
| 68 | file_sys/xts_archive.cpp | 73 | file_sys/xts_archive.cpp |
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp index 205492897..6102ef476 100644 --- a/src/core/file_sys/bis_factory.cpp +++ b/src/core/file_sys/bis_factory.cpp | |||
| @@ -2,13 +2,14 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <fmt/format.h> | ||
| 5 | #include "core/file_sys/bis_factory.h" | 6 | #include "core/file_sys/bis_factory.h" |
| 6 | #include "core/file_sys/registered_cache.h" | 7 | #include "core/file_sys/registered_cache.h" |
| 7 | 8 | ||
| 8 | namespace FileSys { | 9 | namespace FileSys { |
| 9 | 10 | ||
| 10 | BISFactory::BISFactory(VirtualDir nand_root_) | 11 | BISFactory::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 | ||
| 28 | VirtualDir 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. |
| 18 | class BISFactory { | 18 | class BISFactory { |
| 19 | public: | 19 | public: |
| 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 | |||
| 26 | private: | 28 | private: |
| 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 | |||
| 31 | namespace FileSys { | ||
| 32 | |||
| 33 | constexpr u64 FS_MAX_PATH = 0x301; | ||
| 34 | |||
| 35 | constexpr u32 ROMFS_ENTRY_EMPTY = 0xFFFFFFFF; | ||
| 36 | constexpr u32 ROMFS_FILEPARTITION_OFS = 0x200; | ||
| 37 | |||
| 38 | // Types for building a RomFS. | ||
| 39 | struct 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 | }; | ||
| 51 | static_assert(sizeof(RomFSHeader) == 0x50, "RomFSHeader has incorrect size."); | ||
| 52 | |||
| 53 | struct RomFSDirectoryEntry { | ||
| 54 | u32 parent; | ||
| 55 | u32 sibling; | ||
| 56 | u32 child; | ||
| 57 | u32 file; | ||
| 58 | u32 hash; | ||
| 59 | u32 name_size; | ||
| 60 | }; | ||
| 61 | static_assert(sizeof(RomFSDirectoryEntry) == 0x18, "RomFSDirectoryEntry has incorrect size."); | ||
| 62 | |||
| 63 | struct RomFSFileEntry { | ||
| 64 | u32 parent; | ||
| 65 | u32 sibling; | ||
| 66 | u64 offset; | ||
| 67 | u64 size; | ||
| 68 | u32 hash; | ||
| 69 | u32 name_size; | ||
| 70 | }; | ||
| 71 | static_assert(sizeof(RomFSFileEntry) == 0x20, "RomFSFileEntry has incorrect size."); | ||
| 72 | |||
| 73 | struct RomFSBuildFileContext; | ||
| 74 | |||
| 75 | struct 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 | |||
| 86 | struct 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 | |||
| 100 | static 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 | |||
| 110 | static 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 | |||
| 124 | void 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 | |||
| 174 | bool 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 | |||
| 191 | bool 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 | |||
| 209 | RomFSBuildContext::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 | |||
| 219 | RomFSBuildContext::~RomFSBuildContext() = default; | ||
| 220 | |||
| 221 | std::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 | |||
| 34 | namespace FileSys { | ||
| 35 | |||
| 36 | struct RomFSBuildDirectoryContext; | ||
| 37 | struct RomFSBuildFileContext; | ||
| 38 | struct RomFSDirectoryEntry; | ||
| 39 | struct RomFSFileEntry; | ||
| 40 | |||
| 41 | class RomFSBuildContext { | ||
| 42 | public: | ||
| 43 | explicit RomFSBuildContext(VirtualDir base); | ||
| 44 | ~RomFSBuildContext(); | ||
| 45 | |||
| 46 | // This finalizes the context. | ||
| 47 | std::map<u64, VirtualFile> Build(); | ||
| 48 | |||
| 49 | private: | ||
| 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 | ||
| 34 | constexpr std::array<const char*, 1> PATCH_TYPE_NAMES{ | 35 | constexpr std::array<const char*, 2> PATCH_TYPE_NAMES{ |
| 35 | "Update", | 36 | "Update", |
| 37 | "LayeredFS", | ||
| 36 | }; | 38 | }; |
| 37 | 39 | ||
| 38 | std::string FormatPatchTypeName(PatchType type) { | 40 | std::string FormatPatchTypeName(PatchType type) { |
| @@ -66,6 +68,42 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { | |||
| 66 | return exefs; | 68 | return exefs; |
| 67 | } | 69 | } |
| 68 | 70 | ||
| 71 | static 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 | |||
| 69 | VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, | 107 | VirtualFile 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 | ||
| 27 | enum class PatchType { | 27 | enum class PatchType { |
| 28 | Update, | 28 | Update, |
| 29 | LayeredFS, | ||
| 29 | }; | 30 | }; |
| 30 | 31 | ||
| 31 | std::string FormatPatchTypeName(PatchType type); | 32 | std::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 | ||
| 20 | namespace FileSys { | 20 | namespace FileSys { |
| 21 | |||
| 22 | // The size of blocks to use when vfs raw copying into nand. | ||
| 23 | constexpr size_t VFS_RC_LARGE_COPY_BLOCK = 0x400000; | ||
| 24 | |||
| 21 | std::string RegisteredCacheEntry::DebugInfo() const { | 25 | std::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 | ||
| 486 | bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) { | 491 | bool 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 | ||
| 28 | using NcaID = std::array<u8, 0x10>; | 28 | using NcaID = std::array<u8, 0x10>; |
| 29 | using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; | 29 | using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; |
| 30 | using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>; | 30 | using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>; |
| 31 | 31 | ||
| 32 | enum class InstallResult { | 32 | enum 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 | ||
| 101 | VirtualDir ExtractRomFS(VirtualFile file) { | 103 | VirtualDir 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 | |||
| 132 | VirtualFile 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 | ||
| 13 | namespace FileSys { | 14 | namespace FileSys { |
| 14 | 15 | ||
| 16 | struct RomFSHeader; | ||
| 17 | |||
| 15 | struct IVFCLevel { | 18 | struct 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 | }; |
| 30 | static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size."); | 33 | static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size."); |
| 31 | 34 | ||
| 35 | enum 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 |
| 34 | VirtualDir ExtractRomFS(VirtualFile file); | 42 | VirtualDir ExtractRomFS(VirtualFile file, |
| 43 | RomFSExtractionType type = RomFSExtractionType::Truncated); | ||
| 44 | |||
| 45 | // Converts a VFS filesystem into a RomFS binary | ||
| 46 | // Returns nullptr on failure | ||
| 47 | VirtualFile 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 | ||
| 402 | std::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 | |||
| 402 | std::string VfsDirectory::GetFullPath() const { | 411 | std::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 | ||
| 457 | bool VfsRawCopy(VirtualFile src, VirtualFile dest) { | 466 | bool 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 | |||
| 484 | bool 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 | ||
| 466 | VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) { | 503 | VirtualDir 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 |
| 314 | bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t block_size = 0x200); | 319 | bool 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. |
| 319 | bool VfsRawCopy(VirtualFile src, VirtualFile dest); | 324 | bool 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. | ||
| 329 | bool 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 | ||
| 10 | namespace FileSys { | 11 | namespace FileSys { |
| 11 | 12 | ||
| 13 | static 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 | |||
| 12 | VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) { | 25 | VirtualFile 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 | ||
| 43 | ConcatenatedVfsFile::ConcatenatedVfsFile(std::map<u64, VirtualFile> files_, std::string name) | ||
| 44 | : files(std::move(files_)), name(std::move(name)) { | ||
| 45 | ASSERT(VerifyConcatenationMapContinuity(files)); | ||
| 46 | } | ||
| 47 | |||
| 30 | ConcatenatedVfsFile::~ConcatenatedVfsFile() = default; | 48 | ConcatenatedVfsFile::~ConcatenatedVfsFile() = default; |
| 31 | 49 | ||
| 32 | std::string ConcatenatedVfsFile::GetName() const { | 50 | std::string ConcatenatedVfsFile::GetName() const { |
| @@ -62,7 +80,7 @@ bool ConcatenatedVfsFile::IsReadable() const { | |||
| 62 | } | 80 | } |
| 63 | 81 | ||
| 64 | std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { | 82 | std::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 | ||
| 89 | std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { | 104 | std::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:: | |||
| 93 | bool ConcatenatedVfsFile::Rename(std::string_view name) { | 108 | bool 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 | ||
| 12 | namespace FileSys { | 14 | namespace FileSys { |
| 13 | 15 | ||
| 14 | // Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases. | ||
| 15 | VirtualFile 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. |
| 19 | class ConcatenatedVfsFile : public VfsFile { | 18 | class 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 | ||
| 24 | public: | 27 | public: |
| 25 | ~ConcatenatedVfsFile() override; | 28 | ~ConcatenatedVfsFile() override; |
| @@ -36,8 +39,37 @@ public: | |||
| 36 | 39 | ||
| 37 | private: | 40 | private: |
| 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. | ||
| 47 | VirtualFile 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. | ||
| 51 | template <u8 filler_byte> | ||
| 52 | VirtualFile 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 | |||
| 9 | namespace FileSys { | ||
| 10 | |||
| 11 | VirtualDir 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 | |||
| 20 | LayeredVfsDirectory::LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name) | ||
| 21 | : dirs(std::move(dirs)), name(std::move(name)) {} | ||
| 22 | |||
| 23 | LayeredVfsDirectory::~LayeredVfsDirectory() = default; | ||
| 24 | |||
| 25 | std::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 | |||
| 35 | std::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 | |||
| 47 | std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFile(std::string_view name) const { | ||
| 48 | return GetFileRelative(name); | ||
| 49 | } | ||
| 50 | |||
| 51 | std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetSubdirectory(std::string_view name) const { | ||
| 52 | return GetDirectoryRelative(name); | ||
| 53 | } | ||
| 54 | |||
| 55 | std::string LayeredVfsDirectory::GetFullPath() const { | ||
| 56 | return dirs[0]->GetFullPath(); | ||
| 57 | } | ||
| 58 | |||
| 59 | std::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 | |||
| 74 | std::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 | |||
| 91 | bool LayeredVfsDirectory::IsWritable() const { | ||
| 92 | return false; | ||
| 93 | } | ||
| 94 | |||
| 95 | bool LayeredVfsDirectory::IsReadable() const { | ||
| 96 | return true; | ||
| 97 | } | ||
| 98 | |||
| 99 | std::string LayeredVfsDirectory::GetName() const { | ||
| 100 | return name.empty() ? dirs[0]->GetName() : name; | ||
| 101 | } | ||
| 102 | |||
| 103 | std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetParentDirectory() const { | ||
| 104 | return dirs[0]->GetParentDirectory(); | ||
| 105 | } | ||
| 106 | |||
| 107 | std::shared_ptr<VfsDirectory> LayeredVfsDirectory::CreateSubdirectory(std::string_view name) { | ||
| 108 | return nullptr; | ||
| 109 | } | ||
| 110 | |||
| 111 | std::shared_ptr<VfsFile> LayeredVfsDirectory::CreateFile(std::string_view name) { | ||
| 112 | return nullptr; | ||
| 113 | } | ||
| 114 | |||
| 115 | bool LayeredVfsDirectory::DeleteSubdirectory(std::string_view name) { | ||
| 116 | return false; | ||
| 117 | } | ||
| 118 | |||
| 119 | bool LayeredVfsDirectory::DeleteFile(std::string_view name) { | ||
| 120 | return false; | ||
| 121 | } | ||
| 122 | |||
| 123 | bool LayeredVfsDirectory::Rename(std::string_view name_) { | ||
| 124 | name = name_; | ||
| 125 | return true; | ||
| 126 | } | ||
| 127 | |||
| 128 | bool 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 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | // Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases. | ||
| 13 | VirtualDir 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. | ||
| 18 | class 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 | |||
| 23 | public: | ||
| 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 | |||
| 44 | protected: | ||
| 45 | bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; | ||
| 46 | |||
| 47 | private: | ||
| 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 | ||
| 416 | std::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 | |||
| 416 | bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { | 433 | bool 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 | ||
| 102 | protected: | 103 | protected: |
| 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 | |||
| 13 | namespace FileSys { | ||
| 14 | |||
| 15 | template <u8 value> | ||
| 16 | class StaticVfsFile : public VfsFile { | ||
| 17 | public: | ||
| 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 | |||
| 72 | private: | ||
| 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 | ||
| 9 | namespace FileSys { | 10 | namespace FileSys { |
| 11 | VectorVfsFile::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 | |||
| 14 | VectorVfsFile::~VectorVfsFile() = default; | ||
| 15 | |||
| 16 | std::string VectorVfsFile::GetName() const { | ||
| 17 | return name; | ||
| 18 | } | ||
| 19 | |||
| 20 | size_t VectorVfsFile::GetSize() const { | ||
| 21 | return data.size(); | ||
| 22 | } | ||
| 23 | |||
| 24 | bool VectorVfsFile::Resize(size_t new_size) { | ||
| 25 | data.resize(new_size); | ||
| 26 | return true; | ||
| 27 | } | ||
| 28 | |||
| 29 | std::shared_ptr<VfsDirectory> VectorVfsFile::GetContainingDirectory() const { | ||
| 30 | return parent; | ||
| 31 | } | ||
| 32 | |||
| 33 | bool VectorVfsFile::IsWritable() const { | ||
| 34 | return true; | ||
| 35 | } | ||
| 36 | |||
| 37 | bool VectorVfsFile::IsReadable() const { | ||
| 38 | return true; | ||
| 39 | } | ||
| 40 | |||
| 41 | size_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 | |||
| 47 | size_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 | |||
| 55 | bool VectorVfsFile::Rename(std::string_view name_) { | ||
| 56 | name = name_; | ||
| 57 | return true; | ||
| 58 | } | ||
| 59 | |||
| 60 | void VectorVfsFile::Assign(std::vector<u8> new_data) { | ||
| 61 | data = std::move(new_data); | ||
| 62 | } | ||
| 63 | |||
| 10 | VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_, | 64 | VectorVfsDirectory::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 | ||
| 9 | namespace FileSys { | 9 | namespace FileSys { |
| 10 | 10 | ||
| 11 | // An implementation of VfsFile that is backed by a vector optionally supplied upon construction | ||
| 12 | class VectorVfsFile : public VfsFile { | ||
| 13 | public: | ||
| 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 | |||
| 30 | private: | ||
| 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. |
| 13 | class VectorVfsDirectory : public VfsDirectory { | 38 | class VectorVfsDirectory : public VfsDirectory { |
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index d349ee686..aed2abb71 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp | |||
| @@ -343,6 +343,15 @@ std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents() { | |||
| 343 | return sdmc_factory->GetSDMCContents(); | 343 | return sdmc_factory->GetSDMCContents(); |
| 344 | } | 344 | } |
| 345 | 345 | ||
| 346 | FileSys::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 | |||
| 346 | void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) { | 355 | void 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(); | |||
| 52 | std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents(); | 52 | std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents(); |
| 53 | std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents(); | 53 | std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents(); |
| 54 | 54 | ||
| 55 | FileSys::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. |
| 57 | void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite = true); | 59 | void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite = true); |
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index e8b2f720a..991ae10cd 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -318,9 +318,14 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { | |||
| 318 | int row = item_model->itemFromIndex(item)->row(); | 318 | int row = item_model->itemFromIndex(item)->row(); |
| 319 | QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); | 319 | QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); |
| 320 | u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong(); | 320 | u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong(); |
| 321 | std::string path = child_file->data(GameListItemPath::FullPathRole).toString().toStdString(); | ||
| 321 | 322 | ||
| 322 | QMenu context_menu; | 323 | QMenu context_menu; |
| 323 | QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); | 324 | QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); |
| 325 | QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location")); | ||
| 326 | context_menu.addSeparator(); | ||
| 327 | QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS")); | ||
| 328 | QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); | ||
| 324 | QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); | 329 | QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); |
| 325 | 330 | ||
| 326 | open_save_location->setEnabled(program_id != 0); | 331 | open_save_location->setEnabled(program_id != 0); |
| @@ -329,6 +334,10 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { | |||
| 329 | 334 | ||
| 330 | connect(open_save_location, &QAction::triggered, | 335 | connect(open_save_location, &QAction::triggered, |
| 331 | [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); }); | 336 | [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); }); |
| 337 | connect(open_lfs_location, &QAction::triggered, | ||
| 338 | [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); }); | ||
| 339 | connect(dump_romfs, &QAction::triggered, [&]() { emit DumpRomFSRequested(program_id, path); }); | ||
| 340 | connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); }); | ||
| 332 | connect(navigate_to_gamedb_entry, &QAction::triggered, | 341 | connect(navigate_to_gamedb_entry, &QAction::triggered, |
| 333 | [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); | 342 | [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); |
| 334 | 343 | ||
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 2713e7b54..3bf51870e 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h | |||
| @@ -28,7 +28,10 @@ namespace FileSys { | |||
| 28 | class VfsFilesystem; | 28 | class VfsFilesystem; |
| 29 | } | 29 | } |
| 30 | 30 | ||
| 31 | enum class GameListOpenTarget { SaveData }; | 31 | enum class GameListOpenTarget { |
| 32 | SaveData, | ||
| 33 | ModData, | ||
| 34 | }; | ||
| 32 | 35 | ||
| 33 | class GameList : public QWidget { | 36 | class 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. | ||
| 16 | static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper( | ||
| 17 | const FileSys::VirtualFilesystem& vfs, const std::string& path, FileSys::Mode mode) { | ||
| 18 | return vfs->CreateDirectory(path, mode); | ||
| 19 | } | ||
| 20 | |||
| 21 | static 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() { | |||
| 362 | void GMainWindow::ConnectWidgetEvents() { | 380 | void 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 | ||
| 759 | void 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 | |||
| 868 | void GMainWindow::OnGameListCopyTID(u64 program_id) { | ||
| 869 | QClipboard* clipboard = QGuiApplication::clipboard(); | ||
| 870 | clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id))); | ||
| 871 | } | ||
| 872 | |||
| 733 | void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, | 873 | void 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(); |