From 9aab7871222ca86bdf817cc6c96956b25aa76674 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 9 Aug 2018 20:51:52 -0400 Subject: [PATCH] file_sys: Add support for parsing NCA metadata (CNMT) --- src/core/CMakeLists.txt | 8 ++ src/core/file_sys/nca_metadata.cpp | 125 +++++++++++++++++++++++++++++ src/core/file_sys/nca_metadata.h | 105 ++++++++++++++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 src/core/file_sys/nca_metadata.cpp create mode 100644 src/core/file_sys/nca_metadata.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index cceb1564b..8cf9fb405 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -20,6 +20,8 @@ add_library(core STATIC crypto/key_manager.h crypto/ctr_encryption_layer.cpp crypto/ctr_encryption_layer.h + file_sys/bis_factory.cpp + file_sys/bis_factory.h file_sys/card_image.cpp file_sys/card_image.h file_sys/content_archive.cpp @@ -29,10 +31,14 @@ add_library(core STATIC file_sys/directory.h file_sys/errors.h file_sys/mode.h + file_sys/nca_metadata.cpp + file_sys/nca_metadata.h file_sys/partition_filesystem.cpp file_sys/partition_filesystem.h file_sys/program_metadata.cpp file_sys/program_metadata.h + file_sys/registered_cache.cpp + file_sys/registered_cache.h file_sys/romfs.cpp file_sys/romfs.h file_sys/romfs_factory.cpp @@ -43,6 +49,8 @@ add_library(core STATIC file_sys/sdmc_factory.h file_sys/vfs.cpp file_sys/vfs.h + file_sys/vfs_concat.cpp + file_sys/vfs_concat.h file_sys/vfs_offset.cpp file_sys/vfs_offset.h file_sys/vfs_real.cpp diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp new file mode 100644 index 000000000..fa06897b7 --- /dev/null +++ b/src/core/file_sys/nca_metadata.cpp @@ -0,0 +1,125 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/common_funcs.h" +#include "common/swap.h" +#include "content_archive.h" +#include "core/file_sys/nca_metadata.h" + +namespace FileSys { + +CNMT::CNMT(VirtualFile file_) : file(std::move(file_)), header(std::make_unique()) { + if (file->ReadObject(header.get()) != sizeof(CNMTHeader)) + return; + + // If type is {Application, Update, AOC} has opt-header. + if (static_cast(header->type) >= 0x80 && static_cast(header->type) <= 0x82) { + opt_header = std::make_unique(); + if (file->ReadObject(opt_header.get(), sizeof(CNMTHeader)) != sizeof(OptionalHeader)) { + opt_header = nullptr; + } + } + + for (u16 i = 0; i < header->number_content_entries; ++i) { + auto& next = content_records.emplace_back(ContentRecord{}); + if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(ContentRecord) + + header->table_offset) != sizeof(ContentRecord)) { + content_records.erase(content_records.end() - 1); + } + } + + for (u16 i = 0; i < header->number_meta_entries; ++i) { + auto& next = meta_records.emplace_back(MetaRecord{}); + if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(MetaRecord) + + header->table_offset) != sizeof(MetaRecord)) { + meta_records.erase(meta_records.end() - 1); + } + } +} + +CNMT::CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector content_records, + std::vector meta_records) + : file(nullptr), header(std::make_unique(std::move(header))), + opt_header(std::make_unique(std::move(opt_header))), + content_records(std::move(content_records)), meta_records(std::move(meta_records)) {} + +u64 CNMT::GetTitleID() const { + return header->title_id; +} + +u32 CNMT::GetTitleVersion() const { + return header->title_version; +} + +TitleType CNMT::GetType() const { + return header->type; +} + +const std::vector& CNMT::GetContentRecords() const { + return content_records; +} + +const std::vector& CNMT::GetMetaRecords() const { + return meta_records; +} + +bool CNMT::UnionRecords(const CNMT& other) { + bool change = false; + for (const auto& rec : other.content_records) { + const auto iter = std::find_if( + content_records.begin(), content_records.end(), + [rec](const ContentRecord& r) { return r.nca_id == rec.nca_id && r.type == rec.type; }); + if (iter == content_records.end()) { + content_records.emplace_back(rec); + ++header->number_content_entries; + change = true; + } + } + for (const auto& rec : other.meta_records) { + const auto iter = + std::find_if(meta_records.begin(), meta_records.end(), [rec](const MetaRecord& r) { + return r.title_id == rec.title_id && r.title_version == rec.title_version && + r.type == rec.type; + }); + if (iter == meta_records.end()) { + meta_records.emplace_back(rec); + ++header->number_meta_entries; + change = true; + } + } + return change; +} + +std::vector CNMT::Serialize() const { + if (header == nullptr) + return {}; + std::vector out(sizeof(CNMTHeader)); + out.reserve(0x100); // Avoid resizing -- average size. + memcpy(out.data(), header.get(), sizeof(CNMTHeader)); + if (opt_header != nullptr) { + out.resize(out.size() + sizeof(OptionalHeader)); + memcpy(out.data() + sizeof(CNMTHeader), opt_header.get(), sizeof(OptionalHeader)); + } + + auto offset = header->table_offset; + + const auto dead_zone = offset + sizeof(CNMTHeader) - out.size(); + if (dead_zone > 0) + out.resize(offset + sizeof(CNMTHeader)); + + for (const auto& rec : content_records) { + out.resize(out.size() + sizeof(ContentRecord)); + memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(ContentRecord)); + offset += sizeof(ContentRecord); + } + + for (const auto& rec : meta_records) { + out.resize(out.size() + sizeof(MetaRecord)); + memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(MetaRecord)); + offset += sizeof(MetaRecord); + } + + return out; +} +} // namespace FileSys diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h new file mode 100644 index 000000000..7b0725f36 --- /dev/null +++ b/src/core/file_sys/nca_metadata.h @@ -0,0 +1,105 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "core/file_sys/vfs.h" + +namespace FileSys { +class CNMT; + +struct CNMTHeader; +struct OptionalHeader; + +enum class TitleType : u8 { + SystemProgram = 0x01, + SystemDataArchive = 0x02, + SystemUpdate = 0x03, + FirmwarePackageA = 0x04, + FirmwarePackageB = 0x05, + Application = 0x80, + Update = 0x81, + AOC = 0x82, + DeltaTitle = 0x83, +}; + +enum class ContentRecordType : u8 { + Meta = 0, + Program = 1, + Data = 2, + Control = 3, + Manual = 4, + Legal = 5, + Patch = 6, +}; + +struct ContentRecord { + std::array hash; + std::array nca_id; + std::array size; + ContentRecordType type; + INSERT_PADDING_BYTES(1); +}; +static_assert(sizeof(ContentRecord) == 0x38, "ContentRecord has incorrect size."); + +constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, {}}; + +struct MetaRecord { + u64_le title_id; + u32_le title_version; + TitleType type; + u8 install_byte; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(MetaRecord) == 0x10, "MetaRecord has incorrect size."); + +struct OptionalHeader { + u64_le title_id; + u64_le minimum_version; +}; +static_assert(sizeof(OptionalHeader) == 0x10, "OptionalHeader has incorrect size."); + +struct CNMTHeader { + u64_le title_id; + u32_le title_version; + TitleType type; + INSERT_PADDING_BYTES(1); + u16_le table_offset; + u16_le number_content_entries; + u16_le number_meta_entries; + INSERT_PADDING_BYTES(12); +}; +static_assert(sizeof(CNMTHeader) == 0x20, "CNMTHeader has incorrect size."); + +// A class representing the format used by NCA metadata files, typically named {}.cnmt.nca or +// meta0.ncd. These describe which NCA's belong with which titles in the registered cache. +class CNMT { +public: + explicit CNMT(VirtualFile file); + CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector content_records, + std::vector meta_records); + + u64 GetTitleID() const; + u32 GetTitleVersion() const; + TitleType GetType() const; + + const std::vector& GetContentRecords() const; + const std::vector& GetMetaRecords() const; + + bool UnionRecords(const CNMT& other); + std::vector Serialize() const; + +private: + VirtualFile file; + std::unique_ptr header; + std::unique_ptr opt_header; + std::vector content_records; + std::vector meta_records; + + // TODO(DarkLordZach): According to switchbrew, for Patch-type there is additional data + // after the table. This is not documented, unfortunately. +}; + +} // namespace FileSys