diff options
| author | 2018-08-09 20:51:52 -0400 | |
|---|---|---|
| committer | 2018-08-11 22:50:48 -0400 | |
| commit | 9aab7871222ca86bdf817cc6c96956b25aa76674 (patch) | |
| tree | 0995ea9a3d33b191529272983e4abf6281c29d8f /src/core/file_sys | |
| parent | card_image: Add accessor for all NCAs in XCI (diff) | |
| download | yuzu-9aab7871222ca86bdf817cc6c96956b25aa76674.tar.gz yuzu-9aab7871222ca86bdf817cc6c96956b25aa76674.tar.xz yuzu-9aab7871222ca86bdf817cc6c96956b25aa76674.zip | |
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 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "common/common_funcs.h" | ||
| 6 | #include "common/swap.h" | ||
| 7 | #include "content_archive.h" | ||
| 8 | #include "core/file_sys/nca_metadata.h" | ||
| 9 | |||
| 10 | namespace FileSys { | ||
| 11 | |||
| 12 | CNMT::CNMT(VirtualFile file_) : file(std::move(file_)), header(std::make_unique<CNMTHeader>()) { | ||
| 13 | if (file->ReadObject(header.get()) != sizeof(CNMTHeader)) | ||
| 14 | return; | ||
| 15 | |||
| 16 | // If type is {Application, Update, AOC} has opt-header. | ||
| 17 | if (static_cast<u8>(header->type) >= 0x80 && static_cast<u8>(header->type) <= 0x82) { | ||
| 18 | opt_header = std::make_unique<OptionalHeader>(); | ||
| 19 | if (file->ReadObject(opt_header.get(), sizeof(CNMTHeader)) != sizeof(OptionalHeader)) { | ||
| 20 | opt_header = nullptr; | ||
| 21 | } | ||
| 22 | } | ||
| 23 | |||
| 24 | for (u16 i = 0; i < header->number_content_entries; ++i) { | ||
| 25 | auto& next = content_records.emplace_back(ContentRecord{}); | ||
| 26 | if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(ContentRecord) + | ||
| 27 | header->table_offset) != sizeof(ContentRecord)) { | ||
| 28 | content_records.erase(content_records.end() - 1); | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | for (u16 i = 0; i < header->number_meta_entries; ++i) { | ||
| 33 | auto& next = meta_records.emplace_back(MetaRecord{}); | ||
| 34 | if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(MetaRecord) + | ||
| 35 | header->table_offset) != sizeof(MetaRecord)) { | ||
| 36 | meta_records.erase(meta_records.end() - 1); | ||
| 37 | } | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | CNMT::CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records, | ||
| 42 | std::vector<MetaRecord> meta_records) | ||
| 43 | : file(nullptr), header(std::make_unique<CNMTHeader>(std::move(header))), | ||
| 44 | opt_header(std::make_unique<OptionalHeader>(std::move(opt_header))), | ||
| 45 | content_records(std::move(content_records)), meta_records(std::move(meta_records)) {} | ||
| 46 | |||
| 47 | u64 CNMT::GetTitleID() const { | ||
| 48 | return header->title_id; | ||
| 49 | } | ||
| 50 | |||
| 51 | u32 CNMT::GetTitleVersion() const { | ||
| 52 | return header->title_version; | ||
| 53 | } | ||
| 54 | |||
| 55 | TitleType CNMT::GetType() const { | ||
| 56 | return header->type; | ||
| 57 | } | ||
| 58 | |||
| 59 | const std::vector<ContentRecord>& CNMT::GetContentRecords() const { | ||
| 60 | return content_records; | ||
| 61 | } | ||
| 62 | |||
| 63 | const std::vector<MetaRecord>& CNMT::GetMetaRecords() const { | ||
| 64 | return meta_records; | ||
| 65 | } | ||
| 66 | |||
| 67 | bool CNMT::UnionRecords(const CNMT& other) { | ||
| 68 | bool change = false; | ||
| 69 | for (const auto& rec : other.content_records) { | ||
| 70 | const auto iter = std::find_if( | ||
| 71 | content_records.begin(), content_records.end(), | ||
| 72 | [rec](const ContentRecord& r) { return r.nca_id == rec.nca_id && r.type == rec.type; }); | ||
| 73 | if (iter == content_records.end()) { | ||
| 74 | content_records.emplace_back(rec); | ||
| 75 | ++header->number_content_entries; | ||
| 76 | change = true; | ||
| 77 | } | ||
| 78 | } | ||
| 79 | for (const auto& rec : other.meta_records) { | ||
| 80 | const auto iter = | ||
| 81 | std::find_if(meta_records.begin(), meta_records.end(), [rec](const MetaRecord& r) { | ||
| 82 | return r.title_id == rec.title_id && r.title_version == rec.title_version && | ||
| 83 | r.type == rec.type; | ||
| 84 | }); | ||
| 85 | if (iter == meta_records.end()) { | ||
| 86 | meta_records.emplace_back(rec); | ||
| 87 | ++header->number_meta_entries; | ||
| 88 | change = true; | ||
| 89 | } | ||
| 90 | } | ||
| 91 | return change; | ||
| 92 | } | ||
| 93 | |||
| 94 | std::vector<u8> CNMT::Serialize() const { | ||
| 95 | if (header == nullptr) | ||
| 96 | return {}; | ||
| 97 | std::vector<u8> out(sizeof(CNMTHeader)); | ||
| 98 | out.reserve(0x100); // Avoid resizing -- average size. | ||
| 99 | memcpy(out.data(), header.get(), sizeof(CNMTHeader)); | ||
| 100 | if (opt_header != nullptr) { | ||
| 101 | out.resize(out.size() + sizeof(OptionalHeader)); | ||
| 102 | memcpy(out.data() + sizeof(CNMTHeader), opt_header.get(), sizeof(OptionalHeader)); | ||
| 103 | } | ||
| 104 | |||
| 105 | auto offset = header->table_offset; | ||
| 106 | |||
| 107 | const auto dead_zone = offset + sizeof(CNMTHeader) - out.size(); | ||
| 108 | if (dead_zone > 0) | ||
| 109 | out.resize(offset + sizeof(CNMTHeader)); | ||
| 110 | |||
| 111 | for (const auto& rec : content_records) { | ||
| 112 | out.resize(out.size() + sizeof(ContentRecord)); | ||
| 113 | memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(ContentRecord)); | ||
| 114 | offset += sizeof(ContentRecord); | ||
| 115 | } | ||
| 116 | |||
| 117 | for (const auto& rec : meta_records) { | ||
| 118 | out.resize(out.size() + sizeof(MetaRecord)); | ||
| 119 | memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(MetaRecord)); | ||
| 120 | offset += sizeof(MetaRecord); | ||
| 121 | } | ||
| 122 | |||
| 123 | return out; | ||
| 124 | } | ||
| 125 | } // 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 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <memory> | ||
| 8 | #include "core/file_sys/vfs.h" | ||
| 9 | |||
| 10 | namespace FileSys { | ||
| 11 | class CNMT; | ||
| 12 | |||
| 13 | struct CNMTHeader; | ||
| 14 | struct OptionalHeader; | ||
| 15 | |||
| 16 | enum class TitleType : u8 { | ||
| 17 | SystemProgram = 0x01, | ||
| 18 | SystemDataArchive = 0x02, | ||
| 19 | SystemUpdate = 0x03, | ||
| 20 | FirmwarePackageA = 0x04, | ||
| 21 | FirmwarePackageB = 0x05, | ||
| 22 | Application = 0x80, | ||
| 23 | Update = 0x81, | ||
| 24 | AOC = 0x82, | ||
| 25 | DeltaTitle = 0x83, | ||
| 26 | }; | ||
| 27 | |||
| 28 | enum class ContentRecordType : u8 { | ||
| 29 | Meta = 0, | ||
| 30 | Program = 1, | ||
| 31 | Data = 2, | ||
| 32 | Control = 3, | ||
| 33 | Manual = 4, | ||
| 34 | Legal = 5, | ||
| 35 | Patch = 6, | ||
| 36 | }; | ||
| 37 | |||
| 38 | struct ContentRecord { | ||
| 39 | std::array<u8, 0x20> hash; | ||
| 40 | std::array<u8, 0x10> nca_id; | ||
| 41 | std::array<u8, 0x6> size; | ||
| 42 | ContentRecordType type; | ||
| 43 | INSERT_PADDING_BYTES(1); | ||
| 44 | }; | ||
| 45 | static_assert(sizeof(ContentRecord) == 0x38, "ContentRecord has incorrect size."); | ||
| 46 | |||
| 47 | constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, {}}; | ||
| 48 | |||
| 49 | struct MetaRecord { | ||
| 50 | u64_le title_id; | ||
| 51 | u32_le title_version; | ||
| 52 | TitleType type; | ||
| 53 | u8 install_byte; | ||
| 54 | INSERT_PADDING_BYTES(2); | ||
| 55 | }; | ||
| 56 | static_assert(sizeof(MetaRecord) == 0x10, "MetaRecord has incorrect size."); | ||
| 57 | |||
| 58 | struct OptionalHeader { | ||
| 59 | u64_le title_id; | ||
| 60 | u64_le minimum_version; | ||
| 61 | }; | ||
| 62 | static_assert(sizeof(OptionalHeader) == 0x10, "OptionalHeader has incorrect size."); | ||
| 63 | |||
| 64 | struct CNMTHeader { | ||
| 65 | u64_le title_id; | ||
| 66 | u32_le title_version; | ||
| 67 | TitleType type; | ||
| 68 | INSERT_PADDING_BYTES(1); | ||
| 69 | u16_le table_offset; | ||
| 70 | u16_le number_content_entries; | ||
| 71 | u16_le number_meta_entries; | ||
| 72 | INSERT_PADDING_BYTES(12); | ||
| 73 | }; | ||
| 74 | static_assert(sizeof(CNMTHeader) == 0x20, "CNMTHeader has incorrect size."); | ||
| 75 | |||
| 76 | // A class representing the format used by NCA metadata files, typically named {}.cnmt.nca or | ||
| 77 | // meta0.ncd. These describe which NCA's belong with which titles in the registered cache. | ||
| 78 | class CNMT { | ||
| 79 | public: | ||
| 80 | explicit CNMT(VirtualFile file); | ||
| 81 | CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records, | ||
| 82 | std::vector<MetaRecord> meta_records); | ||
| 83 | |||
| 84 | u64 GetTitleID() const; | ||
| 85 | u32 GetTitleVersion() const; | ||
| 86 | TitleType GetType() const; | ||
| 87 | |||
| 88 | const std::vector<ContentRecord>& GetContentRecords() const; | ||
| 89 | const std::vector<MetaRecord>& GetMetaRecords() const; | ||
| 90 | |||
| 91 | bool UnionRecords(const CNMT& other); | ||
| 92 | std::vector<u8> Serialize() const; | ||
| 93 | |||
| 94 | private: | ||
| 95 | VirtualFile file; | ||
| 96 | std::unique_ptr<CNMTHeader> header; | ||
| 97 | std::unique_ptr<OptionalHeader> opt_header; | ||
| 98 | std::vector<ContentRecord> content_records; | ||
| 99 | std::vector<MetaRecord> meta_records; | ||
| 100 | |||
| 101 | // TODO(DarkLordZach): According to switchbrew, for Patch-type there is additional data | ||
| 102 | // after the table. This is not documented, unfortunately. | ||
| 103 | }; | ||
| 104 | |||
| 105 | } // namespace FileSys | ||