|
|
@@ -0,0 +1,1345 @@
|
|
|
+// 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 meta storage.
|
|
|
+ auto meta_storage = std::make_shared<OffsetVfsFile>(decrypted_storage, meta_size, 0);
|
|
|
+ R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
|
|
|
+
|
|
|
+ // Set the output.
|
|
|
+ *out = std::move(meta_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 an alignment-matching storage.
|
|
|
+ using AlignedStorage = AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>;
|
|
|
+ auto aligned_storage = std::make_shared<AlignedStorage>(std::move(meta_storage));
|
|
|
+ R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
|
|
|
+
|
|
|
+ // Set the output.
|
|
|
+ *out = std::move(aligned_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);
|
|
|
+
|
|
|
+ // Set the output.
|
|
|
+ *out = std::move(meta_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;
|
|
|
+ using AlignedStorage = AlignmentMatchingStoragePooledBuffer<1>;
|
|
|
+
|
|
|
+ // 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));
|
|
|
+
|
|
|
+ // Make the aligned storage.
|
|
|
+ auto aligned_storage = std::make_shared<AlignedStorage>(std::move(verification_storage),
|
|
|
+ hash_data.hash_block_size);
|
|
|
+ R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared);
|
|
|
+
|
|
|
+ // Set the output.
|
|
|
+ *out = std::move(aligned_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
|