Просмотр исходного кода

file_sys: Add support for parsing NCA metadata (CNMT)

Zach Hilman 8 лет назад
Родитель
Сommit
9aab787122
3 измененных файлов с 238 добавлено и 0 удалено
  1. 8 0
      src/core/CMakeLists.txt
  2. 125 0
      src/core/file_sys/nca_metadata.cpp
  3. 105 0
      src/core/file_sys/nca_metadata.h

+ 8 - 0
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

+ 125 - 0
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

+ 105 - 0
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