|
|
@@ -102,6 +102,284 @@ bool IsValidNCA(const NCAHeader& header) {
|
|
|
return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
|
|
|
}
|
|
|
|
|
|
+NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
|
|
|
+ : file(std::move(file_)),
|
|
|
+ bktr_base_romfs(bktr_base_romfs_ ? std::move(bktr_base_romfs_) : nullptr) {
|
|
|
+ if (file == nullptr) {
|
|
|
+ status = Loader::ResultStatus::ErrorNullFile;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sizeof(NCAHeader) != file->ReadObject(&header)) {
|
|
|
+ LOG_ERROR(Loader, "File reader errored out during header read.");
|
|
|
+ status = Loader::ResultStatus::ErrorBadNCAHeader;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!HandlePotentialHeaderDecryption()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ has_rights_id = std::find_if_not(header.rights_id.begin(), header.rights_id.end(),
|
|
|
+ [](char c) { return c == '\0'; }) != header.rights_id.end();
|
|
|
+
|
|
|
+ const std::vector<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)
|
|
|
@@ -215,256 +493,6 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
|
|
|
- : file(std::move(file_)),
|
|
|
- bktr_base_romfs(bktr_base_romfs_ ? std::move(bktr_base_romfs_) : nullptr) {
|
|
|
- status = Loader::ResultStatus::Success;
|
|
|
-
|
|
|
- if (file == nullptr) {
|
|
|
- status = Loader::ResultStatus::ErrorNullFile;
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (sizeof(NCAHeader) != file->ReadObject(&header)) {
|
|
|
- LOG_ERROR(Loader, "File reader errored out during header read.");
|
|
|
- status = Loader::ResultStatus::ErrorBadNCAHeader;
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- encrypted = false;
|
|
|
-
|
|
|
- if (!IsValidNCA(header)) {
|
|
|
- if (header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
|
|
|
- status = Loader::ResultStatus::ErrorNCA2;
|
|
|
- return;
|
|
|
- }
|
|
|
- if (header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
|
|
|
- status = Loader::ResultStatus::ErrorNCA0;
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- NCAHeader dec_header{};
|
|
|
- Core::Crypto::AESCipher<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) {
|
|
|
- const auto& section = sections[i];
|
|
|
-
|
|
|
- if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
|
|
|
- const std::size_t base_offset =
|
|
|
- header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER;
|
|
|
- ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
|
|
|
- const std::size_t romfs_offset = base_offset + ivfc_offset;
|
|
|
- const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
|
|
|
- auto raw = std::make_shared<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;
|
|
|
}
|