From 1efe5a76b11a5e7fd8071686acaf1b060e721aaa Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sat, 25 Aug 2018 19:01:46 -0400
Subject: [PATCH] content_archive: Add BKTR header parsing to NCA

---
 src/core/file_sys/content_archive.cpp | 171 +++++++++++++++++++++++---
 src/core/file_sys/content_archive.h   |   8 +-
 2 files changed, 160 insertions(+), 19 deletions(-)

diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 7cfb6f36b6..f0d376bf52 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -12,6 +12,7 @@
 #include "core/crypto/aes_util.h"
 #include "core/crypto/ctr_encryption_layer.h"
 #include "core/file_sys/content_archive.h"
+#include "core/file_sys/nca_patch.h"
 #include "core/file_sys/partition_filesystem.h"
 #include "core/file_sys/romfs.h"
 #include "core/file_sys/vfs_offset.h"
@@ -68,10 +69,31 @@ struct RomFSSuperblock {
 };
 static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size.");
 
+struct BKTRHeader {
+    u64_le offset;
+    u64_le size;
+    u32_le magic;
+    INSERT_PADDING_BYTES(0x4);
+    u32_le number_entries;
+    INSERT_PADDING_BYTES(0x4);
+};
+static_assert(sizeof(BKTRHeader) == 0x20, "BKTRHeader has incorrect size.");
+
+struct BKTRSuperblock {
+    NCASectionHeaderBlock header_block;
+    IVFCHeader ivfc;
+    INSERT_PADDING_BYTES(0x18);
+    BKTRHeader relocation;
+    BKTRHeader subsection;
+    INSERT_PADDING_BYTES(0xC0);
+};
+static_assert(sizeof(BKTRSuperblock) == 0x200, "BKTRSuperblock has incorrect size.");
+
 union NCASectionHeader {
     NCASectionRaw raw;
     PFS0Superblock pfs0;
     RomFSSuperblock romfs;
+    BKTRSuperblock bktr;
 };
 static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
 
@@ -104,7 +126,7 @@ boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType ty
     Core::Crypto::Key128 out;
     if (type == NCASectionCryptoType::XTS)
         std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin());
-    else if (type == NCASectionCryptoType::CTR)
+    else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR)
         std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin());
     else
         LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}",
@@ -154,6 +176,9 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
         LOG_DEBUG(Crypto, "called with mode=NONE");
         return in;
     case NCASectionCryptoType::CTR:
+    // During normal BKTR decryption, this entire function is skipped. This is for the metadata,
+    // which uses the same CTR as usual.
+    case NCASectionCryptoType::BKTR:
         LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
         {
             boost::optional<Core::Crypto::Key128> key = boost::none;
@@ -190,7 +215,9 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
     }
 }
 
-NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
+NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_)
+    : file(std::move(file_)),
+      bktr_base_romfs(bktr_base_romfs_ ? std::move(bktr_base_romfs_) : nullptr) {
     status = Loader::ResultStatus::Success;
 
     if (file == nullptr) {
@@ -270,17 +297,15 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
         auto section = sections[i];
 
         if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
+            const size_t base_offset =
+                header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER;
             const size_t romfs_offset =
-                header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER +
-                section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
+                base_offset + section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
             const size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
-            auto dec =
-                Decrypt(section, std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset),
-                        romfs_offset);
-            if (dec != nullptr) {
-                files.push_back(std::move(dec));
-                romfs = files.back();
-            } else {
+            auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset);
+            auto dec = Decrypt(section, raw, romfs_offset);
+
+            if (dec == nullptr) {
                 if (status != Loader::ResultStatus::Success)
                     return;
                 if (has_rights_id)
@@ -289,6 +314,120 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
                     status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
                 return;
             }
+
+            if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) {
+                if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') ||
+                    section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) {
+                    status = Loader::ResultStatus::ErrorBadBKTRHeader;
+                    return;
+                }
+
+                if (section.bktr.relocation.offset + section.bktr.relocation.size !=
+                    section.bktr.subsection.offset) {
+                    status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation;
+                    return;
+                }
+
+                const u64 size =
+                    MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
+                                               header.section_tables[i].media_offset);
+                if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
+                    status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
+                    return;
+                }
+
+                const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
+                RelocationBlock relocation_block{};
+                if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
+                    sizeof(RelocationBlock)) {
+                    status = Loader::ResultStatus::ErrorBadRelocationBlock;
+                    return;
+                }
+                SubsectionBlock subsection_block{};
+                if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
+                    sizeof(RelocationBlock)) {
+                    status = Loader::ResultStatus::ErrorBadSubsectionBlock;
+                    return;
+                }
+
+                std::vector<RelocationBucketRaw> relocation_buckets_raw(
+                    (section.bktr.relocation.size - sizeof(RelocationBlock)) /
+                    sizeof(RelocationBucketRaw));
+                if (dec->ReadBytes(relocation_buckets_raw.data(),
+                                   section.bktr.relocation.size - sizeof(RelocationBlock),
+                                   section.bktr.relocation.offset + sizeof(RelocationBlock) -
+                                       offset) !=
+                    section.bktr.relocation.size - sizeof(RelocationBlock)) {
+                    status = Loader::ResultStatus::ErrorBadRelocationBuckets;
+                    return;
+                }
+
+                std::vector<SubsectionBucketRaw> subsection_buckets_raw(
+                    (section.bktr.subsection.size - sizeof(SubsectionBlock)) /
+                    sizeof(SubsectionBucketRaw));
+                if (dec->ReadBytes(subsection_buckets_raw.data(),
+                                   section.bktr.subsection.size - sizeof(SubsectionBlock),
+                                   section.bktr.subsection.offset + sizeof(SubsectionBlock) -
+                                       offset) !=
+                    section.bktr.subsection.size - sizeof(SubsectionBlock)) {
+                    status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
+                    return;
+                }
+
+                std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size());
+                std::transform(relocation_buckets_raw.begin(), relocation_buckets_raw.end(),
+                               relocation_buckets.begin(), &ConvertRelocationBucketRaw);
+                std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size());
+                std::transform(subsection_buckets_raw.begin(), subsection_buckets_raw.end(),
+                               subsection_buckets.begin(), &ConvertSubsectionBucketRaw);
+
+                u32 ctr_low;
+                std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low));
+                subsection_buckets.back().entries.push_back(
+                    {section.bktr.relocation.offset, {0}, ctr_low});
+                subsection_buckets.back().entries.push_back({size, {0}, 0});
+
+                boost::optional<Core::Crypto::Key128> key = boost::none;
+                if (encrypted) {
+                    if (has_rights_id) {
+                        status = Loader::ResultStatus::Success;
+                        key = GetTitlekey();
+                        if (key == boost::none) {
+                            status = Loader::ResultStatus::ErrorMissingTitlekey;
+                            return;
+                        }
+                    } else {
+                        key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
+                        if (key == boost::none) {
+                            status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
+                            return;
+                        }
+                    }
+                }
+
+                if (bktr_base_romfs == nullptr) {
+                    status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
+                    return;
+                }
+
+                auto bktr = std::make_shared<BKTR>(
+                    bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
+                    relocation_block, relocation_buckets, subsection_block, subsection_buckets,
+                    encrypted, encrypted ? key.get() : Core::Crypto::Key128{}, base_offset,
+                    romfs_offset - base_offset, section.raw.section_ctr);
+
+                // BKTR applies to entire IVFC, so make an offset version to level 6
+
+                files.push_back(std::make_shared<OffsetVfsFile>(
+                    bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
+                romfs = files.back();
+            } else {
+                files.push_back(std::move(dec));
+                romfs = files.back();
+                const u64 raw_size =
+                    MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
+                                               header.section_tables[i].media_offset);
+            }
         } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
             u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
                           MEDIA_OFFSET_MULTIPLIER) +
@@ -349,11 +488,17 @@ NCAContentType NCA::GetType() const {
 }
 
 u64 NCA::GetTitleId() const {
+    if (is_update || status == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS)
+        return header.title_id | 0x800;
     if (status != Loader::ResultStatus::Success)
         return {};
     return header.title_id;
 }
 
+bool NCA::IsUpdate() const {
+    return is_update;
+}
+
 VirtualFile NCA::GetRomFS() const {
     return romfs;
 }
@@ -366,10 +511,6 @@ VirtualFile NCA::GetBaseFile() const {
     return file;
 }
 
-bool NCA::IsUpdate() const {
-    return is_update;
-}
-
 bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
     return false;
 }
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index 0ea666cac4..104226f3a1 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -79,7 +79,7 @@ bool IsValidNCA(const NCAHeader& header);
 // After construction, use GetStatus to determine if the file is valid and ready to be used.
 class NCA : public ReadOnlyVfsDirectory {
 public:
-    explicit NCA(VirtualFile file);
+    explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr);
     Loader::ResultStatus GetStatus() const;
 
     std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
@@ -89,14 +89,13 @@ public:
 
     NCAContentType GetType() const;
     u64 GetTitleId() const;
+    bool IsUpdate() const;
 
     VirtualFile GetRomFS() const;
     VirtualDir GetExeFS() const;
 
     VirtualFile GetBaseFile() const;
 
-    bool IsUpdate() const;
-
 protected:
     bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
 
@@ -112,14 +111,15 @@ private:
     VirtualFile romfs = nullptr;
     VirtualDir exefs = nullptr;
     VirtualFile file;
+    VirtualFile bktr_base_romfs;
 
     NCAHeader header{};
     bool has_rights_id{};
-    bool is_update{};
 
     Loader::ResultStatus status{};
 
     bool encrypted;
+    bool is_update;
 
     Core::Crypto::KeyManager keys;
 };
-- 
GitLab