From 1efe5a76b11a5e7fd8071686acaf1b060e721aaa Mon Sep 17 00:00:00 2001 From: Zach Hilman 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 7cfb6f36b..f0d376bf5 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 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 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(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(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 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 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 relocation_buckets(relocation_buckets_raw.size()); + std::transform(relocation_buckets_raw.begin(), relocation_buckets_raw.end(), + relocation_buckets.begin(), &ConvertRelocationBucketRaw); + std::vector 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 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_base_romfs, std::make_shared(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( + 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(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 0ea666cac..104226f3a 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> 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; };