Merge pull request #1511 from lioncash/content
content_archive: Minor reorganization changes
This commit is contained in:
		| @@ -97,11 +97,288 @@ union NCASectionHeader { | ||||
| }; | ||||
| static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size."); | ||||
|  | ||||
| bool IsValidNCA(const NCAHeader& header) { | ||||
| static bool IsValidNCA(const NCAHeader& header) { | ||||
|     // TODO(DarkLordZach): Add NCA2/NCA0 support. | ||||
|     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(std::move(bktr_base_romfs_)) { | ||||
|     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::any_of(header.rights_id.begin(), header.rights_id.end(), | ||||
|                                 [](char c) { return c != '\0'; }); | ||||
|  | ||||
|     const std::vector<NCASectionHeader> 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<Core::Crypto::Key256> 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<NCASectionHeader> 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<NCASectionHeader> 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<Core::Crypto::Key256> 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<NCASectionHeader>& 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<OffsetVfsFile>(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<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 false; | ||||
|         } | ||||
|  | ||||
|         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 false; | ||||
|         } | ||||
|  | ||||
|         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 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>( | ||||
|             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, 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<OffsetVfsFile>( | ||||
|             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<u64>(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<OffsetVfsFile>(file, size, offset), offset); | ||||
|     if (dec != nullptr) { | ||||
|         auto npfs = std::make_shared<PartitionFilesystem>(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) | ||||
| @@ -167,7 +444,7 @@ boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() { | ||||
|     return titlekey; | ||||
| } | ||||
|  | ||||
| VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting_offset) { | ||||
| VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) { | ||||
|     if (!encrypted) | ||||
|         return in; | ||||
|  | ||||
| @@ -215,256 +492,6 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting | ||||
|     } | ||||
| } | ||||
|  | ||||
| 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<Core::Crypto::Key256> 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<NCASectionHeader> 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<Core::Crypto::Key256> 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) { | ||||
|         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<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) | ||||
|                     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<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, | ||||
|                     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<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(); | ||||
|             } | ||||
|         } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) { | ||||
|             u64 offset = (static_cast<u64>(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<OffsetVfsFile>(file, size, offset), offset); | ||||
|             if (dec != nullptr) { | ||||
|                 auto npfs = std::make_shared<PartitionFilesystem>(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; | ||||
| } | ||||
|   | ||||
| @@ -73,8 +73,6 @@ inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) { | ||||
|     return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr; | ||||
| } | ||||
|  | ||||
| bool IsValidNCA(const NCAHeader& header); | ||||
|  | ||||
| // An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner. | ||||
| // After construction, use GetStatus to determine if the file is valid and ready to be used. | ||||
| class NCA : public ReadOnlyVfsDirectory { | ||||
| @@ -106,10 +104,19 @@ protected: | ||||
|     bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; | ||||
|  | ||||
| private: | ||||
|     bool CheckSupportedNCA(const NCAHeader& header); | ||||
|     bool HandlePotentialHeaderDecryption(); | ||||
|  | ||||
|     std::vector<NCASectionHeader> ReadSectionHeaders() const; | ||||
|     bool ReadSections(const std::vector<NCASectionHeader>& 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<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const; | ||||
|     boost::optional<Core::Crypto::Key128> GetTitlekey(); | ||||
|     VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset); | ||||
|     VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset); | ||||
|  | ||||
|     std::vector<VirtualDir> dirs; | ||||
|     std::vector<VirtualFile> files; | ||||
| @@ -118,15 +125,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; | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 bunnei
					bunnei