// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "common/common_funcs.h" #include "common/common_types.h" #include "common/literals.h" #include "core/file_sys/errors.h" #include "core/file_sys/fssystem/fs_types.h" namespace FileSys { using namespace Common::Literals; struct Hash { static constexpr std::size_t Size = 256 / 8; std::array value; }; static_assert(sizeof(Hash) == Hash::Size); static_assert(std::is_trivial_v); using NcaDigest = Hash; struct NcaHeader { enum class ContentType : u8 { Program = 0, Meta = 1, Control = 2, Manual = 3, Data = 4, PublicData = 5, Start = Program, End = PublicData, }; enum class DistributionType : u8 { Download = 0, GameCard = 1, Start = Download, End = GameCard, }; enum class EncryptionType : u8 { Auto = 0, None = 1, }; enum DecryptionKey { DecryptionKey_AesXts = 0, DecryptionKey_AesXts1 = DecryptionKey_AesXts, DecryptionKey_AesXts2 = 1, DecryptionKey_AesCtr = 2, DecryptionKey_AesCtrEx = 3, DecryptionKey_AesCtrHw = 4, DecryptionKey_Count, }; struct FsInfo { u32 start_sector; u32 end_sector; u32 hash_sectors; u32 reserved; }; static_assert(sizeof(FsInfo) == 0x10); static_assert(std::is_trivial_v); static constexpr u32 Magic0 = Common::MakeMagic('N', 'C', 'A', '0'); static constexpr u32 Magic1 = Common::MakeMagic('N', 'C', 'A', '1'); static constexpr u32 Magic2 = Common::MakeMagic('N', 'C', 'A', '2'); static constexpr u32 Magic3 = Common::MakeMagic('N', 'C', 'A', '3'); static constexpr u32 Magic = Magic3; static constexpr std::size_t Size = 1_KiB; static constexpr s32 FsCountMax = 4; static constexpr std::size_t HeaderSignCount = 2; static constexpr std::size_t HeaderSignSize = 0x100; static constexpr std::size_t EncryptedKeyAreaSize = 0x100; static constexpr std::size_t SectorSize = 0x200; static constexpr std::size_t SectorShift = 9; static constexpr std::size_t RightsIdSize = 0x10; static constexpr std::size_t XtsBlockSize = 0x200; static constexpr std::size_t CtrBlockSize = 0x10; static_assert(SectorSize == (1 << SectorShift)); // Data members. std::array header_sign_1; std::array header_sign_2; u32 magic; DistributionType distribution_type; ContentType content_type; u8 key_generation; u8 key_index; u64 content_size; u64 program_id; u32 content_index; u32 sdk_addon_version; u8 key_generation_2; u8 header1_signature_key_generation; std::array reserved_222; std::array reserved_224; std::array rights_id; std::array fs_info; std::array fs_header_hash; std::array encrypted_key_area; static constexpr u64 SectorToByte(u32 sector) { return static_cast(sector) << SectorShift; } static constexpr u32 ByteToSector(u64 byte) { return static_cast(byte >> SectorShift); } u8 GetProperKeyGeneration() const; }; static_assert(sizeof(NcaHeader) == NcaHeader::Size); static_assert(std::is_trivial_v); struct NcaBucketInfo { static constexpr size_t HeaderSize = 0x10; Int64 offset; Int64 size; std::array header; }; static_assert(std::is_trivial_v); struct NcaPatchInfo { static constexpr size_t Size = 0x40; static constexpr size_t Offset = 0x100; Int64 indirect_offset; Int64 indirect_size; std::array indirect_header; Int64 aes_ctr_ex_offset; Int64 aes_ctr_ex_size; std::array aes_ctr_ex_header; bool HasIndirectTable() const; bool HasAesCtrExTable() const; }; static_assert(std::is_trivial_v); union NcaAesCtrUpperIv { u64 value; struct { u32 generation; u32 secure_value; } part; }; static_assert(std::is_trivial_v); struct NcaSparseInfo { NcaBucketInfo bucket; Int64 physical_offset; u16 generation; std::array reserved; s64 GetPhysicalSize() const { return this->bucket.offset + this->bucket.size; } u32 GetGeneration() const { return static_cast(this->generation) << 16; } const NcaAesCtrUpperIv MakeAesCtrUpperIv(NcaAesCtrUpperIv upper_iv) const { NcaAesCtrUpperIv sparse_upper_iv = upper_iv; sparse_upper_iv.part.generation = this->GetGeneration(); return sparse_upper_iv; } }; static_assert(std::is_trivial_v); struct NcaCompressionInfo { NcaBucketInfo bucket; std::array resreved; }; static_assert(std::is_trivial_v); struct NcaMetaDataHashDataInfo { Int64 offset; Int64 size; Hash hash; }; static_assert(std::is_trivial_v); struct NcaFsHeader { static constexpr size_t Size = 0x200; static constexpr size_t HashDataOffset = 0x8; struct Region { Int64 offset; Int64 size; }; static_assert(std::is_trivial_v); enum class FsType : u8 { RomFs = 0, PartitionFs = 1, }; enum class EncryptionType : u8 { Auto = 0, None = 1, AesXts = 2, AesCtr = 3, AesCtrEx = 4, AesCtrSkipLayerHash = 5, AesCtrExSkipLayerHash = 6, }; enum class HashType : u8 { Auto = 0, None = 1, HierarchicalSha256Hash = 2, HierarchicalIntegrityHash = 3, AutoSha3 = 4, HierarchicalSha3256Hash = 5, HierarchicalIntegritySha3Hash = 6, }; enum class MetaDataHashType : u8 { None = 0, HierarchicalIntegrity = 1, }; union HashData { struct HierarchicalSha256Data { static constexpr size_t HashLayerCountMax = 5; static const size_t MasterHashOffset; Hash fs_data_master_hash; s32 hash_block_size; s32 hash_layer_count; std::array hash_layer_region; } hierarchical_sha256_data; static_assert(std::is_trivial_v); struct IntegrityMetaInfo { static const size_t MasterHashOffset; u32 magic; u32 version; u32 master_hash_size; struct LevelHashInfo { u32 max_layers; struct HierarchicalIntegrityVerificationLevelInformation { static constexpr size_t IntegrityMaxLayerCount = 7; Int64 offset; Int64 size; s32 block_order; std::array reserved; }; std::array< HierarchicalIntegrityVerificationLevelInformation, HierarchicalIntegrityVerificationLevelInformation::IntegrityMaxLayerCount - 1> info; struct SignatureSalt { static constexpr size_t Size = 0x20; std::array value; }; SignatureSalt seed; } level_hash_info; Hash master_hash; } integrity_meta_info; static_assert(std::is_trivial_v); std::array padding; }; u16 version; FsType fs_type; HashType hash_type; EncryptionType encryption_type; MetaDataHashType meta_data_hash_type; std::array reserved; HashData hash_data; NcaPatchInfo patch_info; NcaAesCtrUpperIv aes_ctr_upper_iv; NcaSparseInfo sparse_info; NcaCompressionInfo compression_info; NcaMetaDataHashDataInfo meta_data_hash_data_info; std::array pad; bool IsSkipLayerHashEncryption() const { return this->encryption_type == EncryptionType::AesCtrSkipLayerHash || this->encryption_type == EncryptionType::AesCtrExSkipLayerHash; } Result GetHashTargetOffset(s64* out) const { switch (this->hash_type) { case HashType::HierarchicalIntegrityHash: case HashType::HierarchicalIntegritySha3Hash: *out = this->hash_data.integrity_meta_info.level_hash_info .info[this->hash_data.integrity_meta_info.level_hash_info.max_layers - 2] .offset; R_SUCCEED(); case HashType::HierarchicalSha256Hash: case HashType::HierarchicalSha3256Hash: *out = this->hash_data.hierarchical_sha256_data .hash_layer_region[this->hash_data.hierarchical_sha256_data.hash_layer_count - 1] .offset; R_SUCCEED(); default: R_THROW(ResultInvalidNcaFsHeader); } } }; static_assert(sizeof(NcaFsHeader) == NcaFsHeader::Size); static_assert(std::is_trivial_v); static_assert(offsetof(NcaFsHeader, patch_info) == NcaPatchInfo::Offset); inline constexpr const size_t NcaFsHeader::HashData::HierarchicalSha256Data::MasterHashOffset = offsetof(NcaFsHeader, hash_data.hierarchical_sha256_data.fs_data_master_hash); inline constexpr const size_t NcaFsHeader::HashData::IntegrityMetaInfo::MasterHashOffset = offsetof(NcaFsHeader, hash_data.integrity_meta_info.master_hash); struct NcaMetaDataHashData { s64 layer_info_offset; NcaFsHeader::HashData::IntegrityMetaInfo integrity_meta_info; }; static_assert(sizeof(NcaMetaDataHashData) == sizeof(NcaFsHeader::HashData::IntegrityMetaInfo) + sizeof(s64)); static_assert(std::is_trivial_v); } // namespace FileSys