From d6604fa765890fd7583440362d4e0bdbb82a8215 Mon Sep 17 00:00:00 2001 From: Lioncash Date: Tue, 16 Oct 2018 12:12:50 -0400 Subject: [PATCH] content_archive: Split loading into separate functions The constructor alone is pretty large, the reading code should be split into its consistuent parts to make it easier to understand it without having to build a mental model of a 300+ line function. --- src/core/file_sys/content_archive.cpp | 528 ++++++++++++++------------ src/core/file_sys/content_archive.h | 15 +- 2 files changed, 290 insertions(+), 253 deletions(-) diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 0872a378bc..0f7cc3fbf5 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -102,6 +102,284 @@ bool IsValidNCA(const NCAHeader& header) { return header.magic == Common::MakeMagic('N', 'C', 'A', '3'); } +NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset) + : file(std::move(file_)), + bktr_base_romfs(bktr_base_romfs_ ? std::move(bktr_base_romfs_) : nullptr) { + if (file == nullptr) { + status = Loader::ResultStatus::ErrorNullFile; + return; + } + + if (sizeof(NCAHeader) != file->ReadObject(&header)) { + LOG_ERROR(Loader, "File reader errored out during header read."); + status = Loader::ResultStatus::ErrorBadNCAHeader; + return; + } + + if (!HandlePotentialHeaderDecryption()) { + return; + } + + has_rights_id = std::find_if_not(header.rights_id.begin(), header.rights_id.end(), + [](char c) { return c == '\0'; }) != header.rights_id.end(); + + const std::vector sections = ReadSectionHeaders(); + is_update = std::any_of(sections.begin(), sections.end(), [](const NCASectionHeader& header) { + return header.raw.header.crypto_type == NCASectionCryptoType::BKTR; + }); + + if (!ReadSections(sections, bktr_base_ivfc_offset)) { + return; + } + + status = Loader::ResultStatus::Success; +} + +NCA::~NCA() = default; + +bool NCA::CheckSupportedNCA(const NCAHeader& nca_header) { + if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) { + status = Loader::ResultStatus::ErrorNCA2; + return false; + } + + if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) { + status = Loader::ResultStatus::ErrorNCA0; + return false; + } + + return true; +} + +bool NCA::HandlePotentialHeaderDecryption() { + if (IsValidNCA(header)) { + return true; + } + + if (!CheckSupportedNCA(header)) { + return false; + } + + NCAHeader dec_header{}; + Core::Crypto::AESCipher cipher( + keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); + cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, + Core::Crypto::Op::Decrypt); + if (IsValidNCA(dec_header)) { + header = dec_header; + encrypted = true; + } else { + if (!CheckSupportedNCA(dec_header)) { + return false; + } + + if (keys.HasKey(Core::Crypto::S256KeyType::Header)) { + status = Loader::ResultStatus::ErrorIncorrectHeaderKey; + } else { + status = Loader::ResultStatus::ErrorMissingHeaderKey; + } + return false; + } + + return true; +} + +std::vector NCA::ReadSectionHeaders() const { + const std::ptrdiff_t number_sections = + std::count_if(std::begin(header.section_tables), std::end(header.section_tables), + [](NCASectionTableEntry entry) { return entry.media_offset > 0; }); + + std::vector sections(number_sections); + const auto length_sections = SECTION_HEADER_SIZE * number_sections; + + if (encrypted) { + auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET); + Core::Crypto::AESCipher cipher( + keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); + cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE, + Core::Crypto::Op::Decrypt); + } else { + file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); + } + + return sections; +} + +bool NCA::ReadSections(const std::vector& sections, u64 bktr_base_ivfc_offset) { + for (std::size_t i = 0; i < sections.size(); ++i) { + const auto& section = sections[i]; + + if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) { + if (!ReadRomFSSection(section, header.section_tables[i], bktr_base_ivfc_offset)) { + return false; + } + } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) { + if (!ReadPFS0Section(section, header.section_tables[i])) { + return false; + } + } + } + + return true; +} + +bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry, + u64 bktr_base_ivfc_offset) { + const std::size_t base_offset = entry.media_offset * MEDIA_OFFSET_MULTIPLIER; + ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; + const std::size_t romfs_offset = base_offset + ivfc_offset; + const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; + 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 false; + if (has_rights_id) + status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; + else + status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; + return false; + } + + 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 false; + } + + if (section.bktr.relocation.offset + section.bktr.relocation.size != + section.bktr.subsection.offset) { + status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation; + return false; + } + + const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset); + if (section.bktr.subsection.offset + section.bktr.subsection.size != size) { + status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd; + return false; + } + + 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 false; + } + SubsectionBlock subsection_block{}; + if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) != + sizeof(RelocationBlock)) { + status = Loader::ResultStatus::ErrorBadSubsectionBlock; + return false; + } + + 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 false; + } + + 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 false; + } + + 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 false; + } + } else { + key = GetKeyAreaKey(NCASectionCryptoType::BKTR); + if (key == boost::none) { + status = Loader::ResultStatus::ErrorMissingKeyAreaKey; + return false; + } + } + } + + if (bktr_base_romfs == nullptr) { + status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; + return false; + } + + 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, bktr_base_ivfc_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)); + } else { + files.push_back(std::move(dec)); + } + + romfs = files.back(); + return true; +} + +bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry) { + const u64 offset = (static_cast(entry.media_offset) * MEDIA_OFFSET_MULTIPLIER) + + section.pfs0.pfs0_header_offset; + const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset); + + auto dec = Decrypt(section, std::make_shared(file, size, offset), offset); + if (dec != nullptr) { + auto npfs = std::make_shared(std::move(dec)); + + if (npfs->GetStatus() == Loader::ResultStatus::Success) { + dirs.push_back(std::move(npfs)); + if (IsDirectoryExeFS(dirs.back())) + exefs = dirs.back(); + } else { + if (has_rights_id) + status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; + else + status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; + return false; + } + } else { + if (status != Loader::ResultStatus::Success) + return false; + if (has_rights_id) + status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; + else + status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; + return false; + } + + return true; +} + u8 NCA::GetCryptoRevision() const { u8 master_key_id = header.crypto_type; if (header.crypto_type_2 > master_key_id) @@ -215,256 +493,6 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s } } -NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset) - : file(std::move(file_)), - bktr_base_romfs(bktr_base_romfs_ ? std::move(bktr_base_romfs_) : nullptr) { - status = Loader::ResultStatus::Success; - - if (file == nullptr) { - status = Loader::ResultStatus::ErrorNullFile; - return; - } - - if (sizeof(NCAHeader) != file->ReadObject(&header)) { - LOG_ERROR(Loader, "File reader errored out during header read."); - status = Loader::ResultStatus::ErrorBadNCAHeader; - return; - } - - encrypted = false; - - if (!IsValidNCA(header)) { - if (header.magic == Common::MakeMagic('N', 'C', 'A', '2')) { - status = Loader::ResultStatus::ErrorNCA2; - return; - } - if (header.magic == Common::MakeMagic('N', 'C', 'A', '0')) { - status = Loader::ResultStatus::ErrorNCA0; - return; - } - - NCAHeader dec_header{}; - Core::Crypto::AESCipher cipher( - keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); - cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, - Core::Crypto::Op::Decrypt); - if (IsValidNCA(dec_header)) { - header = dec_header; - encrypted = true; - } else { - if (dec_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) { - status = Loader::ResultStatus::ErrorNCA2; - return; - } - if (dec_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) { - status = Loader::ResultStatus::ErrorNCA0; - return; - } - - if (!keys.HasKey(Core::Crypto::S256KeyType::Header)) - status = Loader::ResultStatus::ErrorMissingHeaderKey; - else - status = Loader::ResultStatus::ErrorIncorrectHeaderKey; - return; - } - } - - has_rights_id = std::find_if_not(header.rights_id.begin(), header.rights_id.end(), - [](char c) { return c == '\0'; }) != header.rights_id.end(); - - const std::ptrdiff_t number_sections = - std::count_if(std::begin(header.section_tables), std::end(header.section_tables), - [](NCASectionTableEntry entry) { return entry.media_offset > 0; }); - - std::vector sections(number_sections); - const auto length_sections = SECTION_HEADER_SIZE * number_sections; - - if (encrypted) { - auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET); - Core::Crypto::AESCipher cipher( - keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); - cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE, - Core::Crypto::Op::Decrypt); - } else { - file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); - } - - is_update = std::find_if(sections.begin(), sections.end(), [](const NCASectionHeader& header) { - return header.raw.header.crypto_type == NCASectionCryptoType::BKTR; - }) != sections.end(); - ivfc_offset = 0; - - for (std::ptrdiff_t i = 0; i < number_sections; ++i) { - const auto& section = sections[i]; - - if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) { - const std::size_t base_offset = - header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER; - ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; - const std::size_t romfs_offset = base_offset + ivfc_offset; - const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; - 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) - status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; - else - 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, - bktr_base_ivfc_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(); - } - } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) { - u64 offset = (static_cast(header.section_tables[i].media_offset) * - MEDIA_OFFSET_MULTIPLIER) + - section.pfs0.pfs0_header_offset; - u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset - - header.section_tables[i].media_offset); - auto dec = - Decrypt(section, std::make_shared(file, size, offset), offset); - if (dec != nullptr) { - auto npfs = std::make_shared(std::move(dec)); - - if (npfs->GetStatus() == Loader::ResultStatus::Success) { - dirs.push_back(std::move(npfs)); - if (IsDirectoryExeFS(dirs.back())) - exefs = dirs.back(); - } else { - if (has_rights_id) - status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; - else - status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; - return; - } - } else { - if (status != Loader::ResultStatus::Success) - return; - if (has_rights_id) - status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; - else - status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; - return; - } - } - } - - status = Loader::ResultStatus::Success; -} - -NCA::~NCA() = default; - Loader::ResultStatus NCA::GetStatus() const { return status; } diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index d02ea4f4b1..e5d3d3c6ab 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -106,6 +106,15 @@ protected: bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; private: + bool CheckSupportedNCA(const NCAHeader& header); + bool HandlePotentialHeaderDecryption(); + + std::vector ReadSectionHeaders() const; + bool ReadSections(const std::vector& sections, u64 bktr_base_ivfc_offset); + bool ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry, + u64 bktr_base_ivfc_offset); + bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry); + u8 GetCryptoRevision() const; boost::optional GetKeyAreaKey(NCASectionCryptoType type) const; boost::optional GetTitlekey(); @@ -118,15 +127,15 @@ private: VirtualDir exefs = nullptr; VirtualFile file; VirtualFile bktr_base_romfs; - u64 ivfc_offset; + u64 ivfc_offset = 0; NCAHeader header{}; bool has_rights_id{}; Loader::ResultStatus status{}; - bool encrypted; - bool is_update; + bool encrypted = false; + bool is_update = false; Core::Crypto::KeyManager keys; };