// 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/vfs_offset.h" #include "core/file_sys/vfs/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 m_nca_reader; public: SharedNcaBodyStorage(VirtualFile s, std::shared_ptr 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(reader.GetFsOffset(fs_index)); } inline s64 GetFsEndOffset(const NcaReader& reader, s32 fs_index) { return static_cast(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(); 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(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(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 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( 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( 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>( 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 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(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>( 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(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 meta_data(meta_size); decrypted_storage->Read(meta_data.data(), meta_size, 0); auto buffered_storage = std::make_shared(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* 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(); 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(meta_storage, node_size, node_offset), std::make_shared(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(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* 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 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(); 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(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(meta_data_hash_data_offset, NcaHeader::CtrBlockSize), ResultRomNcaInvalidSparseMetaDataHashDataOffset); R_UNLESS(Common::IsAligned(meta_offset, NcaHeader::CtrBlockSize), ResultInvalidNcaFsHeader); // Create the meta storage. auto enc_storage = std::make_shared( 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(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* 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 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(static_cast(meta_data_hash_data_info.offset) + static_cast(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(); 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(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(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(decrypted_storage, meta_size, 0); R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); // Create buffered storage. std::vector meta_data(meta_size); meta_storage->Read(meta_data.data(), meta_size, 0); auto buffered_storage = std::make_shared(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* 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(std::move(base_storage), data_size, data_offset); auto node_storage = std::make_shared(meta_storage, node_size, node_offset); auto entry_storage = std::make_shared(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 decryptor; R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(decryptor))); // Create the aes ctr ex storage. auto impl_storage = std::make_shared(); 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 sw_decryptor; R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(sw_decryptor))); // Make the software storage. auto sw_storage = std::make_shared(); 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; auto aligned_storage = std::make_shared(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(base_storage, patch_info.indirect_size, patch_info.indirect_offset); R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); // Create buffered storage. std::vector meta_data(patch_info.indirect_size); meta_storage->Read(meta_data.data(), patch_info.indirect_size, 0); auto buffered_storage = std::make_shared(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* 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(base_storage, indirect_data_size, 0); R_UNLESS(indirect_data_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); // Create the indirect storage. auto indirect_storage = std::make_shared(); R_UNLESS(indirect_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); // Initialize the indirect storage. R_TRY(indirect_storage->Initialize( std::make_shared(meta_storage, node_size, 0), std::make_shared(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(original_data_storage, original_data_size, 0)); indirect_storage->SetStorage( 1, std::make_shared(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(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(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( 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(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(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(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( 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>(hash_data.fs_data_master_hash.value); // Make the verification storage. auto verification_storage = std::make_shared(); R_UNLESS(verification_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); // Make layer storages. std::array layer_storages{ std::make_shared(master_hash_storage, sizeof(Hash), 0), std::make_shared(buffer_hold_storage, hash_region.size, hash_region.offset), std::make_shared(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( 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( 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(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( 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( std::move(base_storage), layer_info.size, last_layer_info_offset)); // Make the integrity romfs storage. auto integrity_storage = std::make_shared(); 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( 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* 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* 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(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(); R_UNLESS(compressed_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); // Initialize the compressed storage. R_TRY(compressed_storage->Initialize( std::make_shared(base_storage, table_offset, 0), std::make_shared(base_storage, node_size, table_offset), std::make_shared(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