diff options
| author | Zach Hilman <zachhilman@gmail.com> | 2018-08-09 20:51:52 -0400 | 
|---|---|---|
| committer | Zach Hilman <zachhilman@gmail.com> | 2018-08-11 22:50:48 -0400 | 
| commit | 9aab7871222ca86bdf817cc6c96956b25aa76674 (patch) | |
| tree | 0995ea9a3d33b191529272983e4abf6281c29d8f /src/core/file_sys | |
| parent | ab8acce64598e89c31545713180f3334d1f81991 (diff) | |
file_sys: Add support for parsing NCA metadata (CNMT)
Diffstat (limited to 'src/core/file_sys')
| -rw-r--r-- | src/core/file_sys/nca_metadata.cpp | 125 | ||||
| -rw-r--r-- | src/core/file_sys/nca_metadata.h | 105 | 
2 files changed, 230 insertions, 0 deletions
| 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<CNMTHeader>()) { +    if (file->ReadObject(header.get()) != sizeof(CNMTHeader)) +        return; + +    // If type is {Application, Update, AOC} has opt-header. +    if (static_cast<u8>(header->type) >= 0x80 && static_cast<u8>(header->type) <= 0x82) { +        opt_header = std::make_unique<OptionalHeader>(); +        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<ContentRecord> content_records, +           std::vector<MetaRecord> meta_records) +    : file(nullptr), header(std::make_unique<CNMTHeader>(std::move(header))), +      opt_header(std::make_unique<OptionalHeader>(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<ContentRecord>& CNMT::GetContentRecords() const { +    return content_records; +} + +const std::vector<MetaRecord>& 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<u8> CNMT::Serialize() const { +    if (header == nullptr) +        return {}; +    std::vector<u8> 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 <memory> +#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<u8, 0x20> hash; +    std::array<u8, 0x10> nca_id; +    std::array<u8, 0x6> 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<ContentRecord> content_records, +         std::vector<MetaRecord> meta_records); + +    u64 GetTitleID() const; +    u32 GetTitleVersion() const; +    TitleType GetType() const; + +    const std::vector<ContentRecord>& GetContentRecords() const; +    const std::vector<MetaRecord>& GetMetaRecords() const; + +    bool UnionRecords(const CNMT& other); +    std::vector<u8> Serialize() const; + +private: +    VirtualFile file; +    std::unique_ptr<CNMTHeader> header; +    std::unique_ptr<OptionalHeader> opt_header; +    std::vector<ContentRecord> content_records; +    std::vector<MetaRecord> meta_records; + +    // TODO(DarkLordZach): According to switchbrew, for Patch-type there is additional data +    // after the table. This is not documented, unfortunately. +}; + +} // namespace FileSys | 
