Merge pull request #1511 from lioncash/content
content_archive: Minor reorganization changes
This commit is contained in:
		
						commit
						7f152f2273
					
				@ -97,11 +97,288 @@ union NCASectionHeader {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
 | 
					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.
 | 
					    // TODO(DarkLordZach): Add NCA2/NCA0 support.
 | 
				
			||||||
    return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
 | 
					    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 NCA::GetCryptoRevision() const {
 | 
				
			||||||
    u8 master_key_id = header.crypto_type;
 | 
					    u8 master_key_id = header.crypto_type;
 | 
				
			||||||
    if (header.crypto_type_2 > master_key_id)
 | 
					    if (header.crypto_type_2 > master_key_id)
 | 
				
			||||||
@ -167,7 +444,7 @@ boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
 | 
				
			|||||||
    return titlekey;
 | 
					    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)
 | 
					    if (!encrypted)
 | 
				
			||||||
        return in;
 | 
					        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 {
 | 
					Loader::ResultStatus NCA::GetStatus() const {
 | 
				
			||||||
    return status;
 | 
					    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;
 | 
					    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.
 | 
					// 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.
 | 
					// After construction, use GetStatus to determine if the file is valid and ready to be used.
 | 
				
			||||||
class NCA : public ReadOnlyVfsDirectory {
 | 
					class NCA : public ReadOnlyVfsDirectory {
 | 
				
			||||||
@ -106,10 +104,19 @@ protected:
 | 
				
			|||||||
    bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
 | 
					    bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					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;
 | 
					    u8 GetCryptoRevision() const;
 | 
				
			||||||
    boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
 | 
					    boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
 | 
				
			||||||
    boost::optional<Core::Crypto::Key128> GetTitlekey();
 | 
					    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<VirtualDir> dirs;
 | 
				
			||||||
    std::vector<VirtualFile> files;
 | 
					    std::vector<VirtualFile> files;
 | 
				
			||||||
@ -118,15 +125,15 @@ private:
 | 
				
			|||||||
    VirtualDir exefs = nullptr;
 | 
					    VirtualDir exefs = nullptr;
 | 
				
			||||||
    VirtualFile file;
 | 
					    VirtualFile file;
 | 
				
			||||||
    VirtualFile bktr_base_romfs;
 | 
					    VirtualFile bktr_base_romfs;
 | 
				
			||||||
    u64 ivfc_offset;
 | 
					    u64 ivfc_offset = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    NCAHeader header{};
 | 
					    NCAHeader header{};
 | 
				
			||||||
    bool has_rights_id{};
 | 
					    bool has_rights_id{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Loader::ResultStatus status{};
 | 
					    Loader::ResultStatus status{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bool encrypted;
 | 
					    bool encrypted = false;
 | 
				
			||||||
    bool is_update;
 | 
					    bool is_update = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Core::Crypto::KeyManager keys;
 | 
					    Core::Crypto::KeyManager keys;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user