|
|
@ -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; |
|
|
} |
|
|
} |
|
|
|