yuzu/src/core/file_sys/fssystem/fssystem_nca_file_system_dr...

1352 lines
59 KiB
C++

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