From 54e7ddb93a1a0357c14d03aeb182c7c98bb9cebb Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sat, 25 Aug 2018 19:03:03 -0400
Subject: [PATCH] file_sys: Add BKTR patching mechanism

---
 src/core/file_sys/nca_patch.cpp | 208 ++++++++++++++++++++++++++++++++
 src/core/file_sys/nca_patch.h   | 144 ++++++++++++++++++++++
 2 files changed, 352 insertions(+)
 create mode 100644 src/core/file_sys/nca_patch.cpp
 create mode 100644 src/core/file_sys/nca_patch.h

diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp
new file mode 100644
index 0000000000..dd684c38e1
--- /dev/null
+++ b/src/core/file_sys/nca_patch.cpp
@@ -0,0 +1,208 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "core/crypto/aes_util.h"
+#include "core/file_sys/nca_patch.h"
+
+namespace FileSys {
+
+BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_,
+           std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_,
+           std::vector<SubsectionBucket> subsection_buckets_, bool is_encrypted_,
+           Core::Crypto::Key128 key_, u64 base_offset_, u64 ivfc_offset_,
+           std::array<u8, 8> section_ctr_)
+    : base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)),
+      relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)),
+      subsection(subsection_), subsection_buckets(std::move(subsection_buckets_)),
+      encrypted(is_encrypted_), key(key_), base_offset(base_offset_), ivfc_offset(ivfc_offset_),
+      section_ctr(std::move(section_ctr_)) {
+    for (size_t i = 0; i < relocation.number_buckets - 1; ++i) {
+        relocation_buckets[i].entries.push_back({relocation.base_offsets[i + 1], 0, 0});
+    }
+
+    for (size_t i = 0; i < subsection.number_buckets - 1; ++i) {
+        subsection_buckets[i].entries.push_back({subsection_buckets[i + 1].entries[0].address_patch,
+                                                 {0},
+                                                 subsection_buckets[i + 1].entries[0].ctr});
+    }
+
+    relocation_buckets.back().entries.push_back({relocation.size, 0, 0});
+}
+
+size_t BKTR::Read(u8* data, size_t length, size_t offset) const {
+    // Read out of bounds.
+    if (offset >= relocation.size)
+        return 0;
+    const auto relocation = GetRelocationEntry(offset);
+    const auto section_offset = offset - relocation.address_patch + relocation.address_source;
+    const auto bktr_read = relocation.from_patch;
+
+    const auto next_relocation = GetNextRelocationEntry(offset);
+
+    if (offset + length <= next_relocation.address_patch) {
+        if (bktr_read) {
+            if (!encrypted) {
+                return bktr_romfs->Read(data, length, section_offset);
+            }
+
+            const auto subsection = GetSubsectionEntry(section_offset);
+            Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR);
+
+            // Calculate AES IV
+            std::vector<u8> iv(16);
+            auto subsection_ctr = subsection.ctr;
+            auto offset_iv = section_offset + base_offset;
+            for (u8 i = 0; i < 8; ++i)
+                iv[i] = section_ctr[0x8 - i - 1];
+            offset_iv >>= 4;
+            for (size_t i = 0; i < 8; ++i) {
+                iv[0xF - i] = static_cast<u8>(offset_iv & 0xFF);
+                offset_iv >>= 8;
+            }
+            for (size_t i = 0; i < 4; ++i) {
+                iv[0x7 - i] = static_cast<u8>(subsection_ctr & 0xFF);
+                subsection_ctr >>= 8;
+            }
+            cipher.SetIV(iv);
+
+            const auto next_subsection = GetNextSubsectionEntry(section_offset);
+
+            if (section_offset + length <= next_subsection.address_patch) {
+                const auto block_offset = section_offset & 0xF;
+                if (block_offset != 0) {
+                    auto block = bktr_romfs->ReadBytes(0x10, section_offset & ~0xF);
+                    cipher.Transcode(block.data(), block.size(), block.data(),
+                                     Core::Crypto::Op::Decrypt);
+                    if (length + block_offset < 0x10) {
+                        std::memcpy(data, block.data() + block_offset,
+                                    std::min(length, block.size()));
+                        return std::min(length, block.size());
+                    }
+
+                    const auto read = 0x10 - block_offset;
+                    std::memcpy(data, block.data() + block_offset, read);
+                    return read + Read(data + read, length - read, offset + read);
+                }
+
+                const auto raw_read = bktr_romfs->Read(data, length, section_offset);
+                cipher.Transcode(data, raw_read, data, Core::Crypto::Op::Decrypt);
+                return raw_read;
+            } else {
+                const u64 partition = next_subsection.address_patch - section_offset;
+                return Read(data, partition, offset) +
+                       Read(data + partition, length - partition, offset + partition);
+            }
+        } else {
+            ASSERT(section_offset > ivfc_offset, "Offset calculation negative.");
+            return base_romfs->Read(data, length, section_offset);
+        }
+    } else {
+        const u64 partition = next_relocation.address_patch - offset;
+        return Read(data, partition, offset) +
+               Read(data + partition, length - partition, offset + partition);
+    }
+}
+
+template <bool Subsection, typename BlockType, typename BucketType>
+std::pair<size_t, size_t> BKTR::SearchBucketEntry(u64 offset, BlockType block,
+                                                  BucketType buckets) const {
+    if constexpr (Subsection) {
+        const auto last_bucket = buckets[block.number_buckets - 1];
+        if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch)
+            return {block.number_buckets - 1, last_bucket.number_entries};
+    } else {
+        ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block.");
+    }
+
+    size_t bucket_id = 0;
+    for (size_t i = 1; i < block.number_buckets; ++i) {
+        if (block.base_offsets[i] <= offset)
+            ++bucket_id;
+    }
+
+    const auto bucket = buckets[bucket_id];
+
+    if (bucket.number_entries == 1)
+        return {bucket_id, 0};
+
+    size_t low = 0;
+    size_t mid = 0;
+    size_t high = bucket.number_entries - 1;
+    while (low <= high) {
+        mid = (low + high) / 2;
+        if (bucket.entries[mid].address_patch > offset) {
+            high = mid - 1;
+        } else {
+            if (mid == bucket.number_entries - 1 ||
+                bucket.entries[mid + 1].address_patch > offset) {
+                return {bucket_id, mid};
+            }
+
+            low = mid + 1;
+        }
+    }
+
+    UNREACHABLE_MSG("Offset could not be found in BKTR block.");
+}
+
+RelocationEntry BKTR::GetRelocationEntry(u64 offset) const {
+    const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
+    return relocation_buckets[res.first].entries[res.second];
+}
+
+RelocationEntry BKTR::GetNextRelocationEntry(u64 offset) const {
+    const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
+    const auto bucket = relocation_buckets[res.first];
+    if (res.second + 1 < bucket.entries.size())
+        return bucket.entries[res.second + 1];
+    return relocation_buckets[res.first + 1].entries[0];
+}
+
+SubsectionEntry BKTR::GetSubsectionEntry(u64 offset) const {
+    const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets);
+    return subsection_buckets[res.first].entries[res.second];
+}
+
+SubsectionEntry BKTR::GetNextSubsectionEntry(u64 offset) const {
+    const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets);
+    const auto bucket = subsection_buckets[res.first];
+    if (res.second + 1 < bucket.entries.size())
+        return bucket.entries[res.second + 1];
+    return subsection_buckets[res.first + 1].entries[0];
+}
+
+std::string BKTR::GetName() const {
+    return base_romfs->GetName();
+}
+
+size_t BKTR::GetSize() const {
+    return relocation.size;
+}
+
+bool BKTR::Resize(size_t new_size) {
+    return false;
+}
+
+std::shared_ptr<VfsDirectory> BKTR::GetContainingDirectory() const {
+    return base_romfs->GetContainingDirectory();
+}
+
+bool BKTR::IsWritable() const {
+    return false;
+}
+
+bool BKTR::IsReadable() const {
+    return true;
+}
+
+size_t BKTR::Write(const u8* data, size_t length, size_t offset) {
+    return 0;
+}
+
+bool BKTR::Rename(std::string_view name) {
+    return base_romfs->Rename(name);
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/nca_patch.h b/src/core/file_sys/nca_patch.h
new file mode 100644
index 0000000000..8b8d0a4f5f
--- /dev/null
+++ b/src/core/file_sys/nca_patch.h
@@ -0,0 +1,144 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/crypto/key_manager.h"
+#include "core/file_sys/romfs.h"
+#include "core/loader/loader.h"
+
+namespace FileSys {
+
+#pragma pack(push, 1)
+struct RelocationEntry {
+    u64_le address_patch;
+    u64_le address_source;
+    u32 from_patch;
+};
+#pragma pack(pop)
+static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size.");
+
+struct RelocationBucketRaw {
+    INSERT_PADDING_BYTES(4);
+    u32_le number_entries;
+    u64_le end_offset;
+    std::array<RelocationEntry, 0x332> relocation_entries;
+    INSERT_PADDING_BYTES(8);
+};
+static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size.");
+
+// Vector version of RelocationBucketRaw
+struct RelocationBucket {
+    u32 number_entries;
+    u64 end_offset;
+    std::vector<RelocationEntry> entries;
+};
+
+struct RelocationBlock {
+    INSERT_PADDING_BYTES(4);
+    u32_le number_buckets;
+    u64_le size;
+    std::array<u64, 0x7FE> base_offsets;
+};
+static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size.");
+
+struct SubsectionEntry {
+    u64_le address_patch;
+    INSERT_PADDING_BYTES(0x4);
+    u32_le ctr;
+};
+static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size.");
+
+struct SubsectionBucketRaw {
+    INSERT_PADDING_BYTES(4);
+    u32_le number_entries;
+    u64_le end_offset;
+    std::array<SubsectionEntry, 0x3FF> subsection_entries;
+};
+static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size.");
+
+// Vector version of SubsectionBucketRaw
+struct SubsectionBucket {
+    u32 number_entries;
+    u64 end_offset;
+    std::vector<SubsectionEntry> entries;
+};
+
+struct SubsectionBlock {
+    INSERT_PADDING_BYTES(4);
+    u32_le number_buckets;
+    u64_le size;
+    std::array<u64, 0x7FE> base_offsets;
+};
+static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size.");
+
+inline RelocationBucket ConvertRelocationBucketRaw(RelocationBucketRaw raw) {
+    return {raw.number_entries,
+            raw.end_offset,
+            {raw.relocation_entries.begin(), raw.relocation_entries.begin() + raw.number_entries}};
+}
+
+inline SubsectionBucket ConvertSubsectionBucketRaw(SubsectionBucketRaw raw) {
+    return {raw.number_entries,
+            raw.end_offset,
+            {raw.subsection_entries.begin(), raw.subsection_entries.begin() + raw.number_entries}};
+}
+
+class BKTR : public VfsFile {
+public:
+    BKTR(VirtualFile base_romfs, VirtualFile bktr_romfs, RelocationBlock relocation,
+         std::vector<RelocationBucket> relocation_buckets, SubsectionBlock subsection,
+         std::vector<SubsectionBucket> subsection_buckets, bool is_encrypted,
+         Core::Crypto::Key128 key, u64 base_offset, u64 ivfc_offset, std::array<u8, 8> section_ctr);
+
+    size_t Read(u8* data, size_t length, size_t offset) const override;
+
+    std::string GetName() const override;
+
+    size_t GetSize() const override;
+
+    bool Resize(size_t new_size) override;
+
+    std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
+
+    bool IsWritable() const override;
+
+    bool IsReadable() const override;
+
+    size_t Write(const u8* data, size_t length, size_t offset) override;
+
+    bool Rename(std::string_view name) override;
+
+private:
+    template <bool Subsection, typename BlockType, typename BucketType>
+    std::pair<size_t, size_t> SearchBucketEntry(u64 offset, BlockType block,
+                                                BucketType buckets) const;
+
+    RelocationEntry GetRelocationEntry(u64 offset) const;
+    RelocationEntry GetNextRelocationEntry(u64 offset) const;
+
+    SubsectionEntry GetSubsectionEntry(u64 offset) const;
+    SubsectionEntry GetNextSubsectionEntry(u64 offset) const;
+
+    RelocationBlock relocation;
+    std::vector<RelocationBucket> relocation_buckets;
+    SubsectionBlock subsection;
+    std::vector<SubsectionBucket> subsection_buckets;
+
+    // Should be the raw base romfs, decrypted.
+    VirtualFile base_romfs;
+    // Should be the raw BKTR romfs, (located at media_offset with size media_size).
+    VirtualFile bktr_romfs;
+
+    bool encrypted;
+    Core::Crypto::Key128 key;
+
+    // Base offset into NCA, used for IV calculation.
+    u64 base_offset;
+    // Distance between IVFC start and RomFS start, used for base reads
+    u64 ivfc_offset;
+    std::array<u8, 8> section_ctr;
+};
+
+} // namespace FileSys