- loading the control data is ran on its own thread, it does not block the main thread. allows for smooth scrolling like nintendos home menu. - on fw20+, sphaira manually parses the control data, rather than using ns. manually parsing takes 20-40ms, which is faster than ms which can take 50-500ms. - on fw19 and below, if the control data is not in ns cache, sphaira will manually parse the data as its twice as fast as ns. You can see how fast this is by loading the gamecard menu as that manually parses everything, and it loads the gamecard faster than the home menu
229 lines
6.1 KiB
C++
229 lines
6.1 KiB
C++
#pragma once
|
|
|
|
#include "fs.hpp"
|
|
#include "keys.hpp"
|
|
#include "ncm.hpp"
|
|
#include <switch.h>
|
|
#include <vector>
|
|
|
|
namespace sphaira::nca {
|
|
|
|
#define NCA0_MAGIC 0x3041434E
|
|
#define NCA2_MAGIC 0x3241434E
|
|
#define NCA3_MAGIC 0x3341434E
|
|
|
|
#define NCA_SECTOR_SIZE 0x200
|
|
#define NCA_XTS_SECTION_SIZE 0xC00
|
|
#define NCA_SECTION_TOTAL 0x4
|
|
#define NCA_MEDIA_REAL(x)((x * 0x200))
|
|
|
|
#define NCA_PROGRAM_LOGO_OFFSET 0x8000
|
|
#define NCA_META_CNMT_OFFSET 0xC20
|
|
|
|
enum KeyGenerationOld {
|
|
KeyGenerationOld_100 = 0x0,
|
|
KeyGenerationOld_Unused = 0x1,
|
|
KeyGenerationOld_300 = 0x2,
|
|
};
|
|
|
|
enum KeyGeneration {
|
|
KeyGeneration_301 = 0x3,
|
|
KeyGeneration_400 = 0x4,
|
|
KeyGeneration_500 = 0x5,
|
|
KeyGeneration_600 = 0x6,
|
|
KeyGeneration_620 = 0x7,
|
|
KeyGeneration_700 = 0x8,
|
|
KeyGeneration_810 = 0x9,
|
|
KeyGeneration_900 = 0x0A,
|
|
KeyGeneration_910 = 0x0B,
|
|
KeyGeneration_1210 = 0x0C,
|
|
KeyGeneration_1300 = 0x0D,
|
|
KeyGeneration_1400 = 0x0E,
|
|
KeyGeneration_1500 = 0x0F,
|
|
KeyGeneration_1600 = 0x10,
|
|
KeyGeneration_1700 = 0x11,
|
|
KeyGeneration_1800 = 0x12,
|
|
KeyGeneration_1900 = 0x13,
|
|
KeyGeneration_2000 = 0x14,
|
|
KeyGeneration_Invalid = 0xFF,
|
|
};
|
|
|
|
enum KeyAreaEncryptionKeyIndex {
|
|
KeyAreaEncryptionKeyIndex_Application = 0x0,
|
|
KeyAreaEncryptionKeyIndex_Ocean = 0x1,
|
|
KeyAreaEncryptionKeyIndex_System = 0x2
|
|
};
|
|
|
|
enum DistributionType {
|
|
DistributionType_System = 0x0,
|
|
DistributionType_GameCard = 0x1
|
|
};
|
|
|
|
enum ContentType {
|
|
ContentType_Program = 0x0,
|
|
ContentType_Meta = 0x1,
|
|
ContentType_Control = 0x2,
|
|
ContentType_Manual = 0x3,
|
|
ContentType_Data = 0x4,
|
|
ContentType_PublicData = 0x5,
|
|
};
|
|
|
|
enum FileSystemType {
|
|
FileSystemType_RomFS = 0x0,
|
|
FileSystemType_PFS0 = 0x1
|
|
};
|
|
|
|
enum HashType {
|
|
HashType_Auto = 0x0,
|
|
HashType_HierarchicalSha256 = 0x2,
|
|
HashType_HierarchicalIntegrity = 0x3
|
|
};
|
|
|
|
enum EncryptionType {
|
|
EncryptionType_Auto = 0x0,
|
|
EncryptionType_None = 0x1,
|
|
EncryptionType_AesXts = 0x2,
|
|
EncryptionType_AesCtr = 0x3,
|
|
EncryptionType_AesCtrEx = 0x4,
|
|
EncryptionType_AesCtrSkipLayerHash = 0x5, // [14.0.0+]
|
|
EncryptionType_AesCtrExSkipLayerHash = 0x6, // [14.0.0+]
|
|
};
|
|
|
|
struct SectionTableEntry {
|
|
u32 media_start_offset; // divided by 0x200.
|
|
u32 media_end_offset; // divided by 0x200.
|
|
u8 _0x8[0x4]; // unknown.
|
|
u8 _0xC[0x4]; // unknown.
|
|
};
|
|
|
|
struct LayerRegion {
|
|
u64 offset;
|
|
u64 size;
|
|
};
|
|
|
|
struct HierarchicalSha256Data {
|
|
u8 master_hash[0x20];
|
|
u32 block_size;
|
|
u32 layer_count;
|
|
LayerRegion hash_layer;
|
|
LayerRegion pfs0_layer;
|
|
LayerRegion unused_layers[3];
|
|
u8 _0x78[0x80];
|
|
};
|
|
|
|
#pragma pack(push, 1)
|
|
struct HierarchicalIntegrityVerificationLevelInformation {
|
|
u64 logical_offset;
|
|
u64 hash_data_size;
|
|
u32 block_size; // log2
|
|
u32 _0x14; // reserved
|
|
};
|
|
#pragma pack(pop)
|
|
|
|
struct InfoLevelHash {
|
|
u32 max_layers;
|
|
HierarchicalIntegrityVerificationLevelInformation levels[6];
|
|
u8 signature_salt[0x20];
|
|
};
|
|
|
|
struct IntegrityMetaInfo {
|
|
u32 magic; // IVFC
|
|
u32 version;
|
|
u32 master_hash_size;
|
|
InfoLevelHash info_level_hash;
|
|
u8 master_hash[0x20];
|
|
u8 _0xE0[0x18];
|
|
};
|
|
|
|
static_assert(sizeof(HierarchicalSha256Data) == 0xF8);
|
|
static_assert(sizeof(IntegrityMetaInfo) == 0xF8);
|
|
static_assert(sizeof(HierarchicalSha256Data) == sizeof(IntegrityMetaInfo));
|
|
|
|
struct FsHeader {
|
|
u16 version; // always 2.
|
|
u8 fs_type; // see FileSystemType.
|
|
u8 hash_type; // see HashType.
|
|
u8 encryption_type; // see EncryptionType.
|
|
u8 metadata_hash_type;
|
|
u8 _0x6[0x2]; // empty.
|
|
|
|
union {
|
|
HierarchicalSha256Data hierarchical_sha256_data;
|
|
IntegrityMetaInfo integrity_meta_info; // used for romfs
|
|
} hash_data;
|
|
|
|
u8 patch_info[0x40];
|
|
u64 section_ctr;
|
|
u8 spares_info[0x30];
|
|
u8 compression_info[0x28];
|
|
u8 meta_data_hash_data_info[0x30];
|
|
u8 reserved[0x30];
|
|
};
|
|
static_assert(sizeof(FsHeader) == 0x200);
|
|
static_assert(sizeof(FsHeader::hash_data) == 0xF8);
|
|
|
|
struct SectionHeaderHash {
|
|
u8 sha256[0x20];
|
|
};
|
|
|
|
struct KeyArea {
|
|
u8 area[0x10];
|
|
};
|
|
|
|
struct Header {
|
|
u8 rsa_fixed_key[0x100];
|
|
u8 rsa_npdm[0x100]; // key from npdm.
|
|
u32 magic;
|
|
u8 distribution_type; // see DistributionType.
|
|
u8 content_type; // see ContentType.
|
|
u8 old_key_gen; // see KeyGenerationOld.
|
|
u8 kaek_index; // see KeyAreaEncryptionKeyIndex.
|
|
u64 size;
|
|
u64 program_id;
|
|
u32 context_id;
|
|
u32 sdk_version;
|
|
u8 key_gen; // see KeyGeneration.
|
|
u8 sig_key_gen;
|
|
u8 _0x222[0xE]; // empty.
|
|
FsRightsId rights_id;
|
|
|
|
SectionTableEntry fs_table[NCA_SECTION_TOTAL];
|
|
SectionHeaderHash fs_header_hash[NCA_SECTION_TOTAL];
|
|
KeyArea key_area[NCA_SECTION_TOTAL];
|
|
|
|
u8 _0x340[0xC0]; // empty.
|
|
|
|
FsHeader fs_header[NCA_SECTION_TOTAL];
|
|
|
|
auto GetKeyGeneration() const -> u8 {
|
|
if (old_key_gen < key_gen) {
|
|
return key_gen;
|
|
} else {
|
|
return old_key_gen;
|
|
}
|
|
}
|
|
|
|
void SetKeyGeneration(u8 key_generation) {
|
|
if (key_generation <= 0x2) {
|
|
old_key_gen = key_generation;
|
|
key_gen = 0;
|
|
} else {
|
|
old_key_gen = 0x2;
|
|
key_gen = key_generation;
|
|
}
|
|
}
|
|
};
|
|
static_assert(sizeof(Header) == 0xC00);
|
|
|
|
Result DecryptKeak(const keys::Keys& keys, Header& header);
|
|
Result EncryptKeak(const keys::Keys& keys, Header& header, u8 key_generation);
|
|
Result VerifyFixedKey(const Header& header);
|
|
|
|
// helpers that parse an nca.
|
|
Result ParseCnmt(const fs::FsPath& path, u64 program_id, ncm::PackagedContentMeta& header, std::vector<u8>& extended_header, std::vector<NcmPackagedContentInfo>& infos);
|
|
Result ParseControl(const fs::FsPath& path, u64 program_id, void* nacp_out = nullptr, s64 nacp_size = 0, std::vector<u8>* icon_out = nullptr);
|
|
|
|
auto GetKeyGenStr(u8 key_gen) -> const char*;
|
|
|
|
} // namespace sphaira::nca
|