file_sys: Add support for parsing NCA metadata (CNMT)
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
							
								
								
									
										125
									
								
								src/core/file_sys/nca_metadata.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/core/file_sys/nca_metadata.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										105
									
								
								src/core/file_sys/nca_metadata.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/core/file_sys/nca_metadata.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
		Reference in New Issue
	
	Block a user
	 Zach Hilman
					Zach Hilman