fix: add missing NCA encryption type support and patch handling

Changes:
- Add missing encryption type cases to all switch statements
- Only call CreatePatchMetaStorage() when both tables are present
- Update assertions to allow partial table combinations

Fixes loading for Hades II, Pokémon Legends: Z-A, and other games
with newer NCA structures.

Reported-by: Dr.Stug@citron-emu.org
Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
Zephyron
2025-10-24 16:25:23 +10:00
parent 7672d2f1ff
commit 20250c3e31

View File

@@ -140,7 +140,7 @@ Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader*
const auto& patch_info = out_header_reader->GetPatchInfo(); const auto& patch_info = out_header_reader->GetPatchInfo();
VirtualFile patch_meta_aes_ctr_ex_meta_storage; VirtualFile patch_meta_aes_ctr_ex_meta_storage;
VirtualFile patch_meta_indirect_meta_storage; VirtualFile patch_meta_indirect_meta_storage;
if (out_header_reader->ExistsPatchMetaHashLayer()) { if (out_header_reader->ExistsPatchMetaHashLayer() && patch_info.HasAesCtrExTable()) {
// Check the meta hash type. // Check the meta hash type.
R_UNLESS(out_header_reader->GetPatchMetaHashType() == R_UNLESS(out_header_reader->GetPatchMetaHashType() ==
NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, NcaFsHeader::MetaDataHashType::HierarchicalIntegrity,
@@ -165,8 +165,8 @@ Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader*
// Create the ex meta storage. // Create the ex meta storage.
VirtualFile aes_ctr_ex_storage_meta_storage = patch_meta_aes_ctr_ex_meta_storage; VirtualFile aes_ctr_ex_storage_meta_storage = patch_meta_aes_ctr_ex_meta_storage;
if (aes_ctr_ex_storage_meta_storage == nullptr) { if (aes_ctr_ex_storage_meta_storage == nullptr) {
// If we don't have a meta storage, we must not have a patch meta hash layer. // If we don't have a meta storage, we must not have a patch meta hash layer with both tables.
ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); ASSERT(!out_header_reader->ExistsPatchMetaHashLayer() || !patch_info.HasIndirectTable());
R_TRY(this->CreateAesCtrExStorageMetaStorage( R_TRY(this->CreateAesCtrExStorageMetaStorage(
std::addressof(aes_ctr_ex_storage_meta_storage), storage, fs_data_offset, std::addressof(aes_ctr_ex_storage_meta_storage), storage, fs_data_offset,
@@ -202,11 +202,13 @@ Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader*
fs_data_offset)); fs_data_offset));
break; break;
case NcaFsHeader::EncryptionType::AesCtr: case NcaFsHeader::EncryptionType::AesCtr:
case NcaFsHeader::EncryptionType::AesCtrEx:
R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage),
fs_data_offset, out_header_reader->GetAesCtrUpperIv(), fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::None)); AlignmentStorageRequirement::None));
break; break;
case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash: { case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash:
case NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash: {
// Create the aes ctr storage. // Create the aes ctr storage.
VirtualFile aes_ctr_storage; VirtualFile aes_ctr_storage;
R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage, R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage,
@@ -232,8 +234,8 @@ Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader*
// Create the indirect meta storage. // Create the indirect meta storage.
VirtualFile indirect_storage_meta_storage = patch_meta_indirect_meta_storage; VirtualFile indirect_storage_meta_storage = patch_meta_indirect_meta_storage;
if (indirect_storage_meta_storage == nullptr) { if (indirect_storage_meta_storage == nullptr) {
// If we don't have a meta storage, we must not have a patch meta hash layer. // If we don't have a meta storage, we must not have a patch meta hash layer with AesCtrEx.
ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); ASSERT(!out_header_reader->ExistsPatchMetaHashLayer() || !patch_info.HasAesCtrExTable());
R_TRY(this->CreateIndirectStorageMetaStorage( R_TRY(this->CreateIndirectStorageMetaStorage(
std::addressof(indirect_storage_meta_storage), storage, patch_info)); std::addressof(indirect_storage_meta_storage), storage, patch_info));
@@ -382,10 +384,33 @@ Result NcaFileSystemDriver::OpenIndirectableStorageAsOriginal(
this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), fs_data_offset)); this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), fs_data_offset));
break; break;
case NcaFsHeader::EncryptionType::AesCtr: case NcaFsHeader::EncryptionType::AesCtr:
case NcaFsHeader::EncryptionType::AesCtrEx:
R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset, R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset,
header_reader->GetAesCtrUpperIv(), header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::CacheBlockSize)); AlignmentStorageRequirement::CacheBlockSize));
break; break;
case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash: {
// Create the aes ctr storage.
VirtualFile aes_ctr_storage;
R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage, fs_data_offset,
header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::CacheBlockSize));
// Create region switch storage.
R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), header_reader,
std::move(storage), std::move(aes_ctr_storage)));
} break;
case NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash: {
// Create the aes ctr ex storage.
VirtualFile aes_ctr_ex_storage;
R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_ex_storage), storage, fs_data_offset,
header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::CacheBlockSize));
// Create region switch storage.
R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), header_reader,
std::move(storage), std::move(aes_ctr_ex_storage)));
} break;
default: default:
R_THROW(ResultInvalidNcaFsHeaderEncryptionType); R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
} }