Speed up NCZBLOCK reads

This commit is contained in:
PavelBARABANOV 2026-10-28 00:11:36 +03:00
parent 44cd950747
commit 465b95f353
5 changed files with 135 additions and 80 deletions

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@ -93,6 +93,11 @@ Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader*
// Initialize our header reader for the fs index.
R_TRY(out_header_reader->Initialize(*m_reader, fs_index));
// NCZ files serve already-decrypted body data (CTR sections are stored decrypted).
// Skip the AES layers so we don't decrypt plaintext twice.
const bool is_pre_decrypted =
m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections();
// Declare the storage we're opening.
VirtualFile storage;
@ -158,7 +163,7 @@ Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader*
out_header_reader->GetPatchMetaDataHashDataInfo()));
}
if (patch_info.HasAesCtrExTable()) {
if (patch_info.HasAesCtrExTable() && !is_pre_decrypted) {
// Check the encryption type.
ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::None ||
out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx ||
@ -196,32 +201,34 @@ Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader*
}
} else {
// Create the appropriate storage for the encryption type.
switch (out_header_reader->GetEncryptionType()) {
case NcaFsHeader::EncryptionType::None:
// If there's no encryption, use the base storage we made previously.
break;
case NcaFsHeader::EncryptionType::AesXts:
R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage),
fs_data_offset));
break;
case NcaFsHeader::EncryptionType::AesCtr:
R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage),
fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::None));
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, out_header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::None));
if (!is_pre_decrypted) {
switch (out_header_reader->GetEncryptionType()) {
case NcaFsHeader::EncryptionType::None:
// If there's no encryption, use the base storage we made previously.
break;
case NcaFsHeader::EncryptionType::AesXts:
R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage),
fs_data_offset));
break;
case NcaFsHeader::EncryptionType::AesCtr:
R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage),
fs_data_offset, out_header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::None));
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, out_header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::None));
// Create region switch storage.
R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), out_header_reader,
std::move(storage), std::move(aes_ctr_storage)));
} break;
default:
R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
// Create region switch storage.
R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), out_header_reader,
std::move(storage), std::move(aes_ctr_storage)));
} break;
default:
R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
}
}
// Potentially save storages to our context.
@ -382,21 +389,25 @@ Result NcaFileSystemDriver::OpenIndirectableStorageAsOriginal(
}
// Create the appropriate storage for the encryption type.
switch (header_reader->GetEncryptionType()) {
case NcaFsHeader::EncryptionType::None:
// If there's no encryption, use the base storage we made previously.
break;
case NcaFsHeader::EncryptionType::AesXts:
R_TRY(
this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), fs_data_offset));
break;
case NcaFsHeader::EncryptionType::AesCtr:
R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset,
header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::CacheBlockSize));
break;
default:
R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
const bool is_pre_decrypted =
m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections();
if (!is_pre_decrypted) {
switch (header_reader->GetEncryptionType()) {
case NcaFsHeader::EncryptionType::None:
// If there's no encryption, use the base storage we made previously.
break;
case NcaFsHeader::EncryptionType::AesXts:
R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage),
fs_data_offset));
break;
case NcaFsHeader::EncryptionType::AesCtr:
R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage),
fs_data_offset, header_reader->GetAesCtrUpperIv(),
AlignmentStorageRequirement::CacheBlockSize));
break;
default:
R_THROW(ResultInvalidNcaFsHeaderEncryptionType);
}
}
// Set output storage.
@ -432,6 +443,11 @@ Result NcaFileSystemDriver::CreateAesCtrStorage(
ASSERT(out != nullptr);
ASSERT(base_storage != nullptr);
if (m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections()) {
*out = std::move(base_storage);
R_SUCCEED();
}
// Create the iv.
std::array<u8, AesCtrStorage::IvSize> iv{};
AesCtrStorage::MakeIv(iv.data(), sizeof(iv), upper_iv.value, offset);
@ -469,6 +485,11 @@ Result NcaFileSystemDriver::CreateAesXtsStorage(VirtualFile* out, VirtualFile ba
ASSERT(out != nullptr);
ASSERT(base_storage != nullptr);
if (m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections()) {
*out = std::move(base_storage);
R_SUCCEED();
}
// Create the iv.
std::array<u8, AesXtsStorage::IvSize> iv{};
AesXtsStorage::MakeAesXtsIv(iv.data(), sizeof(iv), offset, NcaHeader::XtsBlockSize);
@ -809,6 +830,12 @@ Result NcaFileSystemDriver::CreateAesCtrExStorageMetaStorage(
// Validate preconditions.
ASSERT(out != nullptr);
ASSERT(base_storage != nullptr);
if (m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections()) {
*out = std::move(base_storage);
R_SUCCEED();
}
ASSERT(encryption_type == NcaFsHeader::EncryptionType::None ||
encryption_type == NcaFsHeader::EncryptionType::AesCtrEx ||
encryption_type == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash);
@ -871,6 +898,11 @@ Result NcaFileSystemDriver::CreateAesCtrExStorage(
ASSERT(meta_storage != nullptr);
ASSERT(patch_info.HasAesCtrExTable());
if (m_reader->GetStorage() && m_reader->GetStorage()->HasDecryptedSections()) {
*out = std::move(base_storage);
R_SUCCEED();
}
// Read the bucket tree header.
BucketTree::Header header;
std::memcpy(std::addressof(header), patch_info.aes_ctr_ex_header.data(), sizeof(header));

View file

@ -104,6 +104,7 @@ public:
const NcaCompressionConfiguration& compression_cfg);
VirtualFile GetSharedBodyStorage();
VirtualFile GetStorage() const { return m_body_storage; }
u32 GetMagic() const;
NcaHeader::DistributionType GetDistributionType() const;
NcaHeader::ContentType GetContentType() const;

View file

@ -112,6 +112,9 @@ public:
// Returns whether or not the file can be read from.
virtual bool IsReadable() const = 0;
// Returns true if this file serves NCA body data that is already decrypted (e.g. NCZ).
virtual bool HasDecryptedSections() const { return false; }
// The primary method of reading from the file. Reads length bytes into data starting at offset
// into file. Returns number of bytes successfully read.
virtual std::size_t Read(u8* data, std::size_t length, std::size_t offset = 0) const = 0;

View file

@ -37,6 +37,10 @@ public:
std::size_t GetOffset() const;
bool HasDecryptedSections() const override {
return file && file->HasDecryptedSections();
}
private:
std::size_t TrimToFit(std::size_t r_size, std::size_t r_offset) const;