diff options
Diffstat (limited to 'src/core/file_sys')
44 files changed, 1237 insertions, 202 deletions
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 @@ // 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 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; /// 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; diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 8218893b2..edfc1bbd4 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -41,13 +41,14 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) { for (XCIPartition partition : {XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) { - auto raw = main_hfs.GetFile(partition_names[static_cast<size_t>(partition)]); + auto raw = main_hfs.GetFile(partition_names[static_cast<std::size_t>(partition)]); if (raw != nullptr) - partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw); + partitions[static_cast<std::size_t>(partition)] = + std::make_shared<PartitionFilesystem>(raw); } secure_partition = std::make_shared<NSP>( - main_hfs.GetFile(partition_names[static_cast<size_t>(XCIPartition::Secure)])); + main_hfs.GetFile(partition_names[static_cast<std::size_t>(XCIPartition::Secure)])); const auto secure_ncas = secure_partition->GetNCAsCollapsed(); std::copy(secure_ncas.begin(), secure_ncas.end(), std::back_inserter(ncas)); @@ -92,7 +93,7 @@ Loader::ResultStatus XCI::GetProgramNCAStatus() const { } VirtualDir XCI::GetPartition(XCIPartition partition) const { - return partitions[static_cast<size_t>(partition)]; + return partitions[static_cast<std::size_t>(partition)]; } std::shared_ptr<NSP> XCI::GetSecurePartitionNSP() const { @@ -168,11 +169,11 @@ bool XCI::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { } Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { - if (partitions[static_cast<size_t>(part)] == nullptr) { + if (partitions[static_cast<std::size_t>(part)] == nullptr) { return Loader::ResultStatus::ErrorXCIMissingPartition; } - for (const VirtualFile& file : partitions[static_cast<size_t>(part)]->GetFiles()) { + for (const VirtualFile& file : partitions[static_cast<std::size_t>(part)]->GetFiles()) { if (file->GetExtension() != "nca") continue; auto nca = std::make_shared<NCA>(file); @@ -187,7 +188,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { } else { const u16 error_id = static_cast<u16>(nca->GetStatus()); LOG_CRITICAL(Loader, "Could not load NCA {}/{}, failed with error code {:04X} ({})", - partition_names[static_cast<size_t>(part)], nca->GetName(), error_id, + partition_names[static_cast<std::size_t>(part)], nca->GetName(), error_id, nca->GetStatus()); } } diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 79bfb6fec..aa1b3c17d 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -298,11 +298,11 @@ NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_off auto section = sections[i]; if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) { - const size_t base_offset = + const std::size_t base_offset = header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER; ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; - const size_t romfs_offset = base_offset + ivfc_offset; - const size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; + const std::size_t romfs_offset = base_offset + ivfc_offset; + const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset); auto dec = Decrypt(section, raw, romfs_offset); @@ -463,6 +463,8 @@ NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_off status = Loader::ResultStatus::Success; } +NCA::~NCA() = default; + Loader::ResultStatus NCA::GetStatus() const { return status; } diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 00eca52da..f9f66cae9 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -81,6 +81,8 @@ class NCA : public ReadOnlyVfsDirectory { public: explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr, u64 bktr_base_ivfc_offset = 0); + ~NCA() override; + Loader::ResultStatus GetStatus() const; std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index e76bf77bf..5b1177a03 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -8,6 +8,14 @@ namespace FileSys { +const std::array<const char*, 15> LANGUAGE_NAMES = { + "AmericanEnglish", "BritishEnglish", "Japanese", + "French", "German", "LatinAmericanSpanish", + "Spanish", "Italian", "Dutch", + "CanadianFrench", "Portugese", "Russian", + "Korean", "Taiwanese", "Chinese", +}; + std::string LanguageEntry::GetApplicationName() const { return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(), 0x200); } @@ -20,18 +28,20 @@ NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) { file->ReadObject(raw.get()); } +NACP::~NACP() = default; + const LanguageEntry& NACP::GetLanguageEntry(Language language) const { if (language != Language::Default) { return raw->language_entries.at(static_cast<u8>(language)); - } else { - for (const auto& language_entry : raw->language_entries) { - if (!language_entry.GetApplicationName().empty()) - return language_entry; - } - - // Fallback to English - return GetLanguageEntry(Language::AmericanEnglish); } + + for (const auto& language_entry : raw->language_entries) { + if (!language_entry.GetApplicationName().empty()) + return language_entry; + } + + // Fallback to English + return GetLanguageEntry(Language::AmericanEnglish); } std::string NACP::GetApplicationName(Language language) const { diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h index 8a510bf46..43d6f0719 100644 --- a/src/core/file_sys/control_metadata.h +++ b/src/core/file_sys/control_metadata.h @@ -66,18 +66,15 @@ enum class Language : u8 { Default = 255, }; -static constexpr std::array<const char*, 15> LANGUAGE_NAMES = { - "AmericanEnglish", "BritishEnglish", "Japanese", - "French", "German", "LatinAmericanSpanish", - "Spanish", "Italian", "Dutch", - "CanadianFrench", "Portugese", "Russian", - "Korean", "Taiwanese", "Chinese"}; +extern const std::array<const char*, 15> LANGUAGE_NAMES; // A class representing the format used by NX metadata files, typically named Control.nacp. // These store application name, dev name, title id, and other miscellaneous data. class NACP { public: explicit NACP(VirtualFile file); + ~NACP(); + const LanguageEntry& GetLanguageEntry(Language language = Language::Default) const; std::string GetApplicationName(Language language = Language::Default) const; std::string GetDeveloperName(Language language = Language::Default) const; diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h index 3759e743a..12bb90ec8 100644 --- a/src/core/file_sys/directory.h +++ b/src/core/file_sys/directory.h @@ -25,7 +25,7 @@ enum EntryType : u8 { struct Entry { Entry(std::string_view view, EntryType entry_type, u64 entry_size) : type{entry_type}, file_size{entry_size} { - const size_t copy_size = view.copy(filename, std::size(filename) - 1); + const std::size_t copy_size = view.copy(filename, std::size(filename) - 1); filename[copy_size] = '\0'; } diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp new file mode 100644 index 000000000..2a913ce82 --- /dev/null +++ b/src/core/file_sys/fsmitm_romfsbuild.cpp @@ -0,0 +1,366 @@ +/* + * 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/alignment.h" +#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; +}; + +static u32 romfs_calc_path_hash(u32 parent, std::string path, u32 start, std::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 u64 romfs_get_hash_table_count(u64 num_entries) { + if (num_entries < 3) { + return 3; + } + + if (num_entries < 19) { + return num_entries | 1; + } + + u64 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 + static_cast<u32>(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 + static_cast<u32>(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) + Common::AlignUp(dir_ctx->path_len - dir_ctx->cur_path_ofs, 4); + 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) + Common::AlignUp(file_ctx->path_len - file_ctx->cur_path_ofs, 4); + 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 u64 dir_hash_table_entry_count = romfs_get_hash_table_count(num_dirs); + const u64 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); + + 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 = Common::AlignUp(file_partition_size, 16); + cur_file->offset = file_partition_size; + file_partition_size += cur_file->size; + cur_file->entry_offset = entry_offset; + entry_offset += sizeof(RomFSFileEntry) + + Common::AlignUp(cur_file->path_len - cur_file->cur_path_ofs, 4); + 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) + + Common::AlignUp(cur_dir->path_len - cur_dir->cur_path_ofs, 4); + } + // 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, + Common::AlignUp(cur_entry.name_size, 4)); + 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::memset(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry), 0, + Common::AlignUp(cur_entry.name_size, 4)); + 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 = Common::AlignUp(header.file_partition_ofs + file_partition_size, 4); + 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>(std::move(header_data))); + + std::vector<u8> metadata(file_hash_table_size + file_table_size + dir_hash_table_size + + dir_table_size); + std::size_t 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>(std::move(metadata))); + + return out; +} + +} // 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 @@ +/* + * 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 diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp index cdfbc5aaf..6f34b7836 100644 --- a/src/core/file_sys/nca_metadata.cpp +++ b/src/core/file_sys/nca_metadata.cpp @@ -11,11 +11,11 @@ namespace FileSys { bool operator>=(TitleType lhs, TitleType rhs) { - return static_cast<size_t>(lhs) >= static_cast<size_t>(rhs); + return static_cast<std::size_t>(lhs) >= static_cast<std::size_t>(rhs); } bool operator<=(TitleType lhs, TitleType rhs) { - return static_cast<size_t>(lhs) <= static_cast<size_t>(rhs); + return static_cast<std::size_t>(lhs) <= static_cast<std::size_t>(rhs); } CNMT::CNMT(VirtualFile file) { @@ -51,6 +51,8 @@ CNMT::CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentReco : header(std::move(header)), opt_header(std::move(opt_header)), content_records(std::move(content_records)), meta_records(std::move(meta_records)) {} +CNMT::~CNMT() = default; + u64 CNMT::GetTitleID() const { return header.title_id; } diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h index da5a8dbe8..a05d155f4 100644 --- a/src/core/file_sys/nca_metadata.h +++ b/src/core/file_sys/nca_metadata.h @@ -87,6 +87,7 @@ public: explicit CNMT(VirtualFile file); CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records, std::vector<MetaRecord> meta_records); + ~CNMT(); u64 GetTitleID() const; u32 GetTitleVersion() const; diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp index 6fc5bd7d8..0090cc6c4 100644 --- a/src/core/file_sys/nca_patch.cpp +++ b/src/core/file_sys/nca_patch.cpp @@ -22,11 +22,11 @@ BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock rel base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)), encrypted(is_encrypted_), key(key_), base_offset(base_offset_), ivfc_offset(ivfc_offset_), section_ctr(section_ctr_) { - for (size_t i = 0; i < relocation.number_buckets - 1; ++i) { + for (std::size_t i = 0; i < relocation.number_buckets - 1; ++i) { relocation_buckets[i].entries.push_back({relocation.base_offsets[i + 1], 0, 0}); } - for (size_t i = 0; i < subsection.number_buckets - 1; ++i) { + for (std::size_t i = 0; i < subsection.number_buckets - 1; ++i) { subsection_buckets[i].entries.push_back({subsection_buckets[i + 1].entries[0].address_patch, {0}, subsection_buckets[i + 1].entries[0].ctr}); @@ -37,7 +37,7 @@ BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock rel BKTR::~BKTR() = default; -size_t BKTR::Read(u8* data, size_t length, size_t offset) const { +std::size_t BKTR::Read(u8* data, std::size_t length, std::size_t offset) const { // Read out of bounds. if (offset >= relocation.size) return 0; @@ -69,14 +69,14 @@ size_t BKTR::Read(u8* data, size_t length, size_t offset) const { std::vector<u8> iv(16); auto subsection_ctr = subsection.ctr; auto offset_iv = section_offset + base_offset; - for (size_t i = 0; i < section_ctr.size(); ++i) + for (std::size_t i = 0; i < section_ctr.size(); ++i) iv[i] = section_ctr[0x8 - i - 1]; offset_iv >>= 4; - for (size_t i = 0; i < sizeof(u64); ++i) { + for (std::size_t i = 0; i < sizeof(u64); ++i) { iv[0xF - i] = static_cast<u8>(offset_iv & 0xFF); offset_iv >>= 8; } - for (size_t i = 0; i < sizeof(u32); ++i) { + for (std::size_t i = 0; i < sizeof(u32); ++i) { iv[0x7 - i] = static_cast<u8>(subsection_ctr & 0xFF); subsection_ctr >>= 8; } @@ -110,8 +110,8 @@ size_t BKTR::Read(u8* data, size_t length, size_t offset) const { } template <bool Subsection, typename BlockType, typename BucketType> -std::pair<size_t, size_t> BKTR::SearchBucketEntry(u64 offset, BlockType block, - BucketType buckets) const { +std::pair<std::size_t, std::size_t> BKTR::SearchBucketEntry(u64 offset, BlockType block, + BucketType buckets) const { if constexpr (Subsection) { const auto last_bucket = buckets[block.number_buckets - 1]; if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch) @@ -120,18 +120,18 @@ std::pair<size_t, size_t> BKTR::SearchBucketEntry(u64 offset, BlockType block, ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block."); } - size_t bucket_id = std::count_if(block.base_offsets.begin() + 1, - block.base_offsets.begin() + block.number_buckets, - [&offset](u64 base_offset) { return base_offset <= offset; }); + std::size_t bucket_id = std::count_if( + block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets, + [&offset](u64 base_offset) { return base_offset <= offset; }); const auto bucket = buckets[bucket_id]; if (bucket.number_entries == 1) return {bucket_id, 0}; - size_t low = 0; - size_t mid = 0; - size_t high = bucket.number_entries - 1; + std::size_t low = 0; + std::size_t mid = 0; + std::size_t high = bucket.number_entries - 1; while (low <= high) { mid = (low + high) / 2; if (bucket.entries[mid].address_patch > offset) { @@ -179,11 +179,11 @@ std::string BKTR::GetName() const { return base_romfs->GetName(); } -size_t BKTR::GetSize() const { +std::size_t BKTR::GetSize() const { return relocation.size; } -bool BKTR::Resize(size_t new_size) { +bool BKTR::Resize(std::size_t new_size) { return false; } @@ -199,7 +199,7 @@ bool BKTR::IsReadable() const { return true; } -size_t BKTR::Write(const u8* data, size_t length, size_t offset) { +std::size_t BKTR::Write(const u8* data, std::size_t length, std::size_t offset) { return 0; } diff --git a/src/core/file_sys/nca_patch.h b/src/core/file_sys/nca_patch.h index 381f3504f..8e64e8378 100644 --- a/src/core/file_sys/nca_patch.h +++ b/src/core/file_sys/nca_patch.h @@ -98,13 +98,13 @@ public: Core::Crypto::Key128 key, u64 base_offset, u64 ivfc_offset, std::array<u8, 8> section_ctr); ~BKTR() override; - size_t Read(u8* data, size_t length, size_t offset) const override; + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; std::string GetName() const override; - size_t GetSize() const override; + std::size_t GetSize() const override; - bool Resize(size_t new_size) override; + bool Resize(std::size_t new_size) override; std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; @@ -112,14 +112,14 @@ public: bool IsReadable() const override; - size_t Write(const u8* data, size_t length, size_t offset) override; + std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; bool Rename(std::string_view name) override; private: template <bool Subsection, typename BlockType, typename BucketType> - std::pair<size_t, size_t> SearchBucketEntry(u64 offset, BlockType block, - BucketType buckets) const; + std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, BlockType block, + BucketType buckets) const; RelocationEntry GetRelocationEntry(u64 offset) const; RelocationEntry GetNextRelocationEntry(u64 offset) const; diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp index c377edc9c..5791c76ff 100644 --- a/src/core/file_sys/partition_filesystem.cpp +++ b/src/core/file_sys/partition_filesystem.cpp @@ -42,21 +42,21 @@ PartitionFilesystem::PartitionFilesystem(std::shared_ptr<VfsFile> file) { is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0'); - size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry); - size_t metadata_size = + std::size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry); + std::size_t metadata_size = sizeof(Header) + (pfs_header.num_entries * entry_size) + pfs_header.strtab_size; // Actually read in now... std::vector<u8> file_data = file->ReadBytes(metadata_size); - const size_t total_size = file_data.size(); + const std::size_t total_size = file_data.size(); if (total_size != metadata_size) { status = Loader::ResultStatus::ErrorIncorrectPFSFileSize; return; } - size_t entries_offset = sizeof(Header); - size_t strtab_offset = entries_offset + (pfs_header.num_entries * entry_size); + std::size_t entries_offset = sizeof(Header); + std::size_t strtab_offset = entries_offset + (pfs_header.num_entries * entry_size); content_offset = strtab_offset + pfs_header.strtab_size; for (u16 i = 0; i < pfs_header.num_entries; i++) { FSEntry entry; @@ -72,6 +72,8 @@ PartitionFilesystem::PartitionFilesystem(std::shared_ptr<VfsFile> file) { status = Loader::ResultStatus::Success; } +PartitionFilesystem::~PartitionFilesystem() = default; + Loader::ResultStatus PartitionFilesystem::GetStatus() const { return status; } diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h index be7bc32a8..739c63a7f 100644 --- a/src/core/file_sys/partition_filesystem.h +++ b/src/core/file_sys/partition_filesystem.h @@ -25,6 +25,8 @@ namespace FileSys { class PartitionFilesystem : public ReadOnlyVfsDirectory { public: explicit PartitionFilesystem(std::shared_ptr<VfsFile> file); + ~PartitionFilesystem() override; + Loader::ResultStatus GetStatus() const; std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; @@ -79,7 +81,7 @@ private: Header pfs_header{}; bool is_hfs = false; - size_t content_offset = 0; + std::size_t content_offset = 0; std::vector<VirtualFile> pfs_files; std::vector<VirtualDir> pfs_dirs; diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 6cecab336..4b3b5e665 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -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" @@ -21,7 +22,7 @@ constexpr u64 SINGLE_BYTE_MODULUS = 0x100; std::string FormatTitleVersion(u32 version, TitleVersionFormat format) { std::array<u8, sizeof(u32)> bytes{}; bytes[0] = version % SINGLE_BYTE_MODULUS; - for (size_t i = 1; i < bytes.size(); ++i) { + for (std::size_t i = 1; i < bytes.size(); ++i) { version /= SINGLE_BYTE_MODULUS; bytes[i] = version % SINGLE_BYTE_MODULUS; } @@ -31,16 +32,19 @@ 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) { - return PATCH_TYPE_NAMES.at(static_cast<size_t>(type)); + return PATCH_TYPE_NAMES.at(static_cast<std::size_t>(type)); } PatchManager::PatchManager(u64 title_id) : title_id(title_id) {} +PatchManager::~PatchManager() = default; + VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id); @@ -64,6 +68,44 @@ 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) { + return; + } + + auto extracted = ExtractRomFS(romfs); + if (extracted == nullptr) { + return; + } + + 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)); + + auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers)); + if (layered == nullptr) { + return; + } + + auto packed = CreateRomFS(std::move(layered)); + if (packed == nullptr) { + return; + } + + 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, @@ -87,6 +129,9 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, } } + // LayeredFS + ApplyLayeredFS(romfs, title_id, type); + return romfs; } @@ -112,6 +157,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; } diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index b521977b2..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, enum class PatchType { Update, + LayeredFS, }; std::string FormatPatchTypeName(PatchType type); @@ -34,6 +35,7 @@ std::string FormatPatchTypeName(PatchType type); class PatchManager { public: explicit PatchManager(u64 title_id); + ~PatchManager(); // Currently tracked ExeFS patches: // - Game Updates @@ -41,6 +43,7 @@ public: // Currently tracked RomFS patches: // - Game Updates + // - LayeredFS VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, ContentRecordType type = ContentRecordType::Program) const; diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp index ccb685526..8903ed1d3 100644 --- a/src/core/file_sys/program_metadata.cpp +++ b/src/core/file_sys/program_metadata.cpp @@ -12,8 +12,12 @@ namespace FileSys { +ProgramMetadata::ProgramMetadata() = default; + +ProgramMetadata::~ProgramMetadata() = default; + Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) { - size_t total_size = static_cast<size_t>(file->GetSize()); + std::size_t total_size = static_cast<std::size_t>(file->GetSize()); if (total_size < sizeof(Header)) return Loader::ResultStatus::ErrorBadNPDMHeader; @@ -79,10 +83,12 @@ void ProgramMetadata::Print() const { auto address_space = "Unknown"; switch (npdm_header.address_space_type) { - case ProgramAddressSpaceType::Is64Bit: + case ProgramAddressSpaceType::Is36Bit: + case ProgramAddressSpaceType::Is39Bit: address_space = "64-bit"; break; case ProgramAddressSpaceType::Is32Bit: + case ProgramAddressSpaceType::Is32BitNoMap: address_space = "32-bit"; break; } diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h index 3c0a49f16..e4470d6f0 100644 --- a/src/core/file_sys/program_metadata.h +++ b/src/core/file_sys/program_metadata.h @@ -17,8 +17,10 @@ enum class ResultStatus : u16; namespace FileSys { enum class ProgramAddressSpaceType : u8 { - Is64Bit = 1, - Is32Bit = 2, + Is32Bit = 0, + Is36Bit = 1, + Is32BitNoMap = 2, + Is39Bit = 3, }; enum class ProgramFilePermission : u64 { @@ -36,6 +38,9 @@ enum class ProgramFilePermission : u64 { */ class ProgramMetadata { public: + ProgramMetadata(); + ~ProgramMetadata(); + Loader::ResultStatus Load(VirtualFile file); bool Is64BitProgram() const; diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 7361a67be..e9b040689 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -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)); } @@ -62,11 +66,11 @@ static std::string GetCNMTName(TitleType type, u64 title_id) { "" ///< Currently unknown 'DeltaTitle' }; - auto index = static_cast<size_t>(type); + auto index = static_cast<std::size_t>(type); // If the index is after the jump in TitleType, subtract it out. - if (index >= static_cast<size_t>(TitleType::Application)) { - index -= static_cast<size_t>(TitleType::Application) - - static_cast<size_t>(TitleType::FirmwarePackageB); + if (index >= static_cast<std::size_t>(TitleType::Application)) { + index -= static_cast<std::size_t>(TitleType::Application) - + static_cast<std::size_t>(TitleType::FirmwarePackageB); } return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id); } @@ -105,7 +109,7 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir, } else { std::vector<VirtualFile> concat; // Since the files are a two-digit hex number, max is FF. - for (size_t i = 0; i < 0x100; ++i) { + for (std::size_t i = 0; i < 0x100; ++i) { auto next = nca_dir->GetFile(fmt::format("{:02X}", i)); if (next != nullptr) { concat.push_back(std::move(next)); @@ -121,7 +125,7 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir, if (concat.empty()) return nullptr; - file = FileSys::ConcatenateFiles(concat); + file = ConcatenatedVfsFile::MakeConcatenatedFile(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) { 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; 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, diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp index e490c8ace..5910f7046 100644 --- a/src/core/file_sys/romfs.cpp +++ b/src/core/file_sys/romfs.cpp @@ -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" @@ -49,7 +51,7 @@ struct FileEntry { static_assert(sizeof(FileEntry) == 0x20, "FileEntry has incorrect size."); template <typename Entry> -static std::pair<Entry, std::string> GetEntry(const VirtualFile& file, size_t offset) { +static std::pair<Entry, std::string> GetEntry(const VirtualFile& file, std::size_t offset) { Entry entry{}; if (file->ReadObject(&entry, offset) != sizeof(Entry)) return {}; @@ -59,8 +61,8 @@ static std::pair<Entry, std::string> GetEntry(const VirtualFile& file, size_t of return {entry, string}; } -void ProcessFile(VirtualFile file, size_t file_offset, size_t data_offset, u32 this_file_offset, - std::shared_ptr<VectorVfsDirectory> parent) { +void ProcessFile(VirtualFile file, std::size_t file_offset, std::size_t data_offset, + u32 this_file_offset, std::shared_ptr<VectorVfsDirectory> parent) { while (true) { auto entry = GetEntry<FileEntry>(file, file_offset + this_file_offset); @@ -74,8 +76,9 @@ void ProcessFile(VirtualFile file, size_t file_offset, size_t data_offset, u32 t } } -void ProcessDirectory(VirtualFile file, size_t dir_offset, size_t file_offset, size_t data_offset, - u32 this_dir_offset, std::shared_ptr<VectorVfsDirectory> parent) { +void ProcessDirectory(VirtualFile file, std::size_t dir_offset, std::size_t file_offset, + std::size_t data_offset, u32 this_dir_offset, + std::shared_ptr<VectorVfsDirectory> parent) { while (true) { auto entry = GetEntry<DirectoryEntry>(file, dir_offset + this_dir_offset); auto current = std::make_shared<VectorVfsDirectory>( @@ -97,7 +100,7 @@ void ProcessDirectory(VirtualFile file, size_t dir_offset, size_t file_offset, s } } -VirtualDir ExtractRomFS(VirtualFile file) { +VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) { RomFSHeader header{}; if (file->ReadObject(&header) != sizeof(RomFSHeader)) return nullptr; @@ -116,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 ConcatenatedVfsFile::MakeConcatenatedFile(0, ctx.Build(), dir->GetName()); +} + } // 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 @@ #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 diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp index d9d90939e..d027a8d59 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp @@ -28,11 +28,13 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) { ivfc_offset = app_loader.ReadRomFSIVFCOffset(); } +RomFSFactory::~RomFSFactory() = default; + ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() { if (!updatable) return MakeResult<VirtualFile>(file); - const PatchManager patch_manager(Core::CurrentProcess()->program_id); + const PatchManager patch_manager(Core::CurrentProcess()->GetTitleID()); return MakeResult<VirtualFile>(patch_manager.PatchRomFS(file, ivfc_offset)); } diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h index 26b8f46cc..2cace8180 100644 --- a/src/core/file_sys/romfs_factory.h +++ b/src/core/file_sys/romfs_factory.h @@ -30,6 +30,7 @@ enum class StorageId : u8 { class RomFSFactory { public: explicit RomFSFactory(Loader::AppLoader& app_loader); + ~RomFSFactory(); ResultVal<VirtualFile> OpenCurrentProcess(); ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type); diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index e437d34e5..47f2ab9e0 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -20,6 +20,8 @@ std::string SaveDataDescriptor::DebugInfo() const { SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {} +SaveDataFactory::~SaveDataFactory() = default; + ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescriptor meta) { if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) { if (meta.zero_1 != 0) { @@ -79,16 +81,16 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ // According to switchbrew, if a save is of type SaveData and the title id field is 0, it should // be interpreted as the title id of the current process. if (type == SaveDataType::SaveData && title_id == 0) - title_id = Core::CurrentProcess()->program_id; + title_id = Core::CurrentProcess()->GetTitleID(); std::string out; switch (space) { case SaveDataSpaceId::NandSystem: - out = "/system/save/"; + out = "/system/"; break; case SaveDataSpaceId::NandUser: - out = "/user/save/"; + out = "/user/"; break; default: ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space)); @@ -96,9 +98,12 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ switch (type) { case SaveDataType::SystemSaveData: - return fmt::format("{}{:016X}/{:016X}{:016X}", out, save_id, user_id[1], user_id[0]); + return fmt::format("{}save/{:016X}/{:016X}{:016X}", out, save_id, user_id[1], user_id[0]); case SaveDataType::SaveData: - return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0], + return fmt::format("{}save/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0], + title_id); + case SaveDataType::TemporaryStorage: + return fmt::format("{}temp/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0], title_id); default: ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type)); diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h index ba978695b..d69ef6741 100644 --- a/src/core/file_sys/savedata_factory.h +++ b/src/core/file_sys/savedata_factory.h @@ -48,6 +48,7 @@ static_assert(sizeof(SaveDataDescriptor) == 0x40, "SaveDataDescriptor has incorr class SaveDataFactory { public: explicit SaveDataFactory(VirtualDir dir); + ~SaveDataFactory(); ResultVal<VirtualDir> Open(SaveDataSpaceId space, SaveDataDescriptor meta); diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h index 1120a4920..e85a2b76e 100644 --- a/src/core/file_sys/submission_package.h +++ b/src/core/file_sys/submission_package.h @@ -24,7 +24,7 @@ enum class ContentRecordType : u8; class NSP : public ReadOnlyVfsDirectory { public: explicit NSP(VirtualFile file); - ~NSP(); + ~NSP() override; Loader::ResultStatus GetStatus() const; Loader::ResultStatus GetProgramStatus(u64 title_id) const; diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index 146c839f4..bfe50da73 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp @@ -167,18 +167,18 @@ std::string VfsFile::GetExtension() const { VfsDirectory::~VfsDirectory() = default; -boost::optional<u8> VfsFile::ReadByte(size_t offset) const { +boost::optional<u8> VfsFile::ReadByte(std::size_t offset) const { u8 out{}; - size_t size = Read(&out, 1, offset); + std::size_t size = Read(&out, 1, offset); if (size == 1) return out; return boost::none; } -std::vector<u8> VfsFile::ReadBytes(size_t size, size_t offset) const { +std::vector<u8> VfsFile::ReadBytes(std::size_t size, std::size_t offset) const { std::vector<u8> out(size); - size_t read_size = Read(out.data(), size, offset); + std::size_t read_size = Read(out.data(), size, offset); out.resize(read_size); return out; } @@ -187,11 +187,11 @@ std::vector<u8> VfsFile::ReadAllBytes() const { return ReadBytes(GetSize()); } -bool VfsFile::WriteByte(u8 data, size_t offset) { +bool VfsFile::WriteByte(u8 data, std::size_t offset) { return Write(&data, 1, offset) == 1; } -size_t VfsFile::WriteBytes(const std::vector<u8>& data, size_t offset) { +std::size_t VfsFile::WriteBytes(const std::vector<u8>& data, std::size_t offset) { return Write(data.data(), data.size(), offset); } @@ -215,7 +215,7 @@ std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(std::string_view path) co } auto dir = GetSubdirectory(vec[0]); - for (size_t component = 1; component < vec.size() - 1; ++component) { + for (std::size_t component = 1; component < vec.size() - 1; ++component) { if (dir == nullptr) { return nullptr; } @@ -249,7 +249,7 @@ std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(std::string_vie } auto dir = GetSubdirectory(vec[0]); - for (size_t component = 1; component < vec.size(); ++component) { + for (std::size_t component = 1; component < vec.size(); ++component) { if (dir == nullptr) { return nullptr; } @@ -286,7 +286,7 @@ bool VfsDirectory::IsRoot() const { return GetParentDirectory() == nullptr; } -size_t VfsDirectory::GetSize() const { +std::size_t VfsDirectory::GetSize() const { const auto& files = GetFiles(); const auto sum_sizes = [](const auto& range) { return std::accumulate(range.begin(), range.end(), 0ULL, @@ -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(); @@ -434,13 +443,13 @@ bool ReadOnlyVfsDirectory::Rename(std::string_view name) { return false; } -bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size) { +bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t block_size) { if (file1->GetSize() != file2->GetSize()) return false; std::vector<u8> f1_v(block_size); std::vector<u8> f2_v(block_size); - for (size_t i = 0; i < file1->GetSize(); i += block_size) { + for (std::size_t i = 0; i < file1->GetSize(); i += block_size) { auto f1_vs = file1->Read(f1_v.data(), block_size, i); auto f2_vs = file2->Read(f2_v.data(), block_size, i); @@ -454,13 +463,41 @@ bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block return true; } -bool VfsRawCopy(VirtualFile src, VirtualFile dest) { - if (src == nullptr || dest == nullptr) +bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::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 (std::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, std::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) { diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h index 5142a3e86..270291631 100644 --- a/src/core/file_sys/vfs.h +++ b/src/core/file_sys/vfs.h @@ -4,6 +4,7 @@ #pragma once +#include <map> #include <memory> #include <string> #include <string_view> @@ -92,9 +93,9 @@ public: // Retrieves the extension of the file name. virtual std::string GetExtension() const; // Retrieves the size of the file. - virtual size_t GetSize() const = 0; + virtual std::size_t GetSize() const = 0; // Resizes the file to new_size. Returns whether or not the operation was successful. - virtual bool Resize(size_t new_size) = 0; + virtual bool Resize(std::size_t new_size) = 0; // Gets a pointer to the directory containing this file, returning nullptr if there is none. virtual std::shared_ptr<VfsDirectory> GetContainingDirectory() const = 0; @@ -105,15 +106,15 @@ public: // The primary method of reading from the file. Reads length bytes into data starting at offset // into file. Returns number of bytes successfully read. - virtual size_t Read(u8* data, size_t length, size_t offset = 0) const = 0; + virtual std::size_t Read(u8* data, std::size_t length, std::size_t offset = 0) const = 0; // The primary method of writing to the file. Writes length bytes from data starting at offset // into file. Returns number of bytes successfully written. - virtual size_t Write(const u8* data, size_t length, size_t offset = 0) = 0; + virtual std::size_t Write(const u8* data, std::size_t length, std::size_t offset = 0) = 0; // Reads exactly one byte at the offset provided, returning boost::none on error. - virtual boost::optional<u8> ReadByte(size_t offset = 0) const; + virtual boost::optional<u8> ReadByte(std::size_t offset = 0) const; // Reads size bytes starting at offset in file into a vector. - virtual std::vector<u8> ReadBytes(size_t size, size_t offset = 0) const; + virtual std::vector<u8> ReadBytes(std::size_t size, std::size_t offset = 0) const; // Reads all the bytes from the file into a vector. Equivalent to 'file->Read(file->GetSize(), // 0)' virtual std::vector<u8> ReadAllBytes() const; @@ -121,7 +122,7 @@ public: // Reads an array of type T, size number_elements starting at offset. // Returns the number of bytes (sizeof(T)*number_elements) read successfully. template <typename T> - size_t ReadArray(T* data, size_t number_elements, size_t offset = 0) const { + std::size_t ReadArray(T* data, std::size_t number_elements, std::size_t offset = 0) const { static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); return Read(reinterpret_cast<u8*>(data), number_elements * sizeof(T), offset); @@ -130,7 +131,7 @@ public: // Reads size bytes into the memory starting at data starting at offset into the file. // Returns the number of bytes read successfully. template <typename T> - size_t ReadBytes(T* data, size_t size, size_t offset = 0) const { + std::size_t ReadBytes(T* data, std::size_t size, std::size_t offset = 0) const { static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); return Read(reinterpret_cast<u8*>(data), size, offset); } @@ -138,22 +139,22 @@ public: // Reads one object of type T starting at offset in file. // Returns the number of bytes read successfully (sizeof(T)). template <typename T> - size_t ReadObject(T* data, size_t offset = 0) const { + std::size_t ReadObject(T* data, std::size_t offset = 0) const { static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); return Read(reinterpret_cast<u8*>(data), sizeof(T), offset); } // Writes exactly one byte to offset in file and retuns whether or not the byte was written // successfully. - virtual bool WriteByte(u8 data, size_t offset = 0); + virtual bool WriteByte(u8 data, std::size_t offset = 0); // Writes a vector of bytes to offset in file and returns the number of bytes successfully // written. - virtual size_t WriteBytes(const std::vector<u8>& data, size_t offset = 0); + virtual std::size_t WriteBytes(const std::vector<u8>& data, std::size_t offset = 0); // Writes an array of type T, size number_elements to offset in file. // Returns the number of bytes (sizeof(T)*number_elements) written successfully. template <typename T> - size_t WriteArray(const T* data, size_t number_elements, size_t offset = 0) { + std::size_t WriteArray(const T* data, std::size_t number_elements, std::size_t offset = 0) { static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); return Write(data, number_elements * sizeof(T), offset); } @@ -161,7 +162,7 @@ public: // Writes size bytes starting at memory location data to offset in file. // Returns the number of bytes written successfully. template <typename T> - size_t WriteBytes(const T* data, size_t size, size_t offset = 0) { + std::size_t WriteBytes(const T* data, std::size_t size, std::size_t offset = 0) { static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); return Write(reinterpret_cast<const u8*>(data), size, offset); } @@ -169,7 +170,7 @@ public: // Writes one object of type T to offset in file. // Returns the number of bytes written successfully (sizeof(T)). template <typename T> - size_t WriteObject(const T& data, size_t offset = 0) { + std::size_t WriteObject(const T& data, std::size_t offset = 0) { static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); return Write(&data, sizeof(T), offset); } @@ -221,7 +222,7 @@ public: // Returns the name of the directory. virtual std::string GetName() const = 0; // Returns the total size of all files and subdirectories in this directory. - virtual size_t GetSize() const; + virtual std::size_t GetSize() const; // Returns the parent directory of this directory. Returns nullptr if this directory is root or // has no parent. virtual std::shared_ptr<VfsDirectory> GetParentDirectory() const = 0; @@ -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 @@ -310,13 +315,19 @@ public: bool Rename(std::string_view name) override; }; -// Compare the two files, byte-for-byte, in increments specificed by block_size -bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size = 0x200); +// Compare the two files, byte-for-byte, in increments specified by block_size +bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, + std::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, std::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, std::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. diff --git a/src/core/file_sys/vfs_concat.cpp b/src/core/file_sys/vfs_concat.cpp index e6bf586a3..16d801c0c 100644 --- a/src/core/file_sys/vfs_concat.cpp +++ b/src/core/file_sys/vfs_concat.cpp @@ -5,28 +5,75 @@ #include <algorithm> #include <utility> +#include "common/assert.h" #include "core/file_sys/vfs_concat.h" +#include "core/file_sys/vfs_static.h" namespace FileSys { -VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) { - if (files.empty()) - return nullptr; - if (files.size() == 1) - return files[0]; +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 std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name))); + return map.begin()->first == 0; } ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name) : name(std::move(name)) { - size_t next_offset = 0; + std::size_t next_offset = 0; for (const auto& file : files_) { files[next_offset] = file; next_offset += file->GetSize(); } } +ConcatenatedVfsFile::ConcatenatedVfsFile(std::map<u64, VirtualFile> files_, std::string name) + : files(std::move(files_)), name(std::move(name)) { + ASSERT(VerifyConcatenationMapContinuity(files)); +} + +ConcatenatedVfsFile::~ConcatenatedVfsFile() = default; + +VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(std::vector<VirtualFile> files, + std::string name) { + if (files.empty()) + return nullptr; + if (files.size() == 1) + return files[0]; + + return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name))); +} + +VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte, + 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))); +} + std::string ConcatenatedVfsFile::GetName() const { if (files.empty()) return ""; @@ -35,13 +82,13 @@ std::string ConcatenatedVfsFile::GetName() const { return files.begin()->second->GetName(); } -size_t ConcatenatedVfsFile::GetSize() const { +std::size_t ConcatenatedVfsFile::GetSize() const { if (files.empty()) return 0; return files.rbegin()->first + files.rbegin()->second->GetSize(); } -bool ConcatenatedVfsFile::Resize(size_t new_size) { +bool ConcatenatedVfsFile::Resize(std::size_t new_size) { return false; } @@ -59,8 +106,8 @@ bool ConcatenatedVfsFile::IsReadable() const { return true; } -size_t ConcatenatedVfsFile::Read(u8* data, size_t length, size_t offset) const { - auto entry = files.end(); +std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { + auto entry = --files.end(); for (auto iter = files.begin(); iter != files.end(); ++iter) { if (iter->first > offset) { entry = --iter; @@ -68,27 +115,25 @@ size_t ConcatenatedVfsFile::Read(u8* data, size_t length, size_t offset) const { } } - // 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); } -size_t ConcatenatedVfsFile::Write(const u8* data, size_t length, size_t offset) { +std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { return 0; } bool ConcatenatedVfsFile::Rename(std::string_view name) { return false; } + } // namespace FileSys diff --git a/src/core/file_sys/vfs_concat.h b/src/core/file_sys/vfs_concat.h index 686d32515..c90f9d5d1 100644 --- a/src/core/file_sys/vfs_concat.h +++ b/src/core/file_sys/vfs_concat.h @@ -4,37 +4,43 @@ #pragma once +#include <map> #include <memory> #include <string_view> -#include <boost/container/flat_map.hpp> #include "core/file_sys/vfs.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); - ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name); + ConcatenatedVfsFile(std::map<u64, VirtualFile> files, std::string name); public: + ~ConcatenatedVfsFile() override; + + /// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases. + static VirtualFile MakeConcatenatedFile(std::vector<VirtualFile> files, std::string name); + + /// Convenience function that turns a map of offsets to files into a concatenated file, filling + /// gaps with a given filler byte. + static VirtualFile MakeConcatenatedFile(u8 filler_byte, std::map<u64, VirtualFile> files, + std::string name); + std::string GetName() const override; - size_t GetSize() const override; - bool Resize(size_t new_size) override; + std::size_t GetSize() const override; + bool Resize(std::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; + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; + std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; bool Rename(std::string_view name) override; private: // Maps starting offset to file -- more efficient. - boost::container::flat_map<u64, VirtualFile> files; + std::map<u64, VirtualFile> files; std::string name; }; diff --git a/src/core/file_sys/vfs_layered.cpp b/src/core/file_sys/vfs_layered.cpp new file mode 100644 index 000000000..bfee01725 --- /dev/null +++ b/src/core/file_sys/vfs_layered.cpp @@ -0,0 +1,132 @@ +// 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 { + +LayeredVfsDirectory::LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name) + : dirs(std::move(dirs)), name(std::move(name)) {} + +LayeredVfsDirectory::~LayeredVfsDirectory() = default; + +VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(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))); +} + +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 MakeLayeredDirectory(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 diff --git a/src/core/file_sys/vfs_layered.h b/src/core/file_sys/vfs_layered.h new file mode 100644 index 000000000..d85310f57 --- /dev/null +++ b/src/core/file_sys/vfs_layered.h @@ -0,0 +1,50 @@ +// 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 { + +// 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 { + LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name); + +public: + ~LayeredVfsDirectory() override; + + /// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases. + static VirtualDir MakeLayeredDirectory(std::vector<VirtualDir> dirs, std::string name = ""); + + 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 diff --git a/src/core/file_sys/vfs_offset.cpp b/src/core/file_sys/vfs_offset.cpp index 847cde2f5..a4c6719a0 100644 --- a/src/core/file_sys/vfs_offset.cpp +++ b/src/core/file_sys/vfs_offset.cpp @@ -9,20 +9,22 @@ namespace FileSys { -OffsetVfsFile::OffsetVfsFile(std::shared_ptr<VfsFile> file_, size_t size_, size_t offset_, +OffsetVfsFile::OffsetVfsFile(std::shared_ptr<VfsFile> file_, std::size_t size_, std::size_t offset_, std::string name_, VirtualDir parent_) : file(file_), offset(offset_), size(size_), name(std::move(name_)), parent(parent_ == nullptr ? file->GetContainingDirectory() : std::move(parent_)) {} +OffsetVfsFile::~OffsetVfsFile() = default; + std::string OffsetVfsFile::GetName() const { return name.empty() ? file->GetName() : name; } -size_t OffsetVfsFile::GetSize() const { +std::size_t OffsetVfsFile::GetSize() const { return size; } -bool OffsetVfsFile::Resize(size_t new_size) { +bool OffsetVfsFile::Resize(std::size_t new_size) { if (offset + new_size < file->GetSize()) { size = new_size; } else { @@ -47,22 +49,22 @@ bool OffsetVfsFile::IsReadable() const { return file->IsReadable(); } -size_t OffsetVfsFile::Read(u8* data, size_t length, size_t r_offset) const { +std::size_t OffsetVfsFile::Read(u8* data, std::size_t length, std::size_t r_offset) const { return file->Read(data, TrimToFit(length, r_offset), offset + r_offset); } -size_t OffsetVfsFile::Write(const u8* data, size_t length, size_t r_offset) { +std::size_t OffsetVfsFile::Write(const u8* data, std::size_t length, std::size_t r_offset) { return file->Write(data, TrimToFit(length, r_offset), offset + r_offset); } -boost::optional<u8> OffsetVfsFile::ReadByte(size_t r_offset) const { +boost::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const { if (r_offset < size) return file->ReadByte(offset + r_offset); return boost::none; } -std::vector<u8> OffsetVfsFile::ReadBytes(size_t r_size, size_t r_offset) const { +std::vector<u8> OffsetVfsFile::ReadBytes(std::size_t r_size, std::size_t r_offset) const { return file->ReadBytes(TrimToFit(r_size, r_offset), offset + r_offset); } @@ -70,14 +72,14 @@ std::vector<u8> OffsetVfsFile::ReadAllBytes() const { return file->ReadBytes(size, offset); } -bool OffsetVfsFile::WriteByte(u8 data, size_t r_offset) { +bool OffsetVfsFile::WriteByte(u8 data, std::size_t r_offset) { if (r_offset < size) return file->WriteByte(data, offset + r_offset); return false; } -size_t OffsetVfsFile::WriteBytes(const std::vector<u8>& data, size_t r_offset) { +std::size_t OffsetVfsFile::WriteBytes(const std::vector<u8>& data, std::size_t r_offset) { return file->Write(data.data(), TrimToFit(data.size(), r_offset), offset + r_offset); } @@ -85,12 +87,12 @@ bool OffsetVfsFile::Rename(std::string_view name) { return file->Rename(name); } -size_t OffsetVfsFile::GetOffset() const { +std::size_t OffsetVfsFile::GetOffset() const { return offset; } -size_t OffsetVfsFile::TrimToFit(size_t r_size, size_t r_offset) const { - return std::clamp(r_size, size_t{0}, size - r_offset); +std::size_t OffsetVfsFile::TrimToFit(std::size_t r_size, std::size_t r_offset) const { + return std::clamp(r_size, std::size_t{0}, size - r_offset); } } // namespace FileSys diff --git a/src/core/file_sys/vfs_offset.h b/src/core/file_sys/vfs_offset.h index cb92d1570..8062702a7 100644 --- a/src/core/file_sys/vfs_offset.h +++ b/src/core/file_sys/vfs_offset.h @@ -17,33 +17,34 @@ namespace FileSys { // the size of this wrapper. class OffsetVfsFile : public VfsFile { public: - OffsetVfsFile(std::shared_ptr<VfsFile> file, size_t size, size_t offset = 0, + OffsetVfsFile(std::shared_ptr<VfsFile> file, std::size_t size, std::size_t offset = 0, std::string new_name = "", VirtualDir new_parent = nullptr); + ~OffsetVfsFile() override; std::string GetName() const override; - size_t GetSize() const override; - bool Resize(size_t new_size) override; + std::size_t GetSize() const override; + bool Resize(std::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; - boost::optional<u8> ReadByte(size_t offset) const override; - std::vector<u8> ReadBytes(size_t size, size_t offset) const override; + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; + std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; + boost::optional<u8> ReadByte(std::size_t offset) const override; + std::vector<u8> ReadBytes(std::size_t size, std::size_t offset) const override; std::vector<u8> ReadAllBytes() const override; - bool WriteByte(u8 data, size_t offset) override; - size_t WriteBytes(const std::vector<u8>& data, size_t offset) override; + bool WriteByte(u8 data, std::size_t offset) override; + std::size_t WriteBytes(const std::vector<u8>& data, std::size_t offset) override; bool Rename(std::string_view name) override; - size_t GetOffset() const; + std::size_t GetOffset() const; private: - size_t TrimToFit(size_t r_size, size_t r_offset) const; + std::size_t TrimToFit(std::size_t r_size, std::size_t r_offset) const; std::shared_ptr<VfsFile> file; - size_t offset; - size_t size; + std::size_t offset; + std::size_t size; std::string name; VirtualDir parent; }; diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index 89b101145..9defad04c 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -227,11 +227,11 @@ std::string RealVfsFile::GetName() const { return path_components.back(); } -size_t RealVfsFile::GetSize() const { +std::size_t RealVfsFile::GetSize() const { return backing->GetSize(); } -bool RealVfsFile::Resize(size_t new_size) { +bool RealVfsFile::Resize(std::size_t new_size) { return backing->Resize(new_size); } @@ -247,13 +247,13 @@ bool RealVfsFile::IsReadable() const { return (perms & Mode::ReadWrite) != 0; } -size_t RealVfsFile::Read(u8* data, size_t length, size_t offset) const { +std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { if (!backing->Seek(offset, SEEK_SET)) return 0; return backing->ReadBytes(data, length); } -size_t RealVfsFile::Write(const u8* data, size_t length, size_t offset) { +std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { if (!backing->Seek(offset, SEEK_SET)) return 0; return backing->WriteBytes(data, length); @@ -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; } diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index 7db86691f..5b61db90d 100644 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h @@ -48,13 +48,13 @@ public: ~RealVfsFile() override; std::string GetName() const override; - size_t GetSize() const override; - bool Resize(size_t new_size) override; + std::size_t GetSize() const override; + bool Resize(std::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; + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; + std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; bool Rename(std::string_view name) override; private: @@ -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; diff --git a/src/core/file_sys/vfs_static.h b/src/core/file_sys/vfs_static.h new file mode 100644 index 000000000..44fab51d1 --- /dev/null +++ b/src/core/file_sys/vfs_static.h @@ -0,0 +1,79 @@ +// 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 { + +class StaticVfsFile : public VfsFile { +public: + explicit StaticVfsFile(u8 value, std::size_t size = 0, std::string name = "", + VirtualDir parent = nullptr) + : value{value}, size{size}, name{std::move(name)}, parent{std::move(parent)} {} + + std::string GetName() const override { + return name; + } + + std::size_t GetSize() const override { + return size; + } + + bool Resize(std::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; + } + + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override { + const auto read = std::min(length, size - offset); + std::fill(data, data + read, value); + return read; + } + + std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override { + return 0; + } + + boost::optional<u8> ReadByte(std::size_t offset) const override { + if (offset < size) + return value; + return boost::none; + } + + std::vector<u8> ReadBytes(std::size_t length, std::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: + u8 value; + std::size_t size; + std::string name; + VirtualDir parent; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp index 98e7c4598..389c7e003 100644 --- a/src/core/file_sys/vfs_vector.cpp +++ b/src/core/file_sys/vfs_vector.cpp @@ -3,16 +3,72 @@ // 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)), parent(std::move(parent)), name(std::move(name)) {} + +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; +} + +std::size_t VectorVfsFile::Read(u8* data_, std::size_t length, std::size_t offset) const { + const auto read = std::min(length, data.size() - offset); + std::memcpy(data_, data.data() + offset, read); + return read; +} + +std::size_t VectorVfsFile::Write(const u8* data_, std::size_t length, std::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_) : files(std::move(files_)), dirs(std::move(dirs_)), parent(std::move(parent_)), name(std::move(name_)) {} +VectorVfsDirectory::~VectorVfsDirectory() = default; + std::vector<std::shared_ptr<VfsFile>> VectorVfsDirectory::GetFiles() const { return files; } diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h index 179f62e4b..48a414c98 100644 --- a/src/core/file_sys/vfs_vector.h +++ b/src/core/file_sys/vfs_vector.h @@ -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; + std::size_t GetSize() const override; + bool Resize(std::size_t new_size) override; + std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; + bool IsWritable() const override; + bool IsReadable() const override; + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; + std::size_t Write(const u8* data, std::size_t length, std::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 { @@ -15,6 +40,7 @@ public: explicit VectorVfsDirectory(std::vector<VirtualFile> files = {}, std::vector<VirtualDir> dirs = {}, std::string name = "", VirtualDir parent = nullptr); + ~VectorVfsDirectory() override; std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp index 4dbc25c55..b2b164368 100644 --- a/src/core/file_sys/xts_archive.cpp +++ b/src/core/file_sys/xts_archive.cpp @@ -25,14 +25,11 @@ namespace FileSys { constexpr u64 NAX_HEADER_PADDING_SIZE = 0x4000; template <typename SourceData, typename SourceKey, typename Destination> -static bool CalculateHMAC256(Destination* out, const SourceKey* key, size_t key_length, - const SourceData* data, size_t data_length) { +static bool CalculateHMAC256(Destination* out, const SourceKey* key, std::size_t key_length, + const SourceData* data, std::size_t data_length) { mbedtls_md_context_t context; mbedtls_md_init(&context); - const auto key_f = reinterpret_cast<const u8*>(key); - const std::vector<u8> key_v(key_f, key_f + key_length); - if (mbedtls_md_setup(&context, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1) || mbedtls_md_hmac_starts(&context, reinterpret_cast<const u8*>(key), key_length) || mbedtls_md_hmac_update(&context, reinterpret_cast<const u8*>(data), data_length) || @@ -45,7 +42,7 @@ static bool CalculateHMAC256(Destination* out, const SourceKey* key, size_t key_ return true; } -NAX::NAX(VirtualFile file_) : file(std::move(file_)), header(std::make_unique<NAXHeader>()) { +NAX::NAX(VirtualFile file_) : header(std::make_unique<NAXHeader>()), file(std::move(file_)) { std::string path = FileUtil::SanitizePath(file->GetFullPath()); static const std::regex nax_path_regex("/registered/(000000[0-9A-F]{2})/([0-9A-F]{32})\\.nca", std::regex_constants::ECMAScript | @@ -65,13 +62,15 @@ NAX::NAX(VirtualFile file_) : file(std::move(file_)), header(std::make_unique<NA } NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id) - : file(std::move(file_)), header(std::make_unique<NAXHeader>()) { + : header(std::make_unique<NAXHeader>()), file(std::move(file_)) { Core::Crypto::SHA256Hash hash{}; mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0); status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0], Common::HexArrayToString(nca_id, false))); } +NAX::~NAX() = default; + Loader::ResultStatus NAX::Parse(std::string_view path) { if (file->ReadObject(header.get()) != sizeof(NAXHeader)) return Loader::ResultStatus::ErrorBadNAXHeader; @@ -91,7 +90,7 @@ Loader::ResultStatus NAX::Parse(std::string_view path) { const auto enc_keys = header->key_area; - size_t i = 0; + std::size_t i = 0; for (; i < sd_keys.size(); ++i) { std::array<Core::Crypto::Key128, 2> nax_keys{}; if (!CalculateHMAC256(nax_keys.data(), sd_keys[i].data(), 0x10, std::string(path).c_str(), @@ -99,7 +98,7 @@ Loader::ResultStatus NAX::Parse(std::string_view path) { return Loader::ResultStatus::ErrorNAXKeyHMACFailed; } - for (size_t j = 0; j < nax_keys.size(); ++j) { + for (std::size_t j = 0; j < nax_keys.size(); ++j) { Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(nax_keys[j], Core::Crypto::Mode::ECB); cipher.Transcode(enc_keys[j].data(), 0x10, header->key_area[j].data(), @@ -138,9 +137,9 @@ VirtualFile NAX::GetDecrypted() const { return dec_file; } -std::shared_ptr<NCA> NAX::AsNCA() const { +std::unique_ptr<NCA> NAX::AsNCA() const { if (type == NAXContentType::NCA) - return std::make_shared<NCA>(GetDecrypted()); + return std::make_unique<NCA>(GetDecrypted()); return nullptr; } diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h index 55d2154a6..8fedd8585 100644 --- a/src/core/file_sys/xts_archive.h +++ b/src/core/file_sys/xts_archive.h @@ -33,12 +33,13 @@ class NAX : public ReadOnlyVfsDirectory { public: explicit NAX(VirtualFile file); explicit NAX(VirtualFile file, std::array<u8, 0x10> nca_id); + ~NAX() override; Loader::ResultStatus GetStatus() const; VirtualFile GetDecrypted() const; - std::shared_ptr<NCA> AsNCA() const; + std::unique_ptr<NCA> AsNCA() const; NAXContentType GetContentType() const; @@ -60,7 +61,7 @@ private: VirtualFile file; Loader::ResultStatus status; - NAXContentType type; + NAXContentType type{}; VirtualFile dec_file; |
