|
|
@ -97,150 +97,66 @@ 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'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
u8 NCA::GetCryptoRevision() const { |
|
|
|
|
|
u8 master_key_id = header.crypto_type; |
|
|
|
|
|
if (header.crypto_type_2 > master_key_id) |
|
|
|
|
|
master_key_id = header.crypto_type_2; |
|
|
|
|
|
if (master_key_id > 0) |
|
|
|
|
|
--master_key_id; |
|
|
|
|
|
return master_key_id; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const { |
|
|
|
|
|
const auto master_key_id = GetCryptoRevision(); |
|
|
|
|
|
|
|
|
|
|
|
if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) |
|
|
|
|
|
return boost::none; |
|
|
|
|
|
|
|
|
|
|
|
std::vector<u8> key_area(header.key_area.begin(), header.key_area.end()); |
|
|
|
|
|
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( |
|
|
|
|
|
keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index), |
|
|
|
|
|
Core::Crypto::Mode::ECB); |
|
|
|
|
|
cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt); |
|
|
|
|
|
|
|
|
|
|
|
Core::Crypto::Key128 out; |
|
|
|
|
|
if (type == NCASectionCryptoType::XTS) |
|
|
|
|
|
std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin()); |
|
|
|
|
|
else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR) |
|
|
|
|
|
std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin()); |
|
|
|
|
|
else |
|
|
|
|
|
LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}", |
|
|
|
|
|
static_cast<u8>(type)); |
|
|
|
|
|
u128 out_128{}; |
|
|
|
|
|
memcpy(out_128.data(), out.data(), 16); |
|
|
|
|
|
LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}", |
|
|
|
|
|
master_key_id, header.key_index, out_128[1], out_128[0]); |
|
|
|
|
|
|
|
|
|
|
|
return out; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() { |
|
|
|
|
|
const auto master_key_id = GetCryptoRevision(); |
|
|
|
|
|
|
|
|
|
|
|
u128 rights_id{}; |
|
|
|
|
|
memcpy(rights_id.data(), header.rights_id.data(), 16); |
|
|
|
|
|
if (rights_id == u128{}) { |
|
|
|
|
|
status = Loader::ResultStatus::ErrorInvalidRightsID; |
|
|
|
|
|
return boost::none; |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]); |
|
|
|
|
|
if (titlekey == Core::Crypto::Key128{}) { |
|
|
|
|
|
status = Loader::ResultStatus::ErrorMissingTitlekey; |
|
|
|
|
|
return boost::none; |
|
|
|
|
|
|
|
|
if (sizeof(NCAHeader) != file->ReadObject(&header)) { |
|
|
|
|
|
LOG_ERROR(Loader, "File reader errored out during header read."); |
|
|
|
|
|
status = Loader::ResultStatus::ErrorBadNCAHeader; |
|
|
|
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) { |
|
|
|
|
|
status = Loader::ResultStatus::ErrorMissingTitlekek; |
|
|
|
|
|
return boost::none; |
|
|
|
|
|
|
|
|
if (!HandlePotentialHeaderDecryption()) { |
|
|
|
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( |
|
|
|
|
|
keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id), Core::Crypto::Mode::ECB); |
|
|
|
|
|
cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), Core::Crypto::Op::Decrypt); |
|
|
|
|
|
|
|
|
|
|
|
return titlekey; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
has_rights_id = std::any_of(header.rights_id.begin(), header.rights_id.end(), |
|
|
|
|
|
[](char c) { return c != '\0'; }); |
|
|
|
|
|
|
|
|
VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting_offset) { |
|
|
|
|
|
if (!encrypted) |
|
|
|
|
|
return in; |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
switch (s_header.raw.header.crypto_type) { |
|
|
|
|
|
case NCASectionCryptoType::NONE: |
|
|
|
|
|
LOG_DEBUG(Crypto, "called with mode=NONE"); |
|
|
|
|
|
return in; |
|
|
|
|
|
case NCASectionCryptoType::CTR: |
|
|
|
|
|
// During normal BKTR decryption, this entire function is skipped. This is for the metadata,
|
|
|
|
|
|
// which uses the same CTR as usual.
|
|
|
|
|
|
case NCASectionCryptoType::BKTR: |
|
|
|
|
|
LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); |
|
|
|
|
|
{ |
|
|
|
|
|
boost::optional<Core::Crypto::Key128> key = boost::none; |
|
|
|
|
|
if (has_rights_id) { |
|
|
|
|
|
status = Loader::ResultStatus::Success; |
|
|
|
|
|
key = GetTitlekey(); |
|
|
|
|
|
if (key == boost::none) { |
|
|
|
|
|
if (status == Loader::ResultStatus::Success) |
|
|
|
|
|
status = Loader::ResultStatus::ErrorMissingTitlekey; |
|
|
|
|
|
return nullptr; |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
key = GetKeyAreaKey(NCASectionCryptoType::CTR); |
|
|
|
|
|
if (key == boost::none) { |
|
|
|
|
|
status = Loader::ResultStatus::ErrorMissingKeyAreaKey; |
|
|
|
|
|
return nullptr; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (!ReadSections(sections, bktr_base_ivfc_offset)) { |
|
|
|
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>( |
|
|
|
|
|
std::move(in), key.value(), starting_offset); |
|
|
|
|
|
std::vector<u8> iv(16); |
|
|
|
|
|
for (u8 i = 0; i < 8; ++i) |
|
|
|
|
|
iv[i] = s_header.raw.section_ctr[0x8 - i - 1]; |
|
|
|
|
|
out->SetIV(iv); |
|
|
|
|
|
return std::static_pointer_cast<VfsFile>(out); |
|
|
|
|
|
} |
|
|
|
|
|
case NCASectionCryptoType::XTS: |
|
|
|
|
|
// TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs
|
|
|
|
|
|
default: |
|
|
|
|
|
LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}", |
|
|
|
|
|
static_cast<u8>(s_header.raw.header.crypto_type)); |
|
|
|
|
|
return nullptr; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
status = Loader::ResultStatus::Success; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
NCA::~NCA() = default; |
|
|
|
|
|
|
|
|
if (file == nullptr) { |
|
|
|
|
|
status = Loader::ResultStatus::ErrorNullFile; |
|
|
|
|
|
return; |
|
|
|
|
|
|
|
|
bool NCA::CheckSupportedNCA(const NCAHeader& nca_header) { |
|
|
|
|
|
if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) { |
|
|
|
|
|
status = Loader::ResultStatus::ErrorNCA2; |
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (sizeof(NCAHeader) != file->ReadObject(&header)) { |
|
|
|
|
|
LOG_ERROR(Loader, "File reader errored out during header read."); |
|
|
|
|
|
status = Loader::ResultStatus::ErrorBadNCAHeader; |
|
|
|
|
|
return; |
|
|
|
|
|
|
|
|
if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) { |
|
|
|
|
|
status = Loader::ResultStatus::ErrorNCA0; |
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
encrypted = false; |
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (!IsValidNCA(header)) { |
|
|
|
|
|
if (header.magic == Common::MakeMagic('N', 'C', 'A', '2')) { |
|
|
|
|
|
status = Loader::ResultStatus::ErrorNCA2; |
|
|
|
|
|
return; |
|
|
|
|
|
|
|
|
bool NCA::HandlePotentialHeaderDecryption() { |
|
|
|
|
|
if (IsValidNCA(header)) { |
|
|
|
|
|
return true; |
|
|
} |
|
|
} |
|
|
if (header.magic == Common::MakeMagic('N', 'C', 'A', '0')) { |
|
|
|
|
|
status = Loader::ResultStatus::ErrorNCA0; |
|
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!CheckSupportedNCA(header)) { |
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
NCAHeader dec_header{}; |
|
|
NCAHeader dec_header{}; |
|
|
@ -252,26 +168,22 @@ NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_off |
|
|
header = dec_header; |
|
|
header = dec_header; |
|
|
encrypted = true; |
|
|
encrypted = true; |
|
|
} else { |
|
|
} 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 (!CheckSupportedNCA(dec_header)) { |
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (!keys.HasKey(Core::Crypto::S256KeyType::Header)) |
|
|
|
|
|
status = Loader::ResultStatus::ErrorMissingHeaderKey; |
|
|
|
|
|
else |
|
|
|
|
|
|
|
|
if (keys.HasKey(Core::Crypto::S256KeyType::Header)) { |
|
|
status = Loader::ResultStatus::ErrorIncorrectHeaderKey; |
|
|
status = Loader::ResultStatus::ErrorIncorrectHeaderKey; |
|
|
return; |
|
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
status = Loader::ResultStatus::ErrorMissingHeaderKey; |
|
|
} |
|
|
} |
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
has_rights_id = std::find_if_not(header.rights_id.begin(), header.rights_id.end(), |
|
|
|
|
|
[](char c) { return c == '\0'; }) != header.rights_id.end(); |
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
std::vector<NCASectionHeader> NCA::ReadSectionHeaders() const { |
|
|
const std::ptrdiff_t number_sections = |
|
|
const std::ptrdiff_t number_sections = |
|
|
std::count_if(std::begin(header.section_tables), std::end(header.section_tables), |
|
|
std::count_if(std::begin(header.section_tables), std::end(header.section_tables), |
|
|
[](NCASectionTableEntry entry) { return entry.media_offset > 0; }); |
|
|
[](NCASectionTableEntry entry) { return entry.media_offset > 0; }); |
|
|
@ -289,17 +201,30 @@ NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_off |
|
|
file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); |
|
|
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; |
|
|
|
|
|
|
|
|
return sections; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
for (std::ptrdiff_t i = 0; i < number_sections; ++i) { |
|
|
|
|
|
auto section = sections[i]; |
|
|
|
|
|
|
|
|
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 (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) { |
|
|
const std::size_t base_offset = |
|
|
|
|
|
header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER; |
|
|
|
|
|
|
|
|
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; |
|
|
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_offset = base_offset + ivfc_offset; |
|
|
const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; |
|
|
const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; |
|
|
@ -308,33 +233,31 @@ NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_off |
|
|
|
|
|
|
|
|
if (dec == nullptr) { |
|
|
if (dec == nullptr) { |
|
|
if (status != Loader::ResultStatus::Success) |
|
|
if (status != Loader::ResultStatus::Success) |
|
|
return; |
|
|
|
|
|
|
|
|
return false; |
|
|
if (has_rights_id) |
|
|
if (has_rights_id) |
|
|
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; |
|
|
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; |
|
|
else |
|
|
else |
|
|
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; |
|
|
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; |
|
|
return; |
|
|
|
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) { |
|
|
if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) { |
|
|
if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') || |
|
|
if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') || |
|
|
section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) { |
|
|
section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) { |
|
|
status = Loader::ResultStatus::ErrorBadBKTRHeader; |
|
|
status = Loader::ResultStatus::ErrorBadBKTRHeader; |
|
|
return; |
|
|
|
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (section.bktr.relocation.offset + section.bktr.relocation.size != |
|
|
if (section.bktr.relocation.offset + section.bktr.relocation.size != |
|
|
section.bktr.subsection.offset) { |
|
|
section.bktr.subsection.offset) { |
|
|
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation; |
|
|
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation; |
|
|
return; |
|
|
|
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const u64 size = |
|
|
|
|
|
MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset - |
|
|
|
|
|
header.section_tables[i].media_offset); |
|
|
|
|
|
|
|
|
const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset); |
|
|
if (section.bktr.subsection.offset + section.bktr.subsection.size != size) { |
|
|
if (section.bktr.subsection.offset + section.bktr.subsection.size != size) { |
|
|
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd; |
|
|
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd; |
|
|
return; |
|
|
|
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; |
|
|
const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; |
|
|
@ -342,37 +265,33 @@ NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_off |
|
|
if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) != |
|
|
if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) != |
|
|
sizeof(RelocationBlock)) { |
|
|
sizeof(RelocationBlock)) { |
|
|
status = Loader::ResultStatus::ErrorBadRelocationBlock; |
|
|
status = Loader::ResultStatus::ErrorBadRelocationBlock; |
|
|
return; |
|
|
|
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
SubsectionBlock subsection_block{}; |
|
|
SubsectionBlock subsection_block{}; |
|
|
if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) != |
|
|
if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) != |
|
|
sizeof(RelocationBlock)) { |
|
|
sizeof(RelocationBlock)) { |
|
|
status = Loader::ResultStatus::ErrorBadSubsectionBlock; |
|
|
status = Loader::ResultStatus::ErrorBadSubsectionBlock; |
|
|
return; |
|
|
|
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
std::vector<RelocationBucketRaw> relocation_buckets_raw( |
|
|
std::vector<RelocationBucketRaw> relocation_buckets_raw( |
|
|
(section.bktr.relocation.size - sizeof(RelocationBlock)) / |
|
|
|
|
|
sizeof(RelocationBucketRaw)); |
|
|
|
|
|
|
|
|
(section.bktr.relocation.size - sizeof(RelocationBlock)) / sizeof(RelocationBucketRaw)); |
|
|
if (dec->ReadBytes(relocation_buckets_raw.data(), |
|
|
if (dec->ReadBytes(relocation_buckets_raw.data(), |
|
|
section.bktr.relocation.size - sizeof(RelocationBlock), |
|
|
section.bktr.relocation.size - sizeof(RelocationBlock), |
|
|
section.bktr.relocation.offset + sizeof(RelocationBlock) - |
|
|
|
|
|
offset) != |
|
|
|
|
|
|
|
|
section.bktr.relocation.offset + sizeof(RelocationBlock) - offset) != |
|
|
section.bktr.relocation.size - sizeof(RelocationBlock)) { |
|
|
section.bktr.relocation.size - sizeof(RelocationBlock)) { |
|
|
status = Loader::ResultStatus::ErrorBadRelocationBuckets; |
|
|
status = Loader::ResultStatus::ErrorBadRelocationBuckets; |
|
|
return; |
|
|
|
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
std::vector<SubsectionBucketRaw> subsection_buckets_raw( |
|
|
std::vector<SubsectionBucketRaw> subsection_buckets_raw( |
|
|
(section.bktr.subsection.size - sizeof(SubsectionBlock)) / |
|
|
|
|
|
sizeof(SubsectionBucketRaw)); |
|
|
|
|
|
|
|
|
(section.bktr.subsection.size - sizeof(SubsectionBlock)) / sizeof(SubsectionBucketRaw)); |
|
|
if (dec->ReadBytes(subsection_buckets_raw.data(), |
|
|
if (dec->ReadBytes(subsection_buckets_raw.data(), |
|
|
section.bktr.subsection.size - sizeof(SubsectionBlock), |
|
|
section.bktr.subsection.size - sizeof(SubsectionBlock), |
|
|
section.bktr.subsection.offset + sizeof(SubsectionBlock) - |
|
|
|
|
|
offset) != |
|
|
|
|
|
|
|
|
section.bktr.subsection.offset + sizeof(SubsectionBlock) - offset) != |
|
|
section.bktr.subsection.size - sizeof(SubsectionBlock)) { |
|
|
section.bktr.subsection.size - sizeof(SubsectionBlock)) { |
|
|
status = Loader::ResultStatus::ErrorBadSubsectionBuckets; |
|
|
status = Loader::ResultStatus::ErrorBadSubsectionBuckets; |
|
|
return; |
|
|
|
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size()); |
|
|
std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size()); |
|
|
@ -384,8 +303,7 @@ NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_off |
|
|
|
|
|
|
|
|
u32 ctr_low; |
|
|
u32 ctr_low; |
|
|
std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(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({section.bktr.relocation.offset, {0}, ctr_low}); |
|
|
subsection_buckets.back().entries.push_back({size, {0}, 0}); |
|
|
subsection_buckets.back().entries.push_back({size, {0}, 0}); |
|
|
|
|
|
|
|
|
boost::optional<Core::Crypto::Key128> key = boost::none; |
|
|
boost::optional<Core::Crypto::Key128> key = boost::none; |
|
|
@ -395,45 +313,45 @@ NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_off |
|
|
key = GetTitlekey(); |
|
|
key = GetTitlekey(); |
|
|
if (key == boost::none) { |
|
|
if (key == boost::none) { |
|
|
status = Loader::ResultStatus::ErrorMissingTitlekey; |
|
|
status = Loader::ResultStatus::ErrorMissingTitlekey; |
|
|
return; |
|
|
|
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
key = GetKeyAreaKey(NCASectionCryptoType::BKTR); |
|
|
key = GetKeyAreaKey(NCASectionCryptoType::BKTR); |
|
|
if (key == boost::none) { |
|
|
if (key == boost::none) { |
|
|
status = Loader::ResultStatus::ErrorMissingKeyAreaKey; |
|
|
status = Loader::ResultStatus::ErrorMissingKeyAreaKey; |
|
|
return; |
|
|
|
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (bktr_base_romfs == nullptr) { |
|
|
if (bktr_base_romfs == nullptr) { |
|
|
status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; |
|
|
status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; |
|
|
return; |
|
|
|
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
auto bktr = std::make_shared<BKTR>( |
|
|
auto bktr = std::make_shared<BKTR>( |
|
|
bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset), |
|
|
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); |
|
|
|
|
|
|
|
|
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
|
|
|
// BKTR applies to entire IVFC, so make an offset version to level 6
|
|
|
|
|
|
|
|
|
files.push_back(std::make_shared<OffsetVfsFile>( |
|
|
files.push_back(std::make_shared<OffsetVfsFile>( |
|
|
bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset)); |
|
|
bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset)); |
|
|
romfs = files.back(); |
|
|
|
|
|
} else { |
|
|
} else { |
|
|
files.push_back(std::move(dec)); |
|
|
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) + |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
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); |
|
|
|
|
|
|
|
|
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) { |
|
|
if (dec != nullptr) { |
|
|
auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec)); |
|
|
auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec)); |
|
|
|
|
|
|
|
|
@ -446,24 +364,133 @@ NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_off |
|
|
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; |
|
|
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; |
|
|
else |
|
|
else |
|
|
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; |
|
|
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; |
|
|
return; |
|
|
|
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
if (status != Loader::ResultStatus::Success) |
|
|
if (status != Loader::ResultStatus::Success) |
|
|
return; |
|
|
|
|
|
|
|
|
return false; |
|
|
if (has_rights_id) |
|
|
if (has_rights_id) |
|
|
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; |
|
|
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; |
|
|
else |
|
|
else |
|
|
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; |
|
|
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; |
|
|
return; |
|
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
u8 NCA::GetCryptoRevision() const { |
|
|
|
|
|
u8 master_key_id = header.crypto_type; |
|
|
|
|
|
if (header.crypto_type_2 > master_key_id) |
|
|
|
|
|
master_key_id = header.crypto_type_2; |
|
|
|
|
|
if (master_key_id > 0) |
|
|
|
|
|
--master_key_id; |
|
|
|
|
|
return master_key_id; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const { |
|
|
|
|
|
const auto master_key_id = GetCryptoRevision(); |
|
|
|
|
|
|
|
|
|
|
|
if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) |
|
|
|
|
|
return boost::none; |
|
|
|
|
|
|
|
|
|
|
|
std::vector<u8> key_area(header.key_area.begin(), header.key_area.end()); |
|
|
|
|
|
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( |
|
|
|
|
|
keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index), |
|
|
|
|
|
Core::Crypto::Mode::ECB); |
|
|
|
|
|
cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt); |
|
|
|
|
|
|
|
|
|
|
|
Core::Crypto::Key128 out; |
|
|
|
|
|
if (type == NCASectionCryptoType::XTS) |
|
|
|
|
|
std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin()); |
|
|
|
|
|
else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR) |
|
|
|
|
|
std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin()); |
|
|
|
|
|
else |
|
|
|
|
|
LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}", |
|
|
|
|
|
static_cast<u8>(type)); |
|
|
|
|
|
u128 out_128{}; |
|
|
|
|
|
memcpy(out_128.data(), out.data(), 16); |
|
|
|
|
|
LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}", |
|
|
|
|
|
master_key_id, header.key_index, out_128[1], out_128[0]); |
|
|
|
|
|
|
|
|
|
|
|
return out; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() { |
|
|
|
|
|
const auto master_key_id = GetCryptoRevision(); |
|
|
|
|
|
|
|
|
|
|
|
u128 rights_id{}; |
|
|
|
|
|
memcpy(rights_id.data(), header.rights_id.data(), 16); |
|
|
|
|
|
if (rights_id == u128{}) { |
|
|
|
|
|
status = Loader::ResultStatus::ErrorInvalidRightsID; |
|
|
|
|
|
return boost::none; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]); |
|
|
|
|
|
if (titlekey == Core::Crypto::Key128{}) { |
|
|
|
|
|
status = Loader::ResultStatus::ErrorMissingTitlekey; |
|
|
|
|
|
return boost::none; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) { |
|
|
|
|
|
status = Loader::ResultStatus::ErrorMissingTitlekek; |
|
|
|
|
|
return boost::none; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
status = Loader::ResultStatus::Success; |
|
|
|
|
|
|
|
|
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( |
|
|
|
|
|
keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id), Core::Crypto::Mode::ECB); |
|
|
|
|
|
cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), Core::Crypto::Op::Decrypt); |
|
|
|
|
|
|
|
|
|
|
|
return titlekey; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
NCA::~NCA() = default; |
|
|
|
|
|
|
|
|
VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) { |
|
|
|
|
|
if (!encrypted) |
|
|
|
|
|
return in; |
|
|
|
|
|
|
|
|
|
|
|
switch (s_header.raw.header.crypto_type) { |
|
|
|
|
|
case NCASectionCryptoType::NONE: |
|
|
|
|
|
LOG_DEBUG(Crypto, "called with mode=NONE"); |
|
|
|
|
|
return in; |
|
|
|
|
|
case NCASectionCryptoType::CTR: |
|
|
|
|
|
// During normal BKTR decryption, this entire function is skipped. This is for the metadata,
|
|
|
|
|
|
// which uses the same CTR as usual.
|
|
|
|
|
|
case NCASectionCryptoType::BKTR: |
|
|
|
|
|
LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); |
|
|
|
|
|
{ |
|
|
|
|
|
boost::optional<Core::Crypto::Key128> key = boost::none; |
|
|
|
|
|
if (has_rights_id) { |
|
|
|
|
|
status = Loader::ResultStatus::Success; |
|
|
|
|
|
key = GetTitlekey(); |
|
|
|
|
|
if (key == boost::none) { |
|
|
|
|
|
if (status == Loader::ResultStatus::Success) |
|
|
|
|
|
status = Loader::ResultStatus::ErrorMissingTitlekey; |
|
|
|
|
|
return nullptr; |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
key = GetKeyAreaKey(NCASectionCryptoType::CTR); |
|
|
|
|
|
if (key == boost::none) { |
|
|
|
|
|
status = Loader::ResultStatus::ErrorMissingKeyAreaKey; |
|
|
|
|
|
return nullptr; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>( |
|
|
|
|
|
std::move(in), key.value(), starting_offset); |
|
|
|
|
|
std::vector<u8> iv(16); |
|
|
|
|
|
for (u8 i = 0; i < 8; ++i) |
|
|
|
|
|
iv[i] = s_header.raw.section_ctr[0x8 - i - 1]; |
|
|
|
|
|
out->SetIV(iv); |
|
|
|
|
|
return std::static_pointer_cast<VfsFile>(out); |
|
|
|
|
|
} |
|
|
|
|
|
case NCASectionCryptoType::XTS: |
|
|
|
|
|
// TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs
|
|
|
|
|
|
default: |
|
|
|
|
|
LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}", |
|
|
|
|
|
static_cast<u8>(s_header.raw.header.crypto_type)); |
|
|
|
|
|
return nullptr; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
Loader::ResultStatus NCA::GetStatus() const { |
|
|
Loader::ResultStatus NCA::GetStatus() const { |
|
|
return status; |
|
|
return status; |
|
|
|