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.");
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user