mirror of
				https://git.zaroz.cloud/nintendo-back-up/yuzu/yuzu-mainline.git
				synced 2025-03-21 01:53:15 +00:00 
			
		
		
		
	Merge pull request #1365 from DarkLordZach/lfs
file_sys: Add support for LayeredFS mods
This commit is contained in:
		
						commit
						7b81e1e525
					
				| @ -33,6 +33,8 @@ | ||||
| #define NAND_DIR "nand" | ||||
| #define SYSDATA_DIR "sysdata" | ||||
| #define KEYS_DIR "keys" | ||||
| #define LOAD_DIR "load" | ||||
| #define DUMP_DIR "dump" | ||||
| #define LOG_DIR "log" | ||||
| 
 | ||||
| // Filenames
 | ||||
|  | ||||
| @ -705,6 +705,8 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) { | ||||
| #endif | ||||
|         paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP); | ||||
|         paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP); | ||||
|         paths.emplace(UserPath::LoadDir, user_path + LOAD_DIR DIR_SEP); | ||||
|         paths.emplace(UserPath::DumpDir, user_path + DUMP_DIR DIR_SEP); | ||||
|         paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP); | ||||
|         paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP); | ||||
|         // TODO: Put the logs in a better location for each OS
 | ||||
|  | ||||
| @ -29,6 +29,8 @@ enum class UserPath { | ||||
|     NANDDir, | ||||
|     RootDir, | ||||
|     SDMCDir, | ||||
|     LoadDir, | ||||
|     DumpDir, | ||||
|     SysDataDir, | ||||
|     UserDir, | ||||
| }; | ||||
|  | ||||
| @ -32,6 +32,8 @@ add_library(core STATIC | ||||
|     file_sys/control_metadata.h | ||||
|     file_sys/directory.h | ||||
|     file_sys/errors.h | ||||
|     file_sys/fsmitm_romfsbuild.cpp | ||||
|     file_sys/fsmitm_romfsbuild.h | ||||
|     file_sys/mode.h | ||||
|     file_sys/nca_metadata.cpp | ||||
|     file_sys/nca_metadata.h | ||||
| @ -59,10 +61,13 @@ add_library(core STATIC | ||||
|     file_sys/vfs.h | ||||
|     file_sys/vfs_concat.cpp | ||||
|     file_sys/vfs_concat.h | ||||
|     file_sys/vfs_layered.cpp | ||||
|     file_sys/vfs_layered.h | ||||
|     file_sys/vfs_offset.cpp | ||||
|     file_sys/vfs_offset.h | ||||
|     file_sys/vfs_real.cpp | ||||
|     file_sys/vfs_real.h | ||||
|     file_sys/vfs_static.h | ||||
|     file_sys/vfs_vector.cpp | ||||
|     file_sys/vfs_vector.h | ||||
|     file_sys/xts_archive.cpp | ||||
|  | ||||
| @ -2,13 +2,14 @@ | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <fmt/format.h> | ||||
| #include "core/file_sys/bis_factory.h" | ||||
| #include "core/file_sys/registered_cache.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| BISFactory::BISFactory(VirtualDir nand_root_) | ||||
|     : nand_root(std::move(nand_root_)), | ||||
| BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_) | ||||
|     : nand_root(std::move(nand_root_)), load_root(std::move(load_root_)), | ||||
|       sysnand_cache(std::make_shared<RegisteredCache>( | ||||
|           GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))), | ||||
|       usrnand_cache(std::make_shared<RegisteredCache>( | ||||
| @ -24,4 +25,11 @@ std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const { | ||||
|     return usrnand_cache; | ||||
| } | ||||
| 
 | ||||
| VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const { | ||||
|     // LayeredFS doesn't work on updates and title id-less homebrew
 | ||||
|     if (title_id == 0 || (title_id & 0x800) > 0) | ||||
|         return nullptr; | ||||
|     return GetOrCreateDirectoryRelative(load_root, fmt::format("/{:016X}", title_id)); | ||||
| } | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
|  | ||||
| @ -17,14 +17,17 @@ class RegisteredCache; | ||||
| /// registered caches.
 | ||||
| class BISFactory { | ||||
| public: | ||||
|     explicit BISFactory(VirtualDir nand_root); | ||||
|     explicit BISFactory(VirtualDir nand_root, VirtualDir load_root); | ||||
|     ~BISFactory(); | ||||
| 
 | ||||
|     std::shared_ptr<RegisteredCache> GetSystemNANDContents() const; | ||||
|     std::shared_ptr<RegisteredCache> GetUserNANDContents() const; | ||||
| 
 | ||||
|     VirtualDir GetModificationLoadRoot(u64 title_id) const; | ||||
| 
 | ||||
| private: | ||||
|     VirtualDir nand_root; | ||||
|     VirtualDir load_root; | ||||
| 
 | ||||
|     std::shared_ptr<RegisteredCache> sysnand_cache; | ||||
|     std::shared_ptr<RegisteredCache> usrnand_cache; | ||||
|  | ||||
							
								
								
									
										372
									
								
								src/core/file_sys/fsmitm_romfsbuild.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										372
									
								
								src/core/file_sys/fsmitm_romfsbuild.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,372 @@ | ||||
| /*
 | ||||
|  * Copyright (c) 2018 Atmosphère-NX | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify it | ||||
|  * under the terms and conditions of the GNU General Public License, | ||||
|  * version 2, as published by the Free Software Foundation. | ||||
|  * | ||||
|  * This program is distributed in the hope it will be useful, but WITHOUT | ||||
|  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for | ||||
|  * more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
|  */ | ||||
| 
 | ||||
| /*
 | ||||
|  * Adapted by DarkLordZach for use/interaction with yuzu | ||||
|  * | ||||
|  * Modifications Copyright 2018 yuzu emulator team | ||||
|  * Licensed under GPLv2 or any later version | ||||
|  * Refer to the license.txt file included. | ||||
|  */ | ||||
| 
 | ||||
| #include <cstring> | ||||
| #include "common/assert.h" | ||||
| #include "core/file_sys/fsmitm_romfsbuild.h" | ||||
| #include "core/file_sys/vfs.h" | ||||
| #include "core/file_sys/vfs_vector.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| constexpr u64 FS_MAX_PATH = 0x301; | ||||
| 
 | ||||
| constexpr u32 ROMFS_ENTRY_EMPTY = 0xFFFFFFFF; | ||||
| constexpr u32 ROMFS_FILEPARTITION_OFS = 0x200; | ||||
| 
 | ||||
| // Types for building a RomFS.
 | ||||
| struct RomFSHeader { | ||||
|     u64 header_size; | ||||
|     u64 dir_hash_table_ofs; | ||||
|     u64 dir_hash_table_size; | ||||
|     u64 dir_table_ofs; | ||||
|     u64 dir_table_size; | ||||
|     u64 file_hash_table_ofs; | ||||
|     u64 file_hash_table_size; | ||||
|     u64 file_table_ofs; | ||||
|     u64 file_table_size; | ||||
|     u64 file_partition_ofs; | ||||
| }; | ||||
| static_assert(sizeof(RomFSHeader) == 0x50, "RomFSHeader has incorrect size."); | ||||
| 
 | ||||
| struct RomFSDirectoryEntry { | ||||
|     u32 parent; | ||||
|     u32 sibling; | ||||
|     u32 child; | ||||
|     u32 file; | ||||
|     u32 hash; | ||||
|     u32 name_size; | ||||
| }; | ||||
| static_assert(sizeof(RomFSDirectoryEntry) == 0x18, "RomFSDirectoryEntry has incorrect size."); | ||||
| 
 | ||||
| struct RomFSFileEntry { | ||||
|     u32 parent; | ||||
|     u32 sibling; | ||||
|     u64 offset; | ||||
|     u64 size; | ||||
|     u32 hash; | ||||
|     u32 name_size; | ||||
| }; | ||||
| static_assert(sizeof(RomFSFileEntry) == 0x20, "RomFSFileEntry has incorrect size."); | ||||
| 
 | ||||
| struct RomFSBuildFileContext; | ||||
| 
 | ||||
| struct RomFSBuildDirectoryContext { | ||||
|     std::string path = ""; | ||||
|     u32 cur_path_ofs = 0; | ||||
|     u32 path_len = 0; | ||||
|     u32 entry_offset = 0; | ||||
|     std::shared_ptr<RomFSBuildDirectoryContext> parent; | ||||
|     std::shared_ptr<RomFSBuildDirectoryContext> child; | ||||
|     std::shared_ptr<RomFSBuildDirectoryContext> sibling; | ||||
|     std::shared_ptr<RomFSBuildFileContext> file; | ||||
| }; | ||||
| 
 | ||||
| struct RomFSBuildFileContext { | ||||
|     std::string path = ""; | ||||
|     u32 cur_path_ofs = 0; | ||||
|     u32 path_len = 0; | ||||
|     u32 entry_offset = 0; | ||||
|     u64 offset = 0; | ||||
|     u64 size = 0; | ||||
|     std::shared_ptr<RomFSBuildDirectoryContext> parent; | ||||
|     std::shared_ptr<RomFSBuildFileContext> sibling; | ||||
|     VirtualFile source = nullptr; | ||||
| 
 | ||||
|     RomFSBuildFileContext() : path(""), cur_path_ofs(0), path_len(0) {} | ||||
| }; | ||||
| 
 | ||||
| static u32 romfs_calc_path_hash(u32 parent, std::string path, u32 start, size_t path_len) { | ||||
|     u32 hash = parent ^ 123456789; | ||||
|     for (u32 i = 0; i < path_len; i++) { | ||||
|         hash = (hash >> 5) | (hash << 27); | ||||
|         hash ^= path[start + i]; | ||||
|     } | ||||
| 
 | ||||
|     return hash; | ||||
| } | ||||
| 
 | ||||
| static u32 romfs_get_hash_table_count(u32 num_entries) { | ||||
|     if (num_entries < 3) { | ||||
|         return 3; | ||||
|     } else if (num_entries < 19) { | ||||
|         return num_entries | 1; | ||||
|     } | ||||
|     u32 count = num_entries; | ||||
|     while (count % 2 == 0 || count % 3 == 0 || count % 5 == 0 || count % 7 == 0 || | ||||
|            count % 11 == 0 || count % 13 == 0 || count % 17 == 0) { | ||||
|         count++; | ||||
|     } | ||||
|     return count; | ||||
| } | ||||
| 
 | ||||
| void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, | ||||
|                                        std::shared_ptr<RomFSBuildDirectoryContext> parent) { | ||||
|     std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> child_dirs; | ||||
| 
 | ||||
|     VirtualDir dir; | ||||
| 
 | ||||
|     if (parent->path_len == 0) | ||||
|         dir = root_romfs; | ||||
|     else | ||||
|         dir = root_romfs->GetDirectoryRelative(parent->path); | ||||
| 
 | ||||
|     const auto entries = dir->GetEntries(); | ||||
| 
 | ||||
|     for (const auto& kv : entries) { | ||||
|         if (kv.second == VfsEntryType::Directory) { | ||||
|             const auto child = std::make_shared<RomFSBuildDirectoryContext>(); | ||||
|             // Set child's path.
 | ||||
|             child->cur_path_ofs = parent->path_len + 1; | ||||
|             child->path_len = child->cur_path_ofs + kv.first.size(); | ||||
|             child->path = parent->path + "/" + kv.first; | ||||
| 
 | ||||
|             // Sanity check on path_len
 | ||||
|             ASSERT(child->path_len < FS_MAX_PATH); | ||||
| 
 | ||||
|             if (AddDirectory(parent, child)) { | ||||
|                 child_dirs.push_back(child); | ||||
|             } | ||||
|         } else { | ||||
|             const auto child = std::make_shared<RomFSBuildFileContext>(); | ||||
|             // Set child's path.
 | ||||
|             child->cur_path_ofs = parent->path_len + 1; | ||||
|             child->path_len = child->cur_path_ofs + kv.first.size(); | ||||
|             child->path = parent->path + "/" + kv.first; | ||||
| 
 | ||||
|             // Sanity check on path_len
 | ||||
|             ASSERT(child->path_len < FS_MAX_PATH); | ||||
| 
 | ||||
|             child->source = root_romfs->GetFileRelative(child->path); | ||||
| 
 | ||||
|             child->size = child->source->GetSize(); | ||||
| 
 | ||||
|             AddFile(parent, child); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     for (auto& child : child_dirs) { | ||||
|         this->VisitDirectory(root_romfs, child); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool RomFSBuildContext::AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx, | ||||
|                                      std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx) { | ||||
|     // Check whether it's already in the known directories.
 | ||||
|     const auto existing = directories.find(dir_ctx->path); | ||||
|     if (existing != directories.end()) | ||||
|         return false; | ||||
| 
 | ||||
|     // Add a new directory.
 | ||||
|     num_dirs++; | ||||
|     dir_table_size += | ||||
|         sizeof(RomFSDirectoryEntry) + ((dir_ctx->path_len - dir_ctx->cur_path_ofs + 3) & ~3); | ||||
|     dir_ctx->parent = parent_dir_ctx; | ||||
|     directories.emplace(dir_ctx->path, dir_ctx); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx, | ||||
|                                 std::shared_ptr<RomFSBuildFileContext> file_ctx) { | ||||
|     // Check whether it's already in the known files.
 | ||||
|     const auto existing = files.find(file_ctx->path); | ||||
|     if (existing != files.end()) { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     // Add a new file.
 | ||||
|     num_files++; | ||||
|     file_table_size += | ||||
|         sizeof(RomFSFileEntry) + ((file_ctx->path_len - file_ctx->cur_path_ofs + 3) & ~3); | ||||
|     file_ctx->parent = parent_dir_ctx; | ||||
|     files.emplace(file_ctx->path, file_ctx); | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| RomFSBuildContext::RomFSBuildContext(VirtualDir base_) : base(std::move(base_)) { | ||||
|     root = std::make_shared<RomFSBuildDirectoryContext>(); | ||||
|     root->path = "\0"; | ||||
|     directories.emplace(root->path, root); | ||||
|     num_dirs = 1; | ||||
|     dir_table_size = 0x18; | ||||
| 
 | ||||
|     VisitDirectory(base, root); | ||||
| } | ||||
| 
 | ||||
| RomFSBuildContext::~RomFSBuildContext() = default; | ||||
| 
 | ||||
| std::map<u64, VirtualFile> RomFSBuildContext::Build() { | ||||
|     const auto dir_hash_table_entry_count = romfs_get_hash_table_count(num_dirs); | ||||
|     const auto file_hash_table_entry_count = romfs_get_hash_table_count(num_files); | ||||
|     dir_hash_table_size = 4 * dir_hash_table_entry_count; | ||||
|     file_hash_table_size = 4 * file_hash_table_entry_count; | ||||
| 
 | ||||
|     // Assign metadata pointers
 | ||||
|     RomFSHeader header{}; | ||||
| 
 | ||||
|     std::vector<u32> dir_hash_table(dir_hash_table_entry_count, ROMFS_ENTRY_EMPTY); | ||||
|     std::vector<u32> file_hash_table(file_hash_table_entry_count, ROMFS_ENTRY_EMPTY); | ||||
| 
 | ||||
|     std::vector<u8> dir_table(dir_table_size); | ||||
|     std::vector<u8> file_table(file_table_size); | ||||
| 
 | ||||
|     // Clear out hash tables.
 | ||||
|     for (u32 i = 0; i < dir_hash_table_entry_count; i++) | ||||
|         dir_hash_table[i] = ROMFS_ENTRY_EMPTY; | ||||
|     for (u32 i = 0; i < file_hash_table_entry_count; i++) | ||||
|         file_hash_table[i] = ROMFS_ENTRY_EMPTY; | ||||
| 
 | ||||
|     std::shared_ptr<RomFSBuildFileContext> cur_file; | ||||
| 
 | ||||
|     // Determine file offsets.
 | ||||
|     u32 entry_offset = 0; | ||||
|     std::shared_ptr<RomFSBuildFileContext> prev_file = nullptr; | ||||
|     for (const auto& it : files) { | ||||
|         cur_file = it.second; | ||||
|         file_partition_size = (file_partition_size + 0xFULL) & ~0xFULL; | ||||
|         cur_file->offset = file_partition_size; | ||||
|         file_partition_size += cur_file->size; | ||||
|         cur_file->entry_offset = entry_offset; | ||||
|         entry_offset += | ||||
|             sizeof(RomFSFileEntry) + ((cur_file->path_len - cur_file->cur_path_ofs + 3) & ~3); | ||||
|         prev_file = cur_file; | ||||
|     } | ||||
|     // Assign deferred parent/sibling ownership.
 | ||||
|     for (auto it = files.rbegin(); it != files.rend(); ++it) { | ||||
|         cur_file = it->second; | ||||
|         cur_file->sibling = cur_file->parent->file; | ||||
|         cur_file->parent->file = cur_file; | ||||
|     } | ||||
| 
 | ||||
|     std::shared_ptr<RomFSBuildDirectoryContext> cur_dir; | ||||
| 
 | ||||
|     // Determine directory offsets.
 | ||||
|     entry_offset = 0; | ||||
|     for (const auto& it : directories) { | ||||
|         cur_dir = it.second; | ||||
|         cur_dir->entry_offset = entry_offset; | ||||
|         entry_offset += | ||||
|             sizeof(RomFSDirectoryEntry) + ((cur_dir->path_len - cur_dir->cur_path_ofs + 3) & ~3); | ||||
|     } | ||||
|     // Assign deferred parent/sibling ownership.
 | ||||
|     for (auto it = directories.rbegin(); it->second != root; ++it) { | ||||
|         cur_dir = it->second; | ||||
|         cur_dir->sibling = cur_dir->parent->child; | ||||
|         cur_dir->parent->child = cur_dir; | ||||
|     } | ||||
| 
 | ||||
|     std::map<u64, VirtualFile> out; | ||||
| 
 | ||||
|     // Populate file tables.
 | ||||
|     for (const auto& it : files) { | ||||
|         cur_file = it.second; | ||||
|         RomFSFileEntry cur_entry{}; | ||||
| 
 | ||||
|         cur_entry.parent = cur_file->parent->entry_offset; | ||||
|         cur_entry.sibling = | ||||
|             cur_file->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_file->sibling->entry_offset; | ||||
|         cur_entry.offset = cur_file->offset; | ||||
|         cur_entry.size = cur_file->size; | ||||
| 
 | ||||
|         const auto name_size = cur_file->path_len - cur_file->cur_path_ofs; | ||||
|         const auto hash = romfs_calc_path_hash(cur_file->parent->entry_offset, cur_file->path, | ||||
|                                                cur_file->cur_path_ofs, name_size); | ||||
|         cur_entry.hash = file_hash_table[hash % file_hash_table_entry_count]; | ||||
|         file_hash_table[hash % file_hash_table_entry_count] = cur_file->entry_offset; | ||||
| 
 | ||||
|         cur_entry.name_size = name_size; | ||||
| 
 | ||||
|         out.emplace(cur_file->offset + ROMFS_FILEPARTITION_OFS, cur_file->source); | ||||
|         std::memcpy(file_table.data() + cur_file->entry_offset, &cur_entry, sizeof(RomFSFileEntry)); | ||||
|         std::memset(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry), 0, | ||||
|                     (cur_entry.name_size + 3) & ~3); | ||||
|         std::memcpy(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry), | ||||
|                     cur_file->path.data() + cur_file->cur_path_ofs, name_size); | ||||
|     } | ||||
| 
 | ||||
|     // Populate dir tables.
 | ||||
|     for (const auto& it : directories) { | ||||
|         cur_dir = it.second; | ||||
|         RomFSDirectoryEntry cur_entry{}; | ||||
| 
 | ||||
|         cur_entry.parent = cur_dir == root ? 0 : cur_dir->parent->entry_offset; | ||||
|         cur_entry.sibling = | ||||
|             cur_dir->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->sibling->entry_offset; | ||||
|         cur_entry.child = | ||||
|             cur_dir->child == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->child->entry_offset; | ||||
|         cur_entry.file = cur_dir->file == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->file->entry_offset; | ||||
| 
 | ||||
|         const auto name_size = cur_dir->path_len - cur_dir->cur_path_ofs; | ||||
|         const auto hash = romfs_calc_path_hash(cur_dir == root ? 0 : cur_dir->parent->entry_offset, | ||||
|                                                cur_dir->path, cur_dir->cur_path_ofs, name_size); | ||||
|         cur_entry.hash = dir_hash_table[hash % dir_hash_table_entry_count]; | ||||
|         dir_hash_table[hash % dir_hash_table_entry_count] = cur_dir->entry_offset; | ||||
| 
 | ||||
|         cur_entry.name_size = name_size; | ||||
| 
 | ||||
|         std::memcpy(dir_table.data() + cur_dir->entry_offset, &cur_entry, | ||||
|                     sizeof(RomFSDirectoryEntry)); | ||||
|         std::memcpy(dir_table.data() + cur_dir->entry_offset, &cur_entry, | ||||
|                     sizeof(RomFSDirectoryEntry)); | ||||
|         std::memset(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry), 0, | ||||
|                     (cur_entry.name_size + 3) & ~3); | ||||
|         std::memcpy(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry), | ||||
|                     cur_dir->path.data() + cur_dir->cur_path_ofs, name_size); | ||||
|     } | ||||
| 
 | ||||
|     // Set header fields.
 | ||||
|     header.header_size = sizeof(RomFSHeader); | ||||
|     header.file_hash_table_size = file_hash_table_size; | ||||
|     header.file_table_size = file_table_size; | ||||
|     header.dir_hash_table_size = dir_hash_table_size; | ||||
|     header.dir_table_size = dir_table_size; | ||||
|     header.file_partition_ofs = ROMFS_FILEPARTITION_OFS; | ||||
|     header.dir_hash_table_ofs = (header.file_partition_ofs + file_partition_size + 3ULL) & ~3ULL; | ||||
|     header.dir_table_ofs = header.dir_hash_table_ofs + header.dir_hash_table_size; | ||||
|     header.file_hash_table_ofs = header.dir_table_ofs + header.dir_table_size; | ||||
|     header.file_table_ofs = header.file_hash_table_ofs + header.file_hash_table_size; | ||||
| 
 | ||||
|     std::vector<u8> header_data(sizeof(RomFSHeader)); | ||||
|     std::memcpy(header_data.data(), &header, header_data.size()); | ||||
|     out.emplace(0, std::make_shared<VectorVfsFile>(header_data)); | ||||
| 
 | ||||
|     std::vector<u8> metadata(file_hash_table_size + file_table_size + dir_hash_table_size + | ||||
|                              dir_table_size); | ||||
|     auto index = 0; | ||||
|     std::memcpy(metadata.data(), dir_hash_table.data(), dir_hash_table.size() * sizeof(u32)); | ||||
|     index += dir_hash_table.size() * sizeof(u32); | ||||
|     std::memcpy(metadata.data() + index, dir_table.data(), dir_table.size()); | ||||
|     index += dir_table.size(); | ||||
|     std::memcpy(metadata.data() + index, file_hash_table.data(), | ||||
|                 file_hash_table.size() * sizeof(u32)); | ||||
|     index += file_hash_table.size() * sizeof(u32); | ||||
|     std::memcpy(metadata.data() + index, file_table.data(), file_table.size()); | ||||
|     out.emplace(header.dir_hash_table_ofs, std::make_shared<VectorVfsFile>(metadata)); | ||||
| 
 | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
							
								
								
									
										70
									
								
								src/core/file_sys/fsmitm_romfsbuild.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/core/file_sys/fsmitm_romfsbuild.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,70 @@ | ||||
| /*
 | ||||
|  * Copyright (c) 2018 Atmosphère-NX | ||||
|  * | ||||
|  * This program is free software; you can redistribute it and/or modify it | ||||
|  * under the terms and conditions of the GNU General Public License, | ||||
|  * version 2, as published by the Free Software Foundation. | ||||
|  * | ||||
|  * This program is distributed in the hope it will be useful, but WITHOUT | ||||
|  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for | ||||
|  * more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
|  */ | ||||
| 
 | ||||
| /*
 | ||||
|  * Adapted by DarkLordZach for use/interaction with yuzu | ||||
|  * | ||||
|  * Modifications Copyright 2018 yuzu emulator team | ||||
|  * Licensed under GPLv2 or any later version | ||||
|  * Refer to the license.txt file included. | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <map> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <boost/detail/container_fwd.hpp> | ||||
| #include "common/common_types.h" | ||||
| #include "core/file_sys/vfs.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| struct RomFSBuildDirectoryContext; | ||||
| struct RomFSBuildFileContext; | ||||
| struct RomFSDirectoryEntry; | ||||
| struct RomFSFileEntry; | ||||
| 
 | ||||
| class RomFSBuildContext { | ||||
| public: | ||||
|     explicit RomFSBuildContext(VirtualDir base); | ||||
|     ~RomFSBuildContext(); | ||||
| 
 | ||||
|     // This finalizes the context.
 | ||||
|     std::map<u64, VirtualFile> Build(); | ||||
| 
 | ||||
| private: | ||||
|     VirtualDir base; | ||||
|     std::shared_ptr<RomFSBuildDirectoryContext> root; | ||||
|     std::map<std::string, std::shared_ptr<RomFSBuildDirectoryContext>, std::less<>> directories; | ||||
|     std::map<std::string, std::shared_ptr<RomFSBuildFileContext>, std::less<>> files; | ||||
|     u64 num_dirs = 0; | ||||
|     u64 num_files = 0; | ||||
|     u64 dir_table_size = 0; | ||||
|     u64 file_table_size = 0; | ||||
|     u64 dir_hash_table_size = 0; | ||||
|     u64 file_hash_table_size = 0; | ||||
|     u64 file_partition_size = 0; | ||||
| 
 | ||||
|     void VisitDirectory(VirtualDir filesys, std::shared_ptr<RomFSBuildDirectoryContext> parent); | ||||
| 
 | ||||
|     bool AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx, | ||||
|                       std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx); | ||||
|     bool AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx, | ||||
|                  std::shared_ptr<RomFSBuildFileContext> file_ctx); | ||||
| }; | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
| @ -11,6 +11,7 @@ | ||||
| #include "core/file_sys/patch_manager.h" | ||||
| #include "core/file_sys/registered_cache.h" | ||||
| #include "core/file_sys/romfs.h" | ||||
| #include "core/file_sys/vfs_layered.h" | ||||
| #include "core/hle/service/filesystem/filesystem.h" | ||||
| #include "core/loader/loader.h" | ||||
| 
 | ||||
| @ -31,8 +32,9 @@ std::string FormatTitleVersion(u32 version, TitleVersionFormat format) { | ||||
|     return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]); | ||||
| } | ||||
| 
 | ||||
| constexpr std::array<const char*, 1> PATCH_TYPE_NAMES{ | ||||
| constexpr std::array<const char*, 2> PATCH_TYPE_NAMES{ | ||||
|     "Update", | ||||
|     "LayeredFS", | ||||
| }; | ||||
| 
 | ||||
| std::string FormatPatchTypeName(PatchType type) { | ||||
| @ -66,6 +68,42 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { | ||||
|     return exefs; | ||||
| } | ||||
| 
 | ||||
| static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { | ||||
|     const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); | ||||
|     if (type == ContentRecordType::Program && load_dir != nullptr && load_dir->GetSize() > 0) { | ||||
|         auto extracted = ExtractRomFS(romfs); | ||||
| 
 | ||||
|         if (extracted != nullptr) { | ||||
|             auto patch_dirs = load_dir->GetSubdirectories(); | ||||
|             std::sort(patch_dirs.begin(), patch_dirs.end(), | ||||
|                       [](const VirtualDir& l, const VirtualDir& r) { | ||||
|                           return l->GetName() < r->GetName(); | ||||
|                       }); | ||||
| 
 | ||||
|             std::vector<VirtualDir> layers; | ||||
|             layers.reserve(patch_dirs.size() + 1); | ||||
|             for (const auto& subdir : patch_dirs) { | ||||
|                 auto romfs_dir = subdir->GetSubdirectory("romfs"); | ||||
|                 if (romfs_dir != nullptr) | ||||
|                     layers.push_back(std::move(romfs_dir)); | ||||
|             } | ||||
| 
 | ||||
|             layers.push_back(std::move(extracted)); | ||||
| 
 | ||||
|             const auto layered = LayerDirectories(layers); | ||||
| 
 | ||||
|             if (layered != nullptr) { | ||||
|                 auto packed = CreateRomFS(layered); | ||||
| 
 | ||||
|                 if (packed != nullptr) { | ||||
|                     LOG_INFO(Loader, "    RomFS: LayeredFS patches applied successfully"); | ||||
|                     romfs = std::move(packed); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, | ||||
|                                      ContentRecordType type) const { | ||||
|     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, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // LayeredFS
 | ||||
|     ApplyLayeredFS(romfs, title_id, type); | ||||
| 
 | ||||
|     return romfs; | ||||
| } | ||||
| 
 | ||||
| @ -114,6 +155,10 @@ std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     const auto lfs_dir = Service::FileSystem::GetModificationLoadRoot(title_id); | ||||
|     if (lfs_dir != nullptr && lfs_dir->GetSize() > 0) | ||||
|         out.insert_or_assign(PatchType::LayeredFS, ""); | ||||
| 
 | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -26,6 +26,7 @@ std::string FormatTitleVersion(u32 version, | ||||
| 
 | ||||
| enum class PatchType { | ||||
|     Update, | ||||
|     LayeredFS, | ||||
| }; | ||||
| 
 | ||||
| std::string FormatPatchTypeName(PatchType type); | ||||
| @ -42,6 +43,7 @@ public: | ||||
| 
 | ||||
|     // Currently tracked RomFS patches:
 | ||||
|     // - Game Updates
 | ||||
|     // - LayeredFS
 | ||||
|     VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, | ||||
|                            ContentRecordType type = ContentRecordType::Program) const; | ||||
| 
 | ||||
|  | ||||
| @ -18,6 +18,10 @@ | ||||
| #include "core/loader/loader.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| // The size of blocks to use when vfs raw copying into nand.
 | ||||
| constexpr size_t VFS_RC_LARGE_COPY_BLOCK = 0x400000; | ||||
| 
 | ||||
| std::string RegisteredCacheEntry::DebugInfo() const { | ||||
|     return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type)); | ||||
| } | ||||
| @ -121,7 +125,7 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir, | ||||
|             if (concat.empty()) | ||||
|                 return nullptr; | ||||
| 
 | ||||
|             file = FileSys::ConcatenateFiles(concat); | ||||
|             file = FileSys::ConcatenateFiles(concat, concat.front()->GetName()); | ||||
|         } | ||||
| 
 | ||||
|         return file; | ||||
| @ -480,7 +484,8 @@ InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const Vfs | ||||
|     auto out = dir->CreateFileRelative(path); | ||||
|     if (out == nullptr) | ||||
|         return InstallResult::ErrorCopyFailed; | ||||
|     return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed; | ||||
|     return copy(in, out, VFS_RC_LARGE_COPY_BLOCK) ? InstallResult::Success | ||||
|                                                   : InstallResult::ErrorCopyFailed; | ||||
| } | ||||
| 
 | ||||
| bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) { | ||||
|  | ||||
| @ -27,7 +27,7 @@ struct ContentRecord; | ||||
| 
 | ||||
| using NcaID = std::array<u8, 0x10>; | ||||
| using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; | ||||
| using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>; | ||||
| using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>; | ||||
| 
 | ||||
| enum class InstallResult { | ||||
|     Success, | ||||
|  | ||||
| @ -4,8 +4,10 @@ | ||||
| 
 | ||||
| #include "common/common_types.h" | ||||
| #include "common/swap.h" | ||||
| #include "core/file_sys/fsmitm_romfsbuild.h" | ||||
| #include "core/file_sys/romfs.h" | ||||
| #include "core/file_sys/vfs.h" | ||||
| #include "core/file_sys/vfs_concat.h" | ||||
| #include "core/file_sys/vfs_offset.h" | ||||
| #include "core/file_sys/vfs_vector.h" | ||||
| 
 | ||||
| @ -98,7 +100,7 @@ void ProcessDirectory(VirtualFile file, std::size_t dir_offset, std::size_t file | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| VirtualDir ExtractRomFS(VirtualFile file) { | ||||
| VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) { | ||||
|     RomFSHeader header{}; | ||||
|     if (file->ReadObject(&header) != sizeof(RomFSHeader)) | ||||
|         return nullptr; | ||||
| @ -117,9 +119,22 @@ VirtualDir ExtractRomFS(VirtualFile file) { | ||||
| 
 | ||||
|     VirtualDir out = std::move(root); | ||||
| 
 | ||||
|     while (out->GetSubdirectory("") != nullptr) | ||||
|         out = out->GetSubdirectory(""); | ||||
|     while (out->GetSubdirectories().size() == 1 && out->GetFiles().empty()) { | ||||
|         if (out->GetSubdirectories().front()->GetName() == "data" && | ||||
|             type == RomFSExtractionType::Truncated) | ||||
|             break; | ||||
|         out = out->GetSubdirectories().front(); | ||||
|     } | ||||
| 
 | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| VirtualFile CreateRomFS(VirtualDir dir) { | ||||
|     if (dir == nullptr) | ||||
|         return nullptr; | ||||
| 
 | ||||
|     RomFSBuildContext ctx{dir}; | ||||
|     return ConcatenateFiles<0>(ctx.Build(), dir->GetName()); | ||||
| } | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
|  | ||||
| @ -5,6 +5,7 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <array> | ||||
| #include <map> | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/swap.h" | ||||
| @ -12,6 +13,8 @@ | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| struct RomFSHeader; | ||||
| 
 | ||||
| struct IVFCLevel { | ||||
|     u64_le offset; | ||||
|     u64_le size; | ||||
| @ -29,8 +32,18 @@ struct IVFCHeader { | ||||
| }; | ||||
| static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size."); | ||||
| 
 | ||||
| enum class RomFSExtractionType { | ||||
|     Full,      // Includes data directory
 | ||||
|     Truncated, // Traverses into data directory
 | ||||
| }; | ||||
| 
 | ||||
| // Converts a RomFS binary blob to VFS Filesystem
 | ||||
| // Returns nullptr on failure
 | ||||
| VirtualDir ExtractRomFS(VirtualFile file); | ||||
| VirtualDir ExtractRomFS(VirtualFile file, | ||||
|                         RomFSExtractionType type = RomFSExtractionType::Truncated); | ||||
| 
 | ||||
| // Converts a VFS filesystem into a RomFS binary
 | ||||
| // Returns nullptr on failure
 | ||||
| VirtualFile CreateRomFS(VirtualDir dir); | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
|  | ||||
| @ -399,6 +399,15 @@ bool VfsDirectory::Copy(std::string_view src, std::string_view dest) { | ||||
|     return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize(); | ||||
| } | ||||
| 
 | ||||
| std::map<std::string, VfsEntryType, std::less<>> VfsDirectory::GetEntries() const { | ||||
|     std::map<std::string, VfsEntryType, std::less<>> out; | ||||
|     for (const auto& dir : GetSubdirectories()) | ||||
|         out.emplace(dir->GetName(), VfsEntryType::Directory); | ||||
|     for (const auto& file : GetFiles()) | ||||
|         out.emplace(file->GetName(), VfsEntryType::File); | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| std::string VfsDirectory::GetFullPath() const { | ||||
|     if (IsRoot()) | ||||
|         return GetName(); | ||||
| @ -454,13 +463,41 @@ bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool VfsRawCopy(VirtualFile src, VirtualFile dest) { | ||||
|     if (src == nullptr || dest == nullptr) | ||||
| bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, size_t block_size) { | ||||
|     if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) | ||||
|         return false; | ||||
|     if (!dest->Resize(src->GetSize())) | ||||
|         return false; | ||||
|     std::vector<u8> data = src->ReadAllBytes(); | ||||
|     return dest->WriteBytes(data, 0) == data.size(); | ||||
| 
 | ||||
|     std::vector<u8> temp(std::min(block_size, src->GetSize())); | ||||
|     for (size_t i = 0; i < src->GetSize(); i += block_size) { | ||||
|         const auto read = std::min(block_size, src->GetSize() - i); | ||||
|         const auto block = src->Read(temp.data(), read, i); | ||||
| 
 | ||||
|         if (dest->Write(temp.data(), read, i) != read) | ||||
|             return false; | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, size_t block_size) { | ||||
|     if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) | ||||
|         return false; | ||||
| 
 | ||||
|     for (const auto& file : src->GetFiles()) { | ||||
|         const auto out = dest->CreateFile(file->GetName()); | ||||
|         if (!VfsRawCopy(file, out, block_size)) | ||||
|             return false; | ||||
|     } | ||||
| 
 | ||||
|     for (const auto& dir : src->GetSubdirectories()) { | ||||
|         const auto out = dest->CreateSubdirectory(dir->GetName()); | ||||
|         if (!VfsRawCopyD(dir, out, block_size)) | ||||
|             return false; | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) { | ||||
|  | ||||
| @ -4,6 +4,7 @@ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <map> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <string_view> | ||||
| @ -265,6 +266,10 @@ public: | ||||
|     // dest.
 | ||||
|     virtual bool Copy(std::string_view src, std::string_view dest); | ||||
| 
 | ||||
|     // Gets all of the entries directly in the directory (files and dirs), returning a map between
 | ||||
|     // item name -> type.
 | ||||
|     virtual std::map<std::string, VfsEntryType, std::less<>> GetEntries() const; | ||||
| 
 | ||||
|     // Interprets the file with name file instead as a directory of type directory.
 | ||||
|     // The directory must have a constructor that takes a single argument of type
 | ||||
|     // std::shared_ptr<VfsFile>. Allows to reinterpret container files (i.e NCA, zip, XCI, etc) as a
 | ||||
| @ -311,12 +316,17 @@ public: | ||||
| }; | ||||
| 
 | ||||
| // Compare the two files, byte-for-byte, in increments specificed by block_size
 | ||||
| bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t block_size = 0x200); | ||||
| bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size = 0x1000); | ||||
| 
 | ||||
| // A method that copies the raw data between two different implementations of VirtualFile. If you
 | ||||
| // are using the same implementation, it is probably better to use the Copy method in the parent
 | ||||
| // directory of src/dest.
 | ||||
| bool VfsRawCopy(VirtualFile src, VirtualFile dest); | ||||
| bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, size_t block_size = 0x1000); | ||||
| 
 | ||||
| // A method that performs a similar function to VfsRawCopy above, but instead copies entire
 | ||||
| // directories. It suffers the same performance penalties as above and an implementation-specific
 | ||||
| // Copy should always be preferred.
 | ||||
| bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, size_t block_size = 0x1000); | ||||
| 
 | ||||
| // Checks if the directory at path relative to rel exists. If it does, returns that. If it does not
 | ||||
| // it attempts to create it and returns the new dir or nullptr on failure.
 | ||||
|  | ||||
| @ -5,10 +5,23 @@ | ||||
| #include <algorithm> | ||||
| #include <utility> | ||||
| 
 | ||||
| #include "common/assert.h" | ||||
| #include "core/file_sys/vfs_concat.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| static bool VerifyConcatenationMapContinuity(const std::map<u64, VirtualFile>& map) { | ||||
|     const auto last_valid = --map.end(); | ||||
|     for (auto iter = map.begin(); iter != last_valid;) { | ||||
|         const auto old = iter++; | ||||
|         if (old->first + old->second->GetSize() != iter->first) { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return map.begin()->first == 0; | ||||
| } | ||||
| 
 | ||||
| VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) { | ||||
|     if (files.empty()) | ||||
|         return nullptr; | ||||
| @ -27,6 +40,11 @@ ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::s | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| ConcatenatedVfsFile::ConcatenatedVfsFile(std::map<u64, VirtualFile> files_, std::string name) | ||||
|     : files(std::move(files_)), name(std::move(name)) { | ||||
|     ASSERT(VerifyConcatenationMapContinuity(files)); | ||||
| } | ||||
| 
 | ||||
| ConcatenatedVfsFile::~ConcatenatedVfsFile() = default; | ||||
| 
 | ||||
| std::string ConcatenatedVfsFile::GetName() const { | ||||
| @ -62,7 +80,7 @@ bool ConcatenatedVfsFile::IsReadable() const { | ||||
| } | ||||
| 
 | ||||
| std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { | ||||
|     auto entry = files.end(); | ||||
|     auto entry = --files.end(); | ||||
|     for (auto iter = files.begin(); iter != files.end(); ++iter) { | ||||
|         if (iter->first > offset) { | ||||
|             entry = --iter; | ||||
| @ -70,20 +88,17 @@ std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Check if the entry should be the last one. The loop above will make it end().
 | ||||
|     if (entry == files.end() && offset < files.rbegin()->first + files.rbegin()->second->GetSize()) | ||||
|         --entry; | ||||
| 
 | ||||
|     if (entry == files.end()) | ||||
|     if (entry->first + entry->second->GetSize() <= offset) | ||||
|         return 0; | ||||
| 
 | ||||
|     const auto remaining = entry->second->GetSize() + offset - entry->first; | ||||
|     if (length > remaining) { | ||||
|         return entry->second->Read(data, remaining, offset - entry->first) + | ||||
|                Read(data + remaining, length - remaining, offset + remaining); | ||||
|     const auto read_in = | ||||
|         std::min<u64>(entry->first + entry->second->GetSize() - offset, entry->second->GetSize()); | ||||
|     if (length > read_in) { | ||||
|         return entry->second->Read(data, read_in, offset - entry->first) + | ||||
|                Read(data + read_in, length - read_in, offset + read_in); | ||||
|     } | ||||
| 
 | ||||
|     return entry->second->Read(data, length, offset - entry->first); | ||||
|     return entry->second->Read(data, std::min<u64>(read_in, length), offset - entry->first); | ||||
| } | ||||
| 
 | ||||
| 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:: | ||||
| bool ConcatenatedVfsFile::Rename(std::string_view name) { | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
|  | ||||
| @ -4,22 +4,25 @@ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <map> | ||||
| #include <memory> | ||||
| #include <string_view> | ||||
| #include <boost/container/flat_map.hpp> | ||||
| #include "core/file_sys/vfs.h" | ||||
| #include "core/file_sys/vfs_static.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| // Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
 | ||||
| VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name = ""); | ||||
| 
 | ||||
| // Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently
 | ||||
| // read-only.
 | ||||
| class ConcatenatedVfsFile : public VfsFile { | ||||
|     friend VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name); | ||||
| 
 | ||||
|     template <u8 filler_byte> | ||||
|     friend VirtualFile ConcatenateFiles(std::map<u64, VirtualFile> files, std::string name); | ||||
| 
 | ||||
|     ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name); | ||||
|     ConcatenatedVfsFile(std::map<u64, VirtualFile> files, std::string name); | ||||
| 
 | ||||
| public: | ||||
|     ~ConcatenatedVfsFile() override; | ||||
| @ -36,8 +39,37 @@ public: | ||||
| 
 | ||||
| private: | ||||
|     // Maps starting offset to file -- more efficient.
 | ||||
|     boost::container::flat_map<u64, VirtualFile> files; | ||||
|     std::map<u64, VirtualFile> files; | ||||
|     std::string name; | ||||
| }; | ||||
| 
 | ||||
| // Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
 | ||||
| VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name); | ||||
| 
 | ||||
| // Convenience function that turns a map of offsets to files into a concatenated file, filling gaps
 | ||||
| // with template parameter.
 | ||||
| template <u8 filler_byte> | ||||
| VirtualFile ConcatenateFiles(std::map<u64, VirtualFile> files, std::string name) { | ||||
|     if (files.empty()) | ||||
|         return nullptr; | ||||
|     if (files.size() == 1) | ||||
|         return files.begin()->second; | ||||
| 
 | ||||
|     const auto last_valid = --files.end(); | ||||
|     for (auto iter = files.begin(); iter != last_valid;) { | ||||
|         const auto old = iter++; | ||||
|         if (old->first + old->second->GetSize() != iter->first) { | ||||
|             files.emplace(old->first + old->second->GetSize(), | ||||
|                           std::make_shared<StaticVfsFile<filler_byte>>(iter->first - old->first - | ||||
|                                                                        old->second->GetSize())); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Ensure the map starts at offset 0 (start of file), otherwise pad to fill.
 | ||||
|     if (files.begin()->first != 0) | ||||
|         files.emplace(0, std::make_shared<StaticVfsFile<filler_byte>>(files.begin()->first)); | ||||
| 
 | ||||
|     return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name))); | ||||
| } | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
|  | ||||
							
								
								
									
										131
									
								
								src/core/file_sys/vfs_layered.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/core/file_sys/vfs_layered.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,131 @@ | ||||
| // Copyright 2018 yuzu emulator team
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <utility> | ||||
| #include "core/file_sys/vfs_layered.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| VirtualDir LayerDirectories(std::vector<VirtualDir> dirs, std::string name) { | ||||
|     if (dirs.empty()) | ||||
|         return nullptr; | ||||
|     if (dirs.size() == 1) | ||||
|         return dirs[0]; | ||||
| 
 | ||||
|     return std::shared_ptr<VfsDirectory>(new LayeredVfsDirectory(std::move(dirs), std::move(name))); | ||||
| } | ||||
| 
 | ||||
| LayeredVfsDirectory::LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name) | ||||
|     : dirs(std::move(dirs)), name(std::move(name)) {} | ||||
| 
 | ||||
| LayeredVfsDirectory::~LayeredVfsDirectory() = default; | ||||
| 
 | ||||
| std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFileRelative(std::string_view path) const { | ||||
|     for (const auto& layer : dirs) { | ||||
|         const auto file = layer->GetFileRelative(path); | ||||
|         if (file != nullptr) | ||||
|             return file; | ||||
|     } | ||||
| 
 | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetDirectoryRelative( | ||||
|     std::string_view path) const { | ||||
|     std::vector<VirtualDir> out; | ||||
|     for (const auto& layer : dirs) { | ||||
|         auto dir = layer->GetDirectoryRelative(path); | ||||
|         if (dir != nullptr) | ||||
|             out.push_back(std::move(dir)); | ||||
|     } | ||||
| 
 | ||||
|     return LayerDirectories(std::move(out)); | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFile(std::string_view name) const { | ||||
|     return GetFileRelative(name); | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetSubdirectory(std::string_view name) const { | ||||
|     return GetDirectoryRelative(name); | ||||
| } | ||||
| 
 | ||||
| std::string LayeredVfsDirectory::GetFullPath() const { | ||||
|     return dirs[0]->GetFullPath(); | ||||
| } | ||||
| 
 | ||||
| std::vector<std::shared_ptr<VfsFile>> LayeredVfsDirectory::GetFiles() const { | ||||
|     std::vector<VirtualFile> out; | ||||
|     for (const auto& layer : dirs) { | ||||
|         for (const auto& file : layer->GetFiles()) { | ||||
|             if (std::find_if(out.begin(), out.end(), [&file](const VirtualFile& comp) { | ||||
|                     return comp->GetName() == file->GetName(); | ||||
|                 }) == out.end()) { | ||||
|                 out.push_back(file); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| std::vector<std::shared_ptr<VfsDirectory>> LayeredVfsDirectory::GetSubdirectories() const { | ||||
|     std::vector<std::string> names; | ||||
|     for (const auto& layer : dirs) { | ||||
|         for (const auto& sd : layer->GetSubdirectories()) { | ||||
|             if (std::find(names.begin(), names.end(), sd->GetName()) == names.end()) | ||||
|                 names.push_back(sd->GetName()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     std::vector<VirtualDir> out; | ||||
|     out.reserve(names.size()); | ||||
|     for (const auto& subdir : names) | ||||
|         out.push_back(GetSubdirectory(subdir)); | ||||
| 
 | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| bool LayeredVfsDirectory::IsWritable() const { | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| bool LayeredVfsDirectory::IsReadable() const { | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| std::string LayeredVfsDirectory::GetName() const { | ||||
|     return name.empty() ? dirs[0]->GetName() : name; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetParentDirectory() const { | ||||
|     return dirs[0]->GetParentDirectory(); | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<VfsDirectory> LayeredVfsDirectory::CreateSubdirectory(std::string_view name) { | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<VfsFile> LayeredVfsDirectory::CreateFile(std::string_view name) { | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| bool LayeredVfsDirectory::DeleteSubdirectory(std::string_view name) { | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| bool LayeredVfsDirectory::DeleteFile(std::string_view name) { | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| bool LayeredVfsDirectory::Rename(std::string_view name_) { | ||||
|     name = name_; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool LayeredVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { | ||||
|     return false; | ||||
| } | ||||
| } // namespace FileSys
 | ||||
							
								
								
									
										52
									
								
								src/core/file_sys/vfs_layered.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/core/file_sys/vfs_layered.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | ||||
| // Copyright 2018 yuzu emulator team
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include "core/file_sys/vfs.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| // Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases.
 | ||||
| VirtualDir LayerDirectories(std::vector<VirtualDir> dirs, std::string name = ""); | ||||
| 
 | ||||
| // Class that stacks multiple VfsDirectories on top of each other, attempting to read from the first
 | ||||
| // one and falling back to the one after. The highest priority directory (overwrites all others)
 | ||||
| // should be element 0 in the dirs vector.
 | ||||
| class LayeredVfsDirectory : public VfsDirectory { | ||||
|     friend VirtualDir LayerDirectories(std::vector<VirtualDir> dirs, std::string name); | ||||
| 
 | ||||
|     LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name); | ||||
| 
 | ||||
| public: | ||||
|     ~LayeredVfsDirectory() override; | ||||
| 
 | ||||
|     std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override; | ||||
|     std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override; | ||||
|     std::shared_ptr<VfsFile> GetFile(std::string_view name) const override; | ||||
|     std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const override; | ||||
|     std::string GetFullPath() const override; | ||||
| 
 | ||||
|     std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; | ||||
|     std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; | ||||
|     bool IsWritable() const override; | ||||
|     bool IsReadable() const override; | ||||
|     std::string GetName() const override; | ||||
|     std::shared_ptr<VfsDirectory> GetParentDirectory() const override; | ||||
|     std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override; | ||||
|     std::shared_ptr<VfsFile> CreateFile(std::string_view name) override; | ||||
|     bool DeleteSubdirectory(std::string_view name) override; | ||||
|     bool DeleteFile(std::string_view name) override; | ||||
|     bool Rename(std::string_view name) override; | ||||
| 
 | ||||
| protected: | ||||
|     bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; | ||||
| 
 | ||||
| private: | ||||
|     std::vector<VirtualDir> dirs; | ||||
|     std::string name; | ||||
| }; | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
| @ -413,6 +413,23 @@ std::string RealVfsDirectory::GetFullPath() const { | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries() const { | ||||
|     if (perms == Mode::Append) | ||||
|         return {}; | ||||
| 
 | ||||
|     std::map<std::string, VfsEntryType, std::less<>> out; | ||||
|     FileUtil::ForeachDirectoryEntry( | ||||
|         nullptr, path, | ||||
|         [&out](u64* entries_out, const std::string& directory, const std::string& filename) { | ||||
|             const std::string full_path = directory + DIR_SEP + filename; | ||||
|             out.emplace(filename, FileUtil::IsDirectory(full_path) ? VfsEntryType::Directory | ||||
|                                                                    : VfsEntryType::File); | ||||
|             return true; | ||||
|         }); | ||||
| 
 | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| @ -98,6 +98,7 @@ public: | ||||
|     bool DeleteFile(std::string_view name) override; | ||||
|     bool Rename(std::string_view name) override; | ||||
|     std::string GetFullPath() const override; | ||||
|     std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override; | ||||
| 
 | ||||
| protected: | ||||
|     bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; | ||||
|  | ||||
							
								
								
									
										78
									
								
								src/core/file_sys/vfs_static.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/core/file_sys/vfs_static.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,78 @@ | ||||
| // Copyright 2018 yuzu emulator team
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <memory> | ||||
| #include <string_view> | ||||
| 
 | ||||
| #include "core/file_sys/vfs.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| template <u8 value> | ||||
| class StaticVfsFile : public VfsFile { | ||||
| public: | ||||
|     explicit StaticVfsFile(size_t size = 0, std::string name = "", VirtualDir parent = nullptr) | ||||
|         : size(size), name(std::move(name)), parent(std::move(parent)) {} | ||||
| 
 | ||||
|     std::string GetName() const override { | ||||
|         return name; | ||||
|     } | ||||
| 
 | ||||
|     size_t GetSize() const override { | ||||
|         return size; | ||||
|     } | ||||
| 
 | ||||
|     bool Resize(size_t new_size) override { | ||||
|         size = new_size; | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     std::shared_ptr<VfsDirectory> GetContainingDirectory() const override { | ||||
|         return parent; | ||||
|     } | ||||
| 
 | ||||
|     bool IsWritable() const override { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     bool IsReadable() const override { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     size_t Read(u8* data, size_t length, size_t offset) const override { | ||||
|         const auto read = std::min(length, size - offset); | ||||
|         std::fill(data, data + read, value); | ||||
|         return read; | ||||
|     } | ||||
| 
 | ||||
|     size_t Write(const u8* data, size_t length, size_t offset) override { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     boost::optional<u8> ReadByte(size_t offset) const override { | ||||
|         if (offset < size) | ||||
|             return value; | ||||
|         return boost::none; | ||||
|     } | ||||
| 
 | ||||
|     std::vector<u8> ReadBytes(size_t length, size_t offset) const override { | ||||
|         const auto read = std::min(length, size - offset); | ||||
|         return std::vector<u8>(read, value); | ||||
|     } | ||||
| 
 | ||||
|     bool Rename(std::string_view new_name) override { | ||||
|         name = new_name; | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| private: | ||||
|     size_t size; | ||||
|     std::string name; | ||||
|     VirtualDir parent; | ||||
| }; | ||||
| 
 | ||||
| } // namespace FileSys
 | ||||
| @ -3,10 +3,64 @@ | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <cstring> | ||||
| #include <utility> | ||||
| #include "core/file_sys/vfs_vector.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| VectorVfsFile::VectorVfsFile(std::vector<u8> initial_data, std::string name, VirtualDir parent) | ||||
|     : data(std::move(initial_data)), name(std::move(name)), parent(std::move(parent)) {} | ||||
| 
 | ||||
| VectorVfsFile::~VectorVfsFile() = default; | ||||
| 
 | ||||
| std::string VectorVfsFile::GetName() const { | ||||
|     return name; | ||||
| } | ||||
| 
 | ||||
| size_t VectorVfsFile::GetSize() const { | ||||
|     return data.size(); | ||||
| } | ||||
| 
 | ||||
| bool VectorVfsFile::Resize(size_t new_size) { | ||||
|     data.resize(new_size); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<VfsDirectory> VectorVfsFile::GetContainingDirectory() const { | ||||
|     return parent; | ||||
| } | ||||
| 
 | ||||
| bool VectorVfsFile::IsWritable() const { | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool VectorVfsFile::IsReadable() const { | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| size_t VectorVfsFile::Read(u8* data_, size_t length, size_t offset) const { | ||||
|     const auto read = std::min(length, data.size() - offset); | ||||
|     std::memcpy(data_, data.data() + offset, read); | ||||
|     return read; | ||||
| } | ||||
| 
 | ||||
| size_t VectorVfsFile::Write(const u8* data_, size_t length, size_t offset) { | ||||
|     if (offset + length > data.size()) | ||||
|         data.resize(offset + length); | ||||
|     const auto write = std::min(length, data.size() - offset); | ||||
|     std::memcpy(data.data(), data_, write); | ||||
|     return write; | ||||
| } | ||||
| 
 | ||||
| bool VectorVfsFile::Rename(std::string_view name_) { | ||||
|     name = name_; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void VectorVfsFile::Assign(std::vector<u8> new_data) { | ||||
|     data = std::move(new_data); | ||||
| } | ||||
| 
 | ||||
| VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_, | ||||
|                                        std::vector<VirtualDir> dirs_, std::string name_, | ||||
|                                        VirtualDir parent_) | ||||
|  | ||||
| @ -8,6 +8,31 @@ | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| // An implementation of VfsFile that is backed by a vector optionally supplied upon construction
 | ||||
| class VectorVfsFile : public VfsFile { | ||||
| public: | ||||
|     explicit VectorVfsFile(std::vector<u8> initial_data = {}, std::string name = "", | ||||
|                            VirtualDir parent = nullptr); | ||||
|     ~VectorVfsFile() override; | ||||
| 
 | ||||
|     std::string GetName() const override; | ||||
|     size_t GetSize() const override; | ||||
|     bool Resize(size_t new_size) override; | ||||
|     std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; | ||||
|     bool IsWritable() const override; | ||||
|     bool IsReadable() const override; | ||||
|     size_t Read(u8* data, size_t length, size_t offset) const override; | ||||
|     size_t Write(const u8* data, size_t length, size_t offset) override; | ||||
|     bool Rename(std::string_view name) override; | ||||
| 
 | ||||
|     virtual void Assign(std::vector<u8> new_data); | ||||
| 
 | ||||
| private: | ||||
|     std::vector<u8> data; | ||||
|     VirtualDir parent; | ||||
|     std::string name; | ||||
| }; | ||||
| 
 | ||||
| // An implementation of VfsDirectory that maintains two vectors for subdirectories and files.
 | ||||
| // Vector data is supplied upon construction.
 | ||||
| class VectorVfsDirectory : public VfsDirectory { | ||||
|  | ||||
| @ -343,6 +343,15 @@ std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents() { | ||||
|     return sdmc_factory->GetSDMCContents(); | ||||
| } | ||||
| 
 | ||||
| FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) { | ||||
|     LOG_TRACE(Service_FS, "Opening mod load root for tid={:016X}", title_id); | ||||
| 
 | ||||
|     if (bis_factory == nullptr) | ||||
|         return nullptr; | ||||
| 
 | ||||
|     return bis_factory->GetModificationLoadRoot(title_id); | ||||
| } | ||||
| 
 | ||||
| void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) { | ||||
|     if (overwrite) { | ||||
|         bis_factory = nullptr; | ||||
| @ -354,9 +363,11 @@ void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) { | ||||
|                                              FileSys::Mode::ReadWrite); | ||||
|     auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), | ||||
|                                            FileSys::Mode::ReadWrite); | ||||
|     auto load_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), | ||||
|                                              FileSys::Mode::ReadWrite); | ||||
| 
 | ||||
|     if (bis_factory == nullptr) | ||||
|         bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory); | ||||
|         bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory, load_directory); | ||||
|     if (save_data_factory == nullptr) | ||||
|         save_data_factory = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory)); | ||||
|     if (sdmc_factory == nullptr) | ||||
|  | ||||
| @ -52,6 +52,8 @@ std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents(); | ||||
| std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents(); | ||||
| std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents(); | ||||
| 
 | ||||
| FileSys::VirtualDir GetModificationLoadRoot(u64 title_id); | ||||
| 
 | ||||
| // Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
 | ||||
| // above is called.
 | ||||
| void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite = true); | ||||
|  | ||||
| @ -318,9 +318,14 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { | ||||
|     int row = item_model->itemFromIndex(item)->row(); | ||||
|     QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); | ||||
|     u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong(); | ||||
|     std::string path = child_file->data(GameListItemPath::FullPathRole).toString().toStdString(); | ||||
| 
 | ||||
|     QMenu context_menu; | ||||
|     QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); | ||||
|     QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location")); | ||||
|     context_menu.addSeparator(); | ||||
|     QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS")); | ||||
|     QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); | ||||
|     QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); | ||||
| 
 | ||||
|     open_save_location->setEnabled(program_id != 0); | ||||
| @ -329,6 +334,10 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { | ||||
| 
 | ||||
|     connect(open_save_location, &QAction::triggered, | ||||
|             [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); }); | ||||
|     connect(open_lfs_location, &QAction::triggered, | ||||
|             [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); }); | ||||
|     connect(dump_romfs, &QAction::triggered, [&]() { emit DumpRomFSRequested(program_id, path); }); | ||||
|     connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); }); | ||||
|     connect(navigate_to_gamedb_entry, &QAction::triggered, | ||||
|             [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); | ||||
| 
 | ||||
|  | ||||
| @ -28,7 +28,10 @@ namespace FileSys { | ||||
| class VfsFilesystem; | ||||
| } | ||||
| 
 | ||||
| enum class GameListOpenTarget { SaveData }; | ||||
| enum class GameListOpenTarget { | ||||
|     SaveData, | ||||
|     ModData, | ||||
| }; | ||||
| 
 | ||||
| class GameList : public QWidget { | ||||
|     Q_OBJECT | ||||
| @ -89,6 +92,8 @@ signals: | ||||
|     void GameChosen(QString game_path); | ||||
|     void ShouldCancelWorker(); | ||||
|     void OpenFolderRequested(u64 program_id, GameListOpenTarget target); | ||||
|     void DumpRomFSRequested(u64 program_id, const std::string& game_path); | ||||
|     void CopyTIDRequested(u64 program_id); | ||||
|     void NavigateToGamedbEntryRequested(u64 program_id, | ||||
|                                         const CompatibilityList& compatibility_list); | ||||
| 
 | ||||
|  | ||||
| @ -7,6 +7,22 @@ | ||||
| #include <memory> | ||||
| #include <thread> | ||||
| 
 | ||||
| // VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
 | ||||
| #include "core/file_sys/vfs.h" | ||||
| #include "core/file_sys/vfs_real.h" | ||||
| 
 | ||||
| // These are wrappers to avoid the calls to CreateDirectory and CreateFile becuase of the Windows
 | ||||
| // defines.
 | ||||
| static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper( | ||||
|     const FileSys::VirtualFilesystem& vfs, const std::string& path, FileSys::Mode mode) { | ||||
|     return vfs->CreateDirectory(path, mode); | ||||
| } | ||||
| 
 | ||||
| static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::VirtualDir& dir, | ||||
|                                                           const std::string& path) { | ||||
|     return dir->CreateFile(path); | ||||
| } | ||||
| 
 | ||||
| #include <fmt/ostream.h> | ||||
| #include <glad/glad.h> | ||||
| 
 | ||||
| @ -30,16 +46,18 @@ | ||||
| #include "common/telemetry.h" | ||||
| #include "core/core.h" | ||||
| #include "core/crypto/key_manager.h" | ||||
| #include "core/file_sys/bis_factory.h" | ||||
| #include "core/file_sys/card_image.h" | ||||
| #include "core/file_sys/content_archive.h" | ||||
| #include "core/file_sys/control_metadata.h" | ||||
| #include "core/file_sys/patch_manager.h" | ||||
| #include "core/file_sys/registered_cache.h" | ||||
| #include "core/file_sys/romfs.h" | ||||
| #include "core/file_sys/savedata_factory.h" | ||||
| #include "core/file_sys/submission_package.h" | ||||
| #include "core/file_sys/vfs_real.h" | ||||
| #include "core/hle/kernel/process.h" | ||||
| #include "core/hle/service/filesystem/filesystem.h" | ||||
| #include "core/hle/service/filesystem/fsp_ldr.h" | ||||
| #include "core/loader/loader.h" | ||||
| #include "core/perf_stats.h" | ||||
| #include "core/settings.h" | ||||
| @ -362,6 +380,8 @@ void GMainWindow::RestoreUIState() { | ||||
| void GMainWindow::ConnectWidgetEvents() { | ||||
|     connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); | ||||
|     connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); | ||||
|     connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); | ||||
|     connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); | ||||
|     connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, | ||||
|             &GMainWindow::OnGameListNavigateToGamedbEntry); | ||||
| 
 | ||||
| @ -713,6 +733,12 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target | ||||
|                                                                 program_id, user_id, 0); | ||||
|         break; | ||||
|     } | ||||
|     case GameListOpenTarget::ModData: { | ||||
|         open_target = "Mod Data"; | ||||
|         const auto load_dir = FileUtil::GetUserPath(FileUtil::UserPath::LoadDir); | ||||
|         path = fmt::format("{}{:016X}", load_dir, program_id); | ||||
|         break; | ||||
|     } | ||||
|     default: | ||||
|         UNIMPLEMENTED(); | ||||
|     } | ||||
| @ -730,6 +756,120 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target | ||||
|     QDesktopServices::openUrl(QUrl::fromLocalFile(qpath)); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) { | ||||
|     const auto path = fmt::format("{}{:016X}/romfs", | ||||
|                                   FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id); | ||||
| 
 | ||||
|     auto failed = [this, &path]() { | ||||
|         QMessageBox::warning(this, tr("RomFS Extraction Failed!"), | ||||
|                              tr("There was an error copying the RomFS files or the user " | ||||
|                                 "cancelled the operation.")); | ||||
|         vfs->DeleteDirectory(path); | ||||
|     }; | ||||
| 
 | ||||
|     const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read)); | ||||
|     if (loader == nullptr) { | ||||
|         failed(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     FileSys::VirtualFile file; | ||||
|     if (loader->ReadRomFS(file) != Loader::ResultStatus::Success) { | ||||
|         failed(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const auto romfs = | ||||
|         loader->IsRomFSUpdatable() | ||||
|             ? FileSys::PatchManager(program_id).PatchRomFS(file, loader->ReadRomFSIVFCOffset()) | ||||
|             : file; | ||||
| 
 | ||||
|     const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full); | ||||
|     if (extracted == nullptr) { | ||||
|         failed(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite); | ||||
| 
 | ||||
|     if (out == nullptr) { | ||||
|         failed(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     bool ok; | ||||
|     const auto res = QInputDialog::getItem( | ||||
|         this, tr("Select RomFS Dump Mode"), | ||||
|         tr("Please select the how you would like the RomFS dumped.<br>Full will copy all of the " | ||||
|            "files into the new directory while <br>skeleton will only create the directory " | ||||
|            "structure."), | ||||
|         {"Full", "Skeleton"}, 0, false, &ok); | ||||
|     if (!ok) | ||||
|         failed(); | ||||
| 
 | ||||
|     const auto full = res == "Full"; | ||||
| 
 | ||||
|     const static std::function<size_t(const FileSys::VirtualDir&, bool)> calculate_entry_size = | ||||
|         [](const FileSys::VirtualDir& dir, bool full) { | ||||
|             size_t out = 0; | ||||
|             for (const auto& subdir : dir->GetSubdirectories()) | ||||
|                 out += 1 + calculate_entry_size(subdir, full); | ||||
|             return out + full ? dir->GetFiles().size() : 0; | ||||
|         }; | ||||
|     const auto entry_size = calculate_entry_size(extracted, full); | ||||
| 
 | ||||
|     QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, entry_size, this); | ||||
|     progress.setWindowModality(Qt::WindowModal); | ||||
|     progress.setMinimumDuration(100); | ||||
| 
 | ||||
|     const static std::function<bool(QProgressDialog&, const FileSys::VirtualDir&, | ||||
|                                     const FileSys::VirtualDir&, size_t, bool)> | ||||
|         qt_raw_copy = [](QProgressDialog& dialog, const FileSys::VirtualDir& src, | ||||
|                          const FileSys::VirtualDir& dest, size_t block_size, bool full) { | ||||
|             if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable()) | ||||
|                 return false; | ||||
|             if (dialog.wasCanceled()) | ||||
|                 return false; | ||||
| 
 | ||||
|             if (full) { | ||||
|                 for (const auto& file : src->GetFiles()) { | ||||
|                     const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName()); | ||||
|                     if (!FileSys::VfsRawCopy(file, out, block_size)) | ||||
|                         return false; | ||||
|                     dialog.setValue(dialog.value() + 1); | ||||
|                     if (dialog.wasCanceled()) | ||||
|                         return false; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             for (const auto& dir : src->GetSubdirectories()) { | ||||
|                 const auto out = dest->CreateSubdirectory(dir->GetName()); | ||||
|                 if (!qt_raw_copy(dialog, dir, out, block_size, full)) | ||||
|                     return false; | ||||
|                 dialog.setValue(dialog.value() + 1); | ||||
|                 if (dialog.wasCanceled()) | ||||
|                     return false; | ||||
|             } | ||||
| 
 | ||||
|             return true; | ||||
|         }; | ||||
| 
 | ||||
|     if (qt_raw_copy(progress, extracted, out, 0x400000, full)) { | ||||
|         progress.close(); | ||||
|         QMessageBox::information(this, tr("RomFS Extraction Succeeded!"), | ||||
|                                  tr("The operation completed successfully.")); | ||||
|         QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(path))); | ||||
|     } else { | ||||
|         progress.close(); | ||||
|         failed(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnGameListCopyTID(u64 program_id) { | ||||
|     QClipboard* clipboard = QGuiApplication::clipboard(); | ||||
|     clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id))); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, | ||||
|                                                   const CompatibilityList& compatibility_list) { | ||||
|     const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); | ||||
| @ -790,7 +930,8 @@ void GMainWindow::OnMenuInstallToNAND() { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const auto qt_raw_copy = [this](FileSys::VirtualFile src, FileSys::VirtualFile dest) { | ||||
|     const auto qt_raw_copy = [this](const FileSys::VirtualFile& src, | ||||
|                                     const FileSys::VirtualFile& dest, size_t block_size) { | ||||
|         if (src == nullptr || dest == nullptr) | ||||
|             return false; | ||||
|         if (!dest->Resize(src->GetSize())) | ||||
|  | ||||
| @ -138,6 +138,8 @@ private slots: | ||||
|     /// Called whenever a user selects a game in the game list widget.
 | ||||
|     void OnGameListLoadFile(QString game_path); | ||||
|     void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); | ||||
|     void OnGameListDumpRomFS(u64 program_id, const std::string& game_path); | ||||
|     void OnGameListCopyTID(u64 program_id); | ||||
|     void OnGameListNavigateToGamedbEntry(u64 program_id, | ||||
|                                          const CompatibilityList& compatibility_list); | ||||
|     void OnMenuLoadFile(); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 bunnei
						bunnei