diff options
Diffstat (limited to '')
-rw-r--r-- | src/core/file_sys/fssystem/fssystem_nca_header.h | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/src/core/file_sys/fssystem/fssystem_nca_header.h b/src/core/file_sys/fssystem/fssystem_nca_header.h new file mode 100644 index 000000000..a02c5d881 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_header.h @@ -0,0 +1,338 @@ +// 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<u8, Size> value; +}; +static_assert(sizeof(Hash) == Hash::Size); +static_assert(std::is_trivial_v<Hash>); + +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<FsInfo>); + + 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<u8, HeaderSignSize> header_sign_1; + std::array<u8, HeaderSignSize> 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<u8, 2> reserved_222; + std::array<u32, 3> reserved_224; + std::array<u8, RightsIdSize> rights_id; + std::array<FsInfo, FsCountMax> fs_info; + std::array<Hash, FsCountMax> fs_header_hash; + std::array<u8, EncryptedKeyAreaSize> encrypted_key_area; + + static constexpr u64 SectorToByte(u32 sector) { + return static_cast<u64>(sector) << SectorShift; + } + + static constexpr u32 ByteToSector(u64 byte) { + return static_cast<u32>(byte >> SectorShift); + } + + u8 GetProperKeyGeneration() const; +}; +static_assert(sizeof(NcaHeader) == NcaHeader::Size); +static_assert(std::is_trivial_v<NcaHeader>); + +struct NcaBucketInfo { + static constexpr size_t HeaderSize = 0x10; + Int64 offset; + Int64 size; + std::array<u8, HeaderSize> header; +}; +static_assert(std::is_trivial_v<NcaBucketInfo>); + +struct NcaPatchInfo { + static constexpr size_t Size = 0x40; + static constexpr size_t Offset = 0x100; + + Int64 indirect_offset; + Int64 indirect_size; + std::array<u8, NcaBucketInfo::HeaderSize> indirect_header; + Int64 aes_ctr_ex_offset; + Int64 aes_ctr_ex_size; + std::array<u8, NcaBucketInfo::HeaderSize> aes_ctr_ex_header; + + bool HasIndirectTable() const; + bool HasAesCtrExTable() const; +}; +static_assert(std::is_trivial_v<NcaPatchInfo>); + +union NcaAesCtrUpperIv { + u64 value; + struct { + u32 generation; + u32 secure_value; + } part; +}; +static_assert(std::is_trivial_v<NcaAesCtrUpperIv>); + +struct NcaSparseInfo { + NcaBucketInfo bucket; + Int64 physical_offset; + u16 generation; + std::array<u8, 6> reserved; + + s64 GetPhysicalSize() const { + return this->bucket.offset + this->bucket.size; + } + + u32 GetGeneration() const { + return static_cast<u32>(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<NcaSparseInfo>); + +struct NcaCompressionInfo { + NcaBucketInfo bucket; + std::array<u8, 8> resreved; +}; +static_assert(std::is_trivial_v<NcaCompressionInfo>); + +struct NcaMetaDataHashDataInfo { + Int64 offset; + Int64 size; + Hash hash; +}; +static_assert(std::is_trivial_v<NcaMetaDataHashDataInfo>); + +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<Region>); + + 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<Region, HashLayerCountMax> hash_layer_region; + } hierarchical_sha256_data; + static_assert(std::is_trivial_v<HierarchicalSha256Data>); + + 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<u8, 4> reserved; + }; + std::array< + HierarchicalIntegrityVerificationLevelInformation, + HierarchicalIntegrityVerificationLevelInformation::IntegrityMaxLayerCount - 1> + info; + + struct SignatureSalt { + static constexpr size_t Size = 0x20; + std::array<u8, Size> value; + }; + SignatureSalt seed; + } level_hash_info; + + Hash master_hash; + } integrity_meta_info; + static_assert(std::is_trivial_v<IntegrityMetaInfo>); + + std::array<u8, NcaPatchInfo::Offset - HashDataOffset> padding; + }; + + u16 version; + FsType fs_type; + HashType hash_type; + EncryptionType encryption_type; + MetaDataHashType meta_data_hash_type; + std::array<u8, 2> 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<u8, 0x30> 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<NcaFsHeader>); +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<NcaMetaDataHashData>); + +} // namespace FileSys |