diff options
Diffstat (limited to 'src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp')
-rw-r--r-- | src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp | 1351 |
1 files changed, 1351 insertions, 0 deletions
diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp new file mode 100644 index 000000000..0f5432203 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp @@ -0,0 +1,1351 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h" +#include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" +#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" +#include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h" +#include "core/file_sys/fssystem/fssystem_compressed_storage.h" +#include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" +#include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h" +#include "core/file_sys/fssystem/fssystem_indirect_storage.h" +#include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h" +#include "core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h" +#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" +#include "core/file_sys/fssystem/fssystem_sparse_storage.h" +#include "core/file_sys/fssystem/fssystem_switch_storage.h" +#include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs_vector.h" + +namespace FileSys { + +namespace { + +constexpr inline s32 IntegrityDataCacheCount = 24; +constexpr inline s32 IntegrityHashCacheCount = 8; + +constexpr inline s32 IntegrityDataCacheCountForMeta = 16; +constexpr inline s32 IntegrityHashCacheCountForMeta = 2; + +class SharedNcaBodyStorage : public IReadOnlyStorage { + YUZU_NON_COPYABLE(SharedNcaBodyStorage); + YUZU_NON_MOVEABLE(SharedNcaBodyStorage); + +private: + VirtualFile m_storage; + std::shared_ptr<NcaReader> m_nca_reader; + +public: + SharedNcaBodyStorage(VirtualFile s, std::shared_ptr<NcaReader> r) + : m_storage(std::move(s)), m_nca_reader(std::move(r)) {} + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { + // Validate pre-conditions. + ASSERT(m_storage != nullptr); + + // Read from the base storage. + return m_storage->Read(buffer, size, offset); + } + + virtual size_t GetSize() const override { + // Validate pre-conditions. + ASSERT(m_storage != nullptr); + + return m_storage->GetSize(); + } +}; + +inline s64 GetFsOffset(const NcaReader& reader, s32 fs_index) { + return static_cast<s64>(reader.GetFsOffset(fs_index)); +} + +inline s64 GetFsEndOffset(const NcaReader& reader, s32 fs_index) { + return static_cast<s64>(reader.GetFsEndOffset(fs_index)); +} + +using Sha256DataRegion = NcaFsHeader::Region; +using IntegrityLevelInfo = NcaFsHeader::HashData::IntegrityMetaInfo::LevelHashInfo; +using IntegrityDataInfo = IntegrityLevelInfo::HierarchicalIntegrityVerificationLevelInformation; + +} // namespace + +Result NcaFileSystemDriver::OpenStorageWithContext(VirtualFile* out, + NcaFsHeaderReader* out_header_reader, + s32 fs_index, StorageContext* ctx) { + // Open storage. + R_RETURN(this->OpenStorageImpl(out, out_header_reader, fs_index, ctx)); +} + +Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader, + s32 fs_index, StorageContext* ctx) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(out_header_reader != nullptr); + ASSERT(0 <= fs_index && fs_index < NcaHeader::FsCountMax); + + // Validate the fs index. + R_UNLESS(m_reader->HasFsInfo(fs_index), ResultPartitionNotFound); + + // Initialize our header reader for the fs index. + R_TRY(out_header_reader->Initialize(*m_reader, fs_index)); + + // Declare the storage we're opening. + VirtualFile storage; + + // Process sparse layer. + s64 fs_data_offset = 0; + if (out_header_reader->ExistsSparseLayer()) { + // Get the sparse info. + const auto& sparse_info = out_header_reader->GetSparseInfo(); + + // Create based on whether we have a meta hash layer. + if (out_header_reader->ExistsSparseMetaHashLayer()) { + // Create the sparse storage with verification. + R_TRY(this->CreateSparseStorageWithVerification( + std::addressof(storage), std::addressof(fs_data_offset), + ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, + out_header_reader->GetAesCtrUpperIv(), sparse_info, + out_header_reader->GetSparseMetaDataHashDataInfo(), + out_header_reader->GetSparseMetaHashType())); + } else { + // Create the sparse storage. + R_TRY(this->CreateSparseStorage( + std::addressof(storage), std::addressof(fs_data_offset), + ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, + fs_index, out_header_reader->GetAesCtrUpperIv(), sparse_info)); + } + } else { + // Get the data offsets. + fs_data_offset = GetFsOffset(*m_reader, fs_index); + const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index); + + // Validate that we're within range. + const auto data_size = fs_end_offset - fs_data_offset; + R_UNLESS(data_size > 0, ResultInvalidNcaHeader); + + // Create the body substorage. + R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size)); + + // Potentially save the body substorage to our context. + if (ctx != nullptr) { + ctx->body_substorage = storage; + } + } + + // Process patch layer. + const auto& patch_info = out_header_reader->GetPatchInfo(); + VirtualFile patch_meta_aes_ctr_ex_meta_storage; + VirtualFile patch_meta_indirect_meta_storage; + if (out_header_reader->ExistsPatchMetaHashLayer()) { + // Check the meta hash type. + R_UNLESS(out_header_reader->GetPatchMetaHashType() == + NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, + ResultRomNcaInvalidPatchMetaDataHashType); + + // Create the patch meta storage. + R_TRY(this->CreatePatchMetaStorage( + std::addressof(patch_meta_aes_ctr_ex_meta_storage), + std::addressof(patch_meta_indirect_meta_storage), + ctx != nullptr ? std::addressof(ctx->patch_layer_info_storage) : nullptr, storage, + fs_data_offset, out_header_reader->GetAesCtrUpperIv(), patch_info, + out_header_reader->GetPatchMetaDataHashDataInfo())); + } + + if (patch_info.HasAesCtrExTable()) { + // Check the encryption type. + ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::None || + out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx || + out_header_reader->GetEncryptionType() == + NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash); + + // Create the 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 we don't have a meta storage, we must not have a patch meta hash layer. + ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); + + R_TRY(this->CreateAesCtrExStorageMetaStorage( + std::addressof(aes_ctr_ex_storage_meta_storage), storage, fs_data_offset, + out_header_reader->GetEncryptionType(), out_header_reader->GetAesCtrUpperIv(), + patch_info)); + } + + // Create the ex storage. + VirtualFile aes_ctr_ex_storage; + R_TRY(this->CreateAesCtrExStorage( + std::addressof(aes_ctr_ex_storage), + ctx != nullptr ? std::addressof(ctx->aes_ctr_ex_storage) : nullptr, std::move(storage), + aes_ctr_ex_storage_meta_storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(), + patch_info)); + + // Set the base storage as the ex storage. + storage = std::move(aes_ctr_ex_storage); + + // Potentially save storages to our context. + if (ctx != nullptr) { + ctx->aes_ctr_ex_storage_meta_storage = aes_ctr_ex_storage_meta_storage; + ctx->aes_ctr_ex_storage_data_storage = storage; + ctx->fs_data_storage = storage; + } + } 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)); + + // 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. + if (ctx != nullptr) { + ctx->fs_data_storage = storage; + } + } + + // Process indirect layer. + if (patch_info.HasIndirectTable()) { + // Create the indirect meta storage. + VirtualFile indirect_storage_meta_storage = patch_meta_indirect_meta_storage; + if (indirect_storage_meta_storage == nullptr) { + // If we don't have a meta storage, we must not have a patch meta hash layer. + ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); + + R_TRY(this->CreateIndirectStorageMetaStorage( + std::addressof(indirect_storage_meta_storage), storage, patch_info)); + } + + // Potentially save the indirect meta storage to our context. + if (ctx != nullptr) { + ctx->indirect_storage_meta_storage = indirect_storage_meta_storage; + } + + // Get the original indirectable storage. + VirtualFile original_indirectable_storage; + if (m_original_reader != nullptr && m_original_reader->HasFsInfo(fs_index)) { + // Create a driver for the original. + NcaFileSystemDriver original_driver(m_original_reader); + + // Create a header reader for the original. + NcaFsHeaderReader original_header_reader; + R_TRY(original_header_reader.Initialize(*m_original_reader, fs_index)); + + // Open original indirectable storage. + R_TRY(original_driver.OpenIndirectableStorageAsOriginal( + std::addressof(original_indirectable_storage), + std::addressof(original_header_reader), ctx)); + } else if (ctx != nullptr && ctx->external_original_storage != nullptr) { + // Use the external original storage. + original_indirectable_storage = ctx->external_original_storage; + } else { + // Allocate a dummy memory storage as original storage. + original_indirectable_storage = std::make_shared<VectorVfsFile>(); + R_UNLESS(original_indirectable_storage != nullptr, + ResultAllocationMemoryFailedAllocateShared); + } + + // Create the indirect storage. + VirtualFile indirect_storage; + R_TRY(this->CreateIndirectStorage( + std::addressof(indirect_storage), + ctx != nullptr ? std::addressof(ctx->indirect_storage) : nullptr, std::move(storage), + std::move(original_indirectable_storage), std::move(indirect_storage_meta_storage), + patch_info)); + + // Set storage as the indirect storage. + storage = std::move(indirect_storage); + } + + // Check if we're sparse or requested to skip the integrity layer. + if (out_header_reader->ExistsSparseLayer() || (ctx != nullptr && ctx->open_raw_storage)) { + *out = std::move(storage); + R_SUCCEED(); + } + + // Create the non-raw storage. + R_RETURN(this->CreateStorageByRawStorage(out, out_header_reader, std::move(storage), ctx)); +} + +Result NcaFileSystemDriver::CreateStorageByRawStorage(VirtualFile* out, + const NcaFsHeaderReader* header_reader, + VirtualFile raw_storage, + StorageContext* ctx) { + // Initialize storage as raw storage. + VirtualFile storage = std::move(raw_storage); + + // Process hash/integrity layer. + switch (header_reader->GetHashType()) { + case NcaFsHeader::HashType::HierarchicalSha256Hash: + R_TRY(this->CreateSha256Storage(std::addressof(storage), std::move(storage), + header_reader->GetHashData().hierarchical_sha256_data)); + break; + case NcaFsHeader::HashType::HierarchicalIntegrityHash: + R_TRY(this->CreateIntegrityVerificationStorage( + std::addressof(storage), std::move(storage), + header_reader->GetHashData().integrity_meta_info)); + break; + default: + R_THROW(ResultInvalidNcaFsHeaderHashType); + } + + // Process compression layer. + if (header_reader->ExistsCompressionLayer()) { + R_TRY(this->CreateCompressedStorage( + std::addressof(storage), + ctx != nullptr ? std::addressof(ctx->compressed_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->compressed_storage_meta_storage) : nullptr, + std::move(storage), header_reader->GetCompressionInfo())); + } + + // Set output storage. + *out = std::move(storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::OpenIndirectableStorageAsOriginal( + VirtualFile* out, const NcaFsHeaderReader* header_reader, StorageContext* ctx) { + // Get the fs index. + const auto fs_index = header_reader->GetFsIndex(); + + // Declare the storage we're opening. + VirtualFile storage; + + // Process sparse layer. + s64 fs_data_offset = 0; + if (header_reader->ExistsSparseLayer()) { + // Get the sparse info. + const auto& sparse_info = header_reader->GetSparseInfo(); + + // Create based on whether we have a meta hash layer. + if (header_reader->ExistsSparseMetaHashLayer()) { + // Create the sparse storage with verification. + R_TRY(this->CreateSparseStorageWithVerification( + std::addressof(storage), std::addressof(fs_data_offset), + ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, + header_reader->GetAesCtrUpperIv(), sparse_info, + header_reader->GetSparseMetaDataHashDataInfo(), + header_reader->GetSparseMetaHashType())); + } else { + // Create the sparse storage. + R_TRY(this->CreateSparseStorage( + std::addressof(storage), std::addressof(fs_data_offset), + ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, + fs_index, header_reader->GetAesCtrUpperIv(), sparse_info)); + } + } else { + // Get the data offsets. + fs_data_offset = GetFsOffset(*m_reader, fs_index); + const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index); + + // Validate that we're within range. + const auto data_size = fs_end_offset - fs_data_offset; + R_UNLESS(data_size > 0, ResultInvalidNcaHeader); + + // Create the body substorage. + R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size)); + } + + // 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); + } + + // Set output storage. + *out = std::move(storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size) { + // Create the body storage. + auto body_storage = + std::make_shared<SharedNcaBodyStorage>(m_reader->GetSharedBodyStorage(), m_reader); + R_UNLESS(body_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Get the body storage size. + s64 body_size = body_storage->GetSize(); + + // Check that we're within range. + R_UNLESS(offset + size <= body_size, ResultNcaBaseStorageOutOfRangeB); + + // Create substorage. + auto body_substorage = std::make_shared<OffsetVfsFile>(std::move(body_storage), size, offset); + R_UNLESS(body_substorage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output storage. + *out = std::move(body_substorage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateAesCtrStorage( + VirtualFile* out, VirtualFile base_storage, s64 offset, const NcaAesCtrUpperIv& upper_iv, + AlignmentStorageRequirement alignment_storage_requirement) { + // Check pre-conditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + + // Create the iv. + std::array<u8, AesCtrStorage::IvSize> iv{}; + AesCtrStorage::MakeIv(iv.data(), sizeof(iv), upper_iv.value, offset); + + // Create the ctr storage. + VirtualFile aes_ctr_storage; + if (m_reader->HasExternalDecryptionKey()) { + aes_ctr_storage = std::make_shared<AesCtrStorage>( + std::move(base_storage), m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize, + iv.data(), AesCtrStorage::IvSize); + R_UNLESS(aes_ctr_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + } else { + // Create software decryption storage. + auto sw_storage = std::make_shared<AesCtrStorage>( + base_storage, m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr), + AesCtrStorage::KeySize, iv.data(), AesCtrStorage::IvSize); + R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + aes_ctr_storage = std::move(sw_storage); + } + + // Create alignment matching storage. + auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>>( + std::move(aes_ctr_storage)); + R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the out storage. + *out = std::move(aligned_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage, + s64 offset) { + // Check pre-conditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + + // Create the iv. + std::array<u8, AesXtsStorage::IvSize> iv{}; + AesXtsStorage::MakeAesXtsIv(iv.data(), sizeof(iv), offset, NcaHeader::XtsBlockSize); + + // Make the aes xts storage. + const auto* const key1 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts1); + const auto* const key2 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts2); + auto xts_storage = + std::make_shared<AesXtsStorage>(std::move(base_storage), key1, key2, AesXtsStorage::KeySize, + iv.data(), AesXtsStorage::IvSize, NcaHeader::XtsBlockSize); + R_UNLESS(xts_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create alignment matching storage. + auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::XtsBlockSize, 1>>( + std::move(xts_storage)); + R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the out storage. + *out = std::move(xts_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateSparseStorageMetaStorage(VirtualFile* out, + VirtualFile base_storage, s64 offset, + const NcaAesCtrUpperIv& upper_iv, + const NcaSparseInfo& sparse_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + + // Get the base storage size. + s64 base_size = base_storage->GetSize(); + + // Get the meta extents. + const auto meta_offset = sparse_info.bucket.offset; + const auto meta_size = sparse_info.bucket.size; + R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB); + + // Create the encrypted storage. + auto enc_storage = + std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset); + R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the decrypted storage. + VirtualFile decrypted_storage; + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), + offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv), + AlignmentStorageRequirement::None)); + + // Create buffered storage. + std::vector<u8> meta_data(meta_size); + decrypted_storage->Read(meta_data.data(), meta_size, 0); + + auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data)); + R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out = std::move(buffered_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out, + VirtualFile base_storage, s64 base_size, + VirtualFile meta_storage, + const NcaSparseInfo& sparse_info, + bool external_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(meta_storage != nullptr); + + // Read and verify the bucket tree header. + BucketTree::Header header; + std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); + R_TRY(header.Verify()); + + // Determine storage extents. + const auto node_offset = 0; + const auto node_size = SparseStorage::QueryNodeStorageSize(header.entry_count); + const auto entry_offset = node_offset + node_size; + const auto entry_size = SparseStorage::QueryEntryStorageSize(header.entry_count); + + // Create the sparse storage. + auto sparse_storage = std::make_shared<SparseStorage>(); + R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Sanity check that we can be doing this. + ASSERT(header.entry_count != 0); + + // Initialize the sparse storage. + R_TRY(sparse_storage->Initialize( + std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset), + std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset), + header.entry_count)); + + // If not external, set the data storage. + if (!external_info) { + sparse_storage->SetDataStorage( + std::make_shared<OffsetVfsFile>(std::move(base_storage), base_size, 0)); + } + + // Set the output. + *out = std::move(sparse_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset, + std::shared_ptr<SparseStorage>* out_sparse_storage, + VirtualFile* out_meta_storage, s32 index, + const NcaAesCtrUpperIv& upper_iv, + const NcaSparseInfo& sparse_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(out_fs_data_offset != nullptr); + + // Check the sparse info generation. + R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader); + + // Read and verify the bucket tree header. + BucketTree::Header header; + std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); + R_TRY(header.Verify()); + + // Determine the storage extents. + const auto fs_offset = GetFsOffset(*m_reader, index); + const auto fs_end_offset = GetFsEndOffset(*m_reader, index); + const auto fs_size = fs_end_offset - fs_offset; + + // Create the sparse storage. + std::shared_ptr<SparseStorage> sparse_storage; + if (header.entry_count != 0) { + // Create the body substorage. + VirtualFile body_substorage; + R_TRY(this->CreateBodySubStorage(std::addressof(body_substorage), + sparse_info.physical_offset, + sparse_info.GetPhysicalSize())); + + // Create the meta storage. + VirtualFile meta_storage; + R_TRY(this->CreateSparseStorageMetaStorage(std::addressof(meta_storage), body_substorage, + sparse_info.physical_offset, upper_iv, + sparse_info)); + + // Potentially set the output meta storage. + if (out_meta_storage != nullptr) { + *out_meta_storage = meta_storage; + } + + // Create the sparse storage. + R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage, + sparse_info.GetPhysicalSize(), std::move(meta_storage), + sparse_info, false)); + } else { + // If there are no entries, there's nothing to actually do. + sparse_storage = std::make_shared<SparseStorage>(); + R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + sparse_storage->Initialize(fs_size); + } + + // Potentially set the output sparse storage. + if (out_sparse_storage != nullptr) { + *out_sparse_storage = sparse_storage; + } + + // Set the output fs data offset. + *out_fs_data_offset = fs_offset; + + // Set the output storage. + *out = std::move(sparse_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateSparseStorageMetaStorageWithVerification( + VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, + const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, + const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + + // Get the base storage size. + s64 base_size = base_storage->GetSize(); + + // Get the meta extents. + const auto meta_offset = sparse_info.bucket.offset; + const auto meta_size = sparse_info.bucket.size; + R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB); + + // Get the meta data hash data extents. + const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset; + const s64 meta_data_hash_data_size = + Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize); + R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, + ResultNcaBaseStorageOutOfRangeB); + + // Check that the meta is before the hash data. + R_UNLESS(meta_offset + meta_size <= meta_data_hash_data_offset, + ResultRomNcaInvalidSparseMetaDataHashDataOffset); + + // Check that offsets are appropriately aligned. + R_UNLESS(Common::IsAligned<s64>(meta_data_hash_data_offset, NcaHeader::CtrBlockSize), + ResultRomNcaInvalidSparseMetaDataHashDataOffset); + R_UNLESS(Common::IsAligned<s64>(meta_offset, NcaHeader::CtrBlockSize), + ResultInvalidNcaFsHeader); + + // Create the meta storage. + auto enc_storage = std::make_shared<OffsetVfsFile>( + std::move(base_storage), + meta_data_hash_data_offset + meta_data_hash_data_size - meta_offset, meta_offset); + R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the decrypted storage. + VirtualFile decrypted_storage; + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), + offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv), + AlignmentStorageRequirement::None)); + + // Create the verification storage. + VirtualFile integrity_storage; + Result rc = this->CreateIntegrityVerificationStorageForMeta( + std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), + meta_offset, meta_data_hash_data_info); + if (rc == ResultInvalidNcaMetaDataHashDataSize) { + R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataSize); + } + if (rc == ResultInvalidNcaMetaDataHashDataHash) { + R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataHash); + } + R_TRY(rc); + + // Create the meta storage. + auto meta_storage = std::make_shared<OffsetVfsFile>(std::move(integrity_storage), meta_size, 0); + R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out = std::move(meta_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateSparseStorageWithVerification( + VirtualFile* out, s64* out_fs_data_offset, std::shared_ptr<SparseStorage>* out_sparse_storage, + VirtualFile* out_meta_storage, VirtualFile* out_layer_info_storage, s32 index, + const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, + const NcaMetaDataHashDataInfo& meta_data_hash_data_info, + NcaFsHeader::MetaDataHashType meta_data_hash_type) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(out_fs_data_offset != nullptr); + + // Check the sparse info generation. + R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader); + + // Read and verify the bucket tree header. + BucketTree::Header header; + std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); + R_TRY(header.Verify()); + + // Determine the storage extents. + const auto fs_offset = GetFsOffset(*m_reader, index); + const auto fs_end_offset = GetFsEndOffset(*m_reader, index); + const auto fs_size = fs_end_offset - fs_offset; + + // Create the sparse storage. + std::shared_ptr<SparseStorage> sparse_storage; + if (header.entry_count != 0) { + // Create the body substorage. + VirtualFile body_substorage; + R_TRY(this->CreateBodySubStorage( + std::addressof(body_substorage), sparse_info.physical_offset, + Common::AlignUp<s64>(static_cast<s64>(meta_data_hash_data_info.offset) + + static_cast<s64>(meta_data_hash_data_info.size), + NcaHeader::CtrBlockSize))); + + // Check the meta data hash type. + R_UNLESS(meta_data_hash_type == NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, + ResultRomNcaInvalidSparseMetaDataHashType); + + // Create the meta storage. + VirtualFile meta_storage; + R_TRY(this->CreateSparseStorageMetaStorageWithVerification( + std::addressof(meta_storage), out_layer_info_storage, body_substorage, + sparse_info.physical_offset, upper_iv, sparse_info, meta_data_hash_data_info)); + + // Potentially set the output meta storage. + if (out_meta_storage != nullptr) { + *out_meta_storage = meta_storage; + } + + // Create the sparse storage. + R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage, + sparse_info.GetPhysicalSize(), std::move(meta_storage), + sparse_info, false)); + } else { + // If there are no entries, there's nothing to actually do. + sparse_storage = std::make_shared<SparseStorage>(); + R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + sparse_storage->Initialize(fs_size); + } + + // Potentially set the output sparse storage. + if (out_sparse_storage != nullptr) { + *out_sparse_storage = sparse_storage; + } + + // Set the output fs data offset. + *out_fs_data_offset = fs_offset; + + // Set the output storage. + *out = std::move(sparse_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateAesCtrExStorageMetaStorage( + VirtualFile* out, VirtualFile base_storage, s64 offset, + NcaFsHeader::EncryptionType encryption_type, const NcaAesCtrUpperIv& upper_iv, + const NcaPatchInfo& patch_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(encryption_type == NcaFsHeader::EncryptionType::None || + encryption_type == NcaFsHeader::EncryptionType::AesCtrEx || + encryption_type == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash); + ASSERT(patch_info.HasAesCtrExTable()); + + // Validate patch info extents. + R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize); + R_UNLESS(patch_info.aes_ctr_ex_size > 0, ResultInvalidNcaPatchInfoAesCtrExSize); + R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset, + ResultInvalidNcaPatchInfoAesCtrExOffset); + + // Get the base storage size. + s64 base_size = base_storage->GetSize(); + + // Get and validate the meta extents. + const s64 meta_offset = patch_info.aes_ctr_ex_offset; + const s64 meta_size = + Common::AlignUp(static_cast<s64>(patch_info.aes_ctr_ex_size), NcaHeader::XtsBlockSize); + R_UNLESS(meta_offset + meta_size <= base_size, ResultNcaBaseStorageOutOfRangeB); + + // Create the encrypted storage. + auto enc_storage = + std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset); + R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the decrypted storage. + VirtualFile decrypted_storage; + if (encryption_type != NcaFsHeader::EncryptionType::None) { + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), + offset + meta_offset, upper_iv, + AlignmentStorageRequirement::None)); + } else { + // If encryption type is none, don't do any decryption. + decrypted_storage = std::move(enc_storage); + } + + // Create meta storage. + auto meta_storage = std::make_shared<OffsetVfsFile>(decrypted_storage, meta_size, 0); + R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create buffered storage. + std::vector<u8> meta_data(meta_size); + meta_storage->Read(meta_data.data(), meta_size, 0); + + auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data)); + R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out = std::move(buffered_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateAesCtrExStorage( + VirtualFile* out, std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext, + VirtualFile base_storage, VirtualFile meta_storage, s64 counter_offset, + const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info) { + // Validate pre-conditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(meta_storage != nullptr); + ASSERT(patch_info.HasAesCtrExTable()); + + // Read the bucket tree header. + BucketTree::Header header; + std::memcpy(std::addressof(header), patch_info.aes_ctr_ex_header.data(), sizeof(header)); + R_TRY(header.Verify()); + + // Determine the bucket extents. + const auto entry_count = header.entry_count; + const s64 data_offset = 0; + const s64 data_size = patch_info.aes_ctr_ex_offset; + const s64 node_offset = 0; + const s64 node_size = AesCtrCounterExtendedStorage::QueryNodeStorageSize(entry_count); + const s64 entry_offset = node_offset + node_size; + const s64 entry_size = AesCtrCounterExtendedStorage::QueryEntryStorageSize(entry_count); + + // Create bucket storages. + auto data_storage = + std::make_shared<OffsetVfsFile>(std::move(base_storage), data_size, data_offset); + auto node_storage = std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset); + auto entry_storage = std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset); + + // Get the secure value. + const auto secure_value = upper_iv.part.secure_value; + + // Create the aes ctr ex storage. + VirtualFile aes_ctr_ex_storage; + if (m_reader->HasExternalDecryptionKey()) { + // Create the decryptor. + std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> decryptor; + R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(decryptor))); + + // Create the aes ctr ex storage. + auto impl_storage = std::make_shared<AesCtrCounterExtendedStorage>(); + R_UNLESS(impl_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Initialize the aes ctr ex storage. + R_TRY(impl_storage->Initialize(m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize, + secure_value, counter_offset, data_storage, node_storage, + entry_storage, entry_count, std::move(decryptor))); + + // Potentially set the output implementation storage. + if (out_ext != nullptr) { + *out_ext = impl_storage; + } + + // Set the implementation storage. + aes_ctr_ex_storage = std::move(impl_storage); + } else { + // Create the software decryptor. + std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> sw_decryptor; + R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(sw_decryptor))); + + // Make the software storage. + auto sw_storage = std::make_shared<AesCtrCounterExtendedStorage>(); + R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Initialize the software storage. + R_TRY(sw_storage->Initialize(m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr), + AesCtrStorage::KeySize, secure_value, counter_offset, + data_storage, node_storage, entry_storage, entry_count, + std::move(sw_decryptor))); + + // Potentially set the output implementation storage. + if (out_ext != nullptr) { + *out_ext = sw_storage; + } + + // Set the implementation storage. + aes_ctr_ex_storage = std::move(sw_storage); + } + + // Create an alignment-matching storage. + using AlignedStorage = AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>; + auto aligned_storage = std::make_shared<AlignedStorage>(std::move(aes_ctr_ex_storage)); + R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out = std::move(aligned_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateIndirectStorageMetaStorage(VirtualFile* out, + VirtualFile base_storage, + const NcaPatchInfo& patch_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(patch_info.HasIndirectTable()); + + // Get the base storage size. + s64 base_size = base_storage->GetSize(); + + // Check that we're within range. + R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size, + ResultNcaBaseStorageOutOfRangeE); + + // Create the meta storage. + auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, patch_info.indirect_size, + patch_info.indirect_offset); + R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create buffered storage. + std::vector<u8> meta_data(patch_info.indirect_size); + meta_storage->Read(meta_data.data(), patch_info.indirect_size, 0); + + auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data)); + R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out = std::move(buffered_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateIndirectStorage( + VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind, VirtualFile base_storage, + VirtualFile original_data_storage, VirtualFile meta_storage, const NcaPatchInfo& patch_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(meta_storage != nullptr); + ASSERT(patch_info.HasIndirectTable()); + + // Read the bucket tree header. + BucketTree::Header header; + std::memcpy(std::addressof(header), patch_info.indirect_header.data(), sizeof(header)); + R_TRY(header.Verify()); + + // Determine the storage sizes. + const auto node_size = IndirectStorage::QueryNodeStorageSize(header.entry_count); + const auto entry_size = IndirectStorage::QueryEntryStorageSize(header.entry_count); + R_UNLESS(node_size + entry_size <= patch_info.indirect_size, + ResultInvalidNcaIndirectStorageOutOfRange); + + // Get the indirect data size. + const s64 indirect_data_size = patch_info.indirect_offset; + ASSERT(Common::IsAligned(indirect_data_size, NcaHeader::XtsBlockSize)); + + // Create the indirect data storage. + auto indirect_data_storage = + std::make_shared<OffsetVfsFile>(base_storage, indirect_data_size, 0); + R_UNLESS(indirect_data_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the indirect storage. + auto indirect_storage = std::make_shared<IndirectStorage>(); + R_UNLESS(indirect_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Initialize the indirect storage. + R_TRY(indirect_storage->Initialize( + std::make_shared<OffsetVfsFile>(meta_storage, node_size, 0), + std::make_shared<OffsetVfsFile>(meta_storage, entry_size, node_size), header.entry_count)); + + // Get the original data size. + s64 original_data_size = original_data_storage->GetSize(); + + // Set the indirect storages. + indirect_storage->SetStorage( + 0, std::make_shared<OffsetVfsFile>(original_data_storage, original_data_size, 0)); + indirect_storage->SetStorage( + 1, std::make_shared<OffsetVfsFile>(indirect_data_storage, indirect_data_size, 0)); + + // If necessary, set the output indirect storage. + if (out_ind != nullptr) { + *out_ind = indirect_storage; + } + + // Set the output. + *out = std::move(indirect_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreatePatchMetaStorage( + VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta, + VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, + const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info, + const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { + // Validate preconditions. + ASSERT(out_aes_ctr_ex_meta != nullptr); + ASSERT(out_indirect_meta != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(patch_info.HasAesCtrExTable()); + ASSERT(patch_info.HasIndirectTable()); + ASSERT(Common::IsAligned<s64>(patch_info.aes_ctr_ex_size, NcaHeader::XtsBlockSize)); + + // Validate patch info extents. + R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize); + R_UNLESS(patch_info.aes_ctr_ex_size >= 0, ResultInvalidNcaPatchInfoAesCtrExSize); + R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset, + ResultInvalidNcaPatchInfoAesCtrExOffset); + R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= + meta_data_hash_data_info.offset, + ResultRomNcaInvalidPatchMetaDataHashDataOffset); + + // Get the base storage size. + s64 base_size = base_storage->GetSize(); + + // Check that extents remain within range. + R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size, + ResultNcaBaseStorageOutOfRangeE); + R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= base_size, + ResultNcaBaseStorageOutOfRangeB); + + // Check that metadata hash data extents remain within range. + const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset; + const s64 meta_data_hash_data_size = + Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize); + R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, + ResultNcaBaseStorageOutOfRangeB); + + // Create the encrypted storage. + auto enc_storage = std::make_shared<OffsetVfsFile>( + std::move(base_storage), + meta_data_hash_data_offset + meta_data_hash_data_size - patch_info.indirect_offset, + patch_info.indirect_offset); + R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the decrypted storage. + VirtualFile decrypted_storage; + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), + offset + patch_info.indirect_offset, upper_iv, + AlignmentStorageRequirement::None)); + + // Create the verification storage. + VirtualFile integrity_storage; + Result rc = this->CreateIntegrityVerificationStorageForMeta( + std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), + patch_info.indirect_offset, meta_data_hash_data_info); + if (rc == ResultInvalidNcaMetaDataHashDataSize) { + R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataSize); + } + if (rc == ResultInvalidNcaMetaDataHashDataHash) { + R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataHash); + } + R_TRY(rc); + + // Create the indirect meta storage. + auto indirect_meta_storage = + std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.indirect_size, + patch_info.indirect_offset - patch_info.indirect_offset); + R_UNLESS(indirect_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the aes ctr ex meta storage. + auto aes_ctr_ex_meta_storage = + std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.aes_ctr_ex_size, + patch_info.aes_ctr_ex_offset - patch_info.indirect_offset); + R_UNLESS(aes_ctr_ex_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out_aes_ctr_ex_meta = std::move(aes_ctr_ex_meta_storage); + *out_indirect_meta = std::move(indirect_meta_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateSha256Storage( + VirtualFile* out, VirtualFile base_storage, + const NcaFsHeader::HashData::HierarchicalSha256Data& hash_data) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + + // Define storage types. + using VerificationStorage = HierarchicalSha256Storage; + + // Validate the hash data. + R_UNLESS(Common::IsPowerOfTwo(hash_data.hash_block_size), + ResultInvalidHierarchicalSha256BlockSize); + R_UNLESS(hash_data.hash_layer_count == VerificationStorage::LayerCount - 1, + ResultInvalidHierarchicalSha256LayerCount); + + // Get the regions. + const auto& hash_region = hash_data.hash_layer_region[0]; + const auto& data_region = hash_data.hash_layer_region[1]; + + // Determine buffer sizes. + constexpr s32 CacheBlockCount = 2; + const auto hash_buffer_size = static_cast<size_t>(hash_region.size); + const auto cache_buffer_size = CacheBlockCount * hash_data.hash_block_size; + const auto total_buffer_size = hash_buffer_size + cache_buffer_size; + + // Make a buffer holder storage. + auto buffer_hold_storage = std::make_shared<MemoryResourceBufferHoldStorage>( + std::move(base_storage), total_buffer_size); + R_UNLESS(buffer_hold_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + R_UNLESS(buffer_hold_storage->IsValid(), ResultAllocationMemoryFailedInNcaFileSystemDriverI); + + // Get storage size. + s64 base_size = buffer_hold_storage->GetSize(); + + // Check that we're within range. + R_UNLESS(hash_region.offset + hash_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC); + R_UNLESS(data_region.offset + data_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC); + + // Create the master hash storage. + auto master_hash_storage = + std::make_shared<ArrayVfsFile<sizeof(Hash)>>(hash_data.fs_data_master_hash.value); + + // Make the verification storage. + auto verification_storage = std::make_shared<VerificationStorage>(); + R_UNLESS(verification_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Make layer storages. + std::array<VirtualFile, VerificationStorage::LayerCount> layer_storages{ + std::make_shared<OffsetVfsFile>(master_hash_storage, sizeof(Hash), 0), + std::make_shared<OffsetVfsFile>(buffer_hold_storage, hash_region.size, hash_region.offset), + std::make_shared<OffsetVfsFile>(buffer_hold_storage, data_region.size, data_region.offset), + }; + + // Initialize the verification storage. + R_TRY(verification_storage->Initialize(layer_storages.data(), VerificationStorage::LayerCount, + hash_data.hash_block_size, + buffer_hold_storage->GetBuffer(), hash_buffer_size)); + + // Set the output. + *out = std::move(verification_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateIntegrityVerificationStorage( + VirtualFile* out, VirtualFile base_storage, + const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info) { + R_RETURN(this->CreateIntegrityVerificationStorageImpl( + out, base_storage, meta_info, 0, IntegrityDataCacheCount, IntegrityHashCacheCount, + HierarchicalIntegrityVerificationStorage::GetDefaultDataCacheBufferLevel( + meta_info.level_hash_info.max_layers))); +} + +Result NcaFileSystemDriver::CreateIntegrityVerificationStorageForMeta( + VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, + const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { + // Validate preconditions. + ASSERT(out != nullptr); + + // Check the meta data hash data size. + R_UNLESS(meta_data_hash_data_info.size == sizeof(NcaMetaDataHashData), + ResultInvalidNcaMetaDataHashDataSize); + + // Read the meta data hash data. + NcaMetaDataHashData meta_data_hash_data; + base_storage->ReadObject(std::addressof(meta_data_hash_data), + meta_data_hash_data_info.offset - offset); + + // Set the out layer info storage, if necessary. + if (out_layer_info_storage != nullptr) { + auto layer_info_storage = std::make_shared<OffsetVfsFile>( + base_storage, + meta_data_hash_data_info.offset + meta_data_hash_data_info.size - + meta_data_hash_data.layer_info_offset, + meta_data_hash_data.layer_info_offset - offset); + R_UNLESS(layer_info_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + *out_layer_info_storage = std::move(layer_info_storage); + } + + // Create the meta storage. + auto meta_storage = std::make_shared<OffsetVfsFile>( + std::move(base_storage), meta_data_hash_data_info.offset - offset, 0); + R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the integrity verification storage. + R_RETURN(this->CreateIntegrityVerificationStorageImpl( + out, std::move(meta_storage), meta_data_hash_data.integrity_meta_info, + meta_data_hash_data.layer_info_offset - offset, IntegrityDataCacheCountForMeta, + IntegrityHashCacheCountForMeta, 0)); +} + +Result NcaFileSystemDriver::CreateIntegrityVerificationStorageImpl( + VirtualFile* out, VirtualFile base_storage, + const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset, + int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(layer_info_offset >= 0); + + // Define storage types. + using VerificationStorage = HierarchicalIntegrityVerificationStorage; + using StorageInfo = VerificationStorage::HierarchicalStorageInformation; + + // Validate the meta info. + HierarchicalIntegrityVerificationInformation level_hash_info; + std::memcpy(std::addressof(level_hash_info), std::addressof(meta_info.level_hash_info), + sizeof(level_hash_info)); + + R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers, + ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); + R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount, + ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); + + // Get the base storage size. + s64 base_storage_size = base_storage->GetSize(); + + // Create storage info. + StorageInfo storage_info; + for (s32 i = 0; i < static_cast<s32>(level_hash_info.max_layers - 2); ++i) { + const auto& layer_info = level_hash_info.info[i]; + R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size, + ResultNcaBaseStorageOutOfRangeD); + + storage_info[i + 1] = std::make_shared<OffsetVfsFile>( + base_storage, layer_info.size, layer_info_offset + layer_info.offset); + } + + // Set the last layer info. + const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2]; + const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get(); + R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size, + ResultNcaBaseStorageOutOfRangeD); + if (layer_info_offset > 0) { + R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset, + ResultRomNcaInvalidIntegrityLayerInfoOffset); + } + storage_info.SetDataStorage(std::make_shared<OffsetVfsFile>( + std::move(base_storage), layer_info.size, last_layer_info_offset)); + + // Make the integrity romfs storage. + auto integrity_storage = std::make_shared<IntegrityRomFsStorage>(); + R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Initialize the integrity storage. + R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info, + max_data_cache_entries, max_hash_cache_entries, + buffer_level)); + + // Set the output. + *out = std::move(integrity_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateRegionSwitchStorage(VirtualFile* out, + const NcaFsHeaderReader* header_reader, + VirtualFile inside_storage, + VirtualFile outside_storage) { + // Check pre-conditions. + ASSERT(header_reader->GetHashType() == NcaFsHeader::HashType::HierarchicalIntegrityHash); + + // Create the region. + RegionSwitchStorage::Region region = {}; + R_TRY(header_reader->GetHashTargetOffset(std::addressof(region.size))); + + // Create the region switch storage. + auto region_switch_storage = std::make_shared<RegionSwitchStorage>( + std::move(inside_storage), std::move(outside_storage), region); + R_UNLESS(region_switch_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out = std::move(region_switch_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out, + std::shared_ptr<CompressedStorage>* out_cmp, + VirtualFile* out_meta, VirtualFile base_storage, + const NcaCompressionInfo& compression_info) { + R_RETURN(this->CreateCompressedStorage(out, out_cmp, out_meta, std::move(base_storage), + compression_info, m_reader->GetDecompressor())); +} + +Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out, + std::shared_ptr<CompressedStorage>* out_cmp, + VirtualFile* out_meta, VirtualFile base_storage, + const NcaCompressionInfo& compression_info, + GetDecompressorFunction get_decompressor) { + // Check pre-conditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(get_decompressor != nullptr); + + // Read and verify the bucket tree header. + BucketTree::Header header; + std::memcpy(std::addressof(header), compression_info.bucket.header.data(), sizeof(header)); + R_TRY(header.Verify()); + + // Determine the storage extents. + const auto table_offset = compression_info.bucket.offset; + const auto table_size = compression_info.bucket.size; + const auto node_size = CompressedStorage::QueryNodeStorageSize(header.entry_count); + const auto entry_size = CompressedStorage::QueryEntryStorageSize(header.entry_count); + R_UNLESS(node_size + entry_size <= table_size, ResultInvalidCompressedStorageSize); + + // If we should, set the output meta storage. + if (out_meta != nullptr) { + auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, table_size, table_offset); + R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + *out_meta = std::move(meta_storage); + } + + // Allocate the compressed storage. + auto compressed_storage = std::make_shared<CompressedStorage>(); + R_UNLESS(compressed_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Initialize the compressed storage. + R_TRY(compressed_storage->Initialize( + std::make_shared<OffsetVfsFile>(base_storage, table_offset, 0), + std::make_shared<OffsetVfsFile>(base_storage, node_size, table_offset), + std::make_shared<OffsetVfsFile>(base_storage, entry_size, table_offset + node_size), + header.entry_count, 64_KiB, 640_KiB, get_decompressor, 16_KiB, 16_KiB, 32)); + + // Potentially set the output compressed storage. + if (out_cmp) { + *out_cmp = compressed_storage; + } + + // Set the output. + *out = std::move(compressed_storage); + R_SUCCEED(); +} + +} // namespace FileSys |