mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-26 04:02:11 +00:00
nca: SparseInfo support (part 1).
Proper CTR IV and context are generated to decrypt the SparseInfo IndirectBucket. More to come at a later time.
This commit is contained in:
parent
b51dd1674c
commit
8d8f19b229
3 changed files with 61 additions and 10 deletions
|
@ -47,6 +47,7 @@ extern "C" {
|
|||
#define NCA_IVFC_BLOCK_SIZE(x) (1U << (x))
|
||||
|
||||
#define NCA_BKTR_MAGIC 0x424B5452 /* "BKTR". */
|
||||
#define NCA_BKTR_VERSION 1
|
||||
|
||||
#define NCA_FS_SECTOR_SIZE 0x200
|
||||
#define NCA_FS_SECTOR_OFFSET(x) ((u64)(x) * NCA_FS_SECTOR_SIZE)
|
||||
|
@ -248,7 +249,7 @@ NXDT_ASSERT(NcaHashData, 0xF8);
|
|||
|
||||
typedef struct {
|
||||
u32 magic; ///< "BKTR".
|
||||
u32 version; ///< offset_count / node_count ?
|
||||
u32 version; ///< Always NCA_BKTR_VERSION.
|
||||
u32 entry_count;
|
||||
u8 reserved[0x4];
|
||||
} NcaBucketTreeHeader;
|
||||
|
@ -285,7 +286,7 @@ NXDT_ASSERT(NcaAesCtrUpperIv, 0x8);
|
|||
|
||||
/// Used in NCAs with sparse storage.
|
||||
typedef struct {
|
||||
NcaBucketInfo sparse_bucket;
|
||||
NcaBucketInfo bucket;
|
||||
u64 physical_offset;
|
||||
u16 generation;
|
||||
u8 reserved[0x6];
|
||||
|
@ -293,6 +294,13 @@ typedef struct {
|
|||
|
||||
NXDT_ASSERT(NcaSparseInfo, 0x30);
|
||||
|
||||
/// Used in NCAs with LZ4-compressed sections.
|
||||
typedef struct {
|
||||
NcaBucketInfo bucket;
|
||||
} NcaCompressionInfo;
|
||||
|
||||
NXDT_ASSERT(NcaCompressionInfo, 0x20);
|
||||
|
||||
/// Four NCA FS headers are placed right after the 0x400 byte long NCA header in NCA2 and NCA3.
|
||||
/// NCA0 place the FS headers at the start sector from the NcaFsInfo entries.
|
||||
typedef struct {
|
||||
|
@ -305,7 +313,8 @@ typedef struct {
|
|||
NcaPatchInfo patch_info;
|
||||
NcaAesCtrUpperIv aes_ctr_upper_iv;
|
||||
NcaSparseInfo sparse_info;
|
||||
u8 reserved_2[0x88];
|
||||
NcaCompressionInfo compression_info;
|
||||
u8 reserved_2[0x68];
|
||||
} NcaFsHeader;
|
||||
|
||||
NXDT_ASSERT(NcaFsHeader, 0x200);
|
||||
|
@ -336,6 +345,13 @@ typedef struct {
|
|||
Aes128CtrContext ctr_ctx;
|
||||
Aes128XtsContext xts_decrypt_ctx;
|
||||
Aes128XtsContext xts_encrypt_ctx;
|
||||
|
||||
///< SparseInfo-related fields.
|
||||
bool has_sparse_layer;
|
||||
u64 sparse_table_offset; ///< header.sparse_info.physical_offset + header.sparse_info.bucket.offset. Placed here for convenience.
|
||||
u64 sparse_table_size; ///< header.sparse_info.bucket.size. Placed here for convenience.
|
||||
u8 sparse_ctr[AES_BLOCK_SIZE];
|
||||
Aes128CtrContext sparse_ctr_ctx;
|
||||
} NcaFsSectionContext;
|
||||
|
||||
typedef enum {
|
||||
|
@ -380,7 +396,7 @@ typedef struct {
|
|||
///< NSP-related fields.
|
||||
bool header_written; ///< Set to true after the NCA header and the FS section headers have been written to an output dump.
|
||||
void *content_type_ctx; ///< Pointer to a content type context (e.g. ContentMetaContext, ProgramInfoContext, NacpContext, LegalInfoContext). Set to NULL if unused.
|
||||
bool content_type_ctx_patch; ///< Set to true if a NCA patch generated by the content type context is needed and hasn't been completely writen yet.
|
||||
bool content_type_ctx_patch; ///< Set to true if a NCA patch generated by the content type context is needed and hasn't been completely written yet.
|
||||
u32 content_type_ctx_data_idx; ///< Start index for the data generated by the content type context. Used while creating NSPs.
|
||||
} NcaContext;
|
||||
|
||||
|
|
|
@ -43,7 +43,9 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct
|
|||
update_nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs || update_nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx || \
|
||||
base_nca_ctx->header.program_id != update_nca_ctx->header.program_id || base_nca_ctx->header.content_type != update_nca_ctx->header.content_type || \
|
||||
__builtin_bswap32(update_nca_fs_ctx->header.patch_info.indirect_bucket.header.magic) != NCA_BKTR_MAGIC || \
|
||||
update_nca_fs_ctx->header.patch_info.indirect_bucket.header.version != NCA_BKTR_VERSION || \
|
||||
__builtin_bswap32(update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.header.magic) != NCA_BKTR_MAGIC || \
|
||||
update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.header.version != NCA_BKTR_VERSION || \
|
||||
(update_nca_fs_ctx->header.patch_info.indirect_bucket.offset + update_nca_fs_ctx->header.patch_info.indirect_bucket.size) != update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.offset || \
|
||||
(update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.offset + update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.size) != update_nca_fs_ctx->section_size || \
|
||||
(base_nca_ctx->rights_id_available && !base_nca_ctx->titlekey_retrieved) || (update_nca_ctx->rights_id_available && !update_nca_ctx->titlekey_retrieved))
|
||||
|
|
|
@ -169,10 +169,14 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
|
|||
NcaFsSectionContext *fs_ctx = &(out->fs_ctx[i]);
|
||||
u8 *fs_header_hash = out->header.fs_header_hash[i].hash;
|
||||
|
||||
NcaSparseInfo *sparse_info = &(fs_ctx->header.sparse_info);
|
||||
NcaBucketInfo *sparse_bucket = &(sparse_info->bucket);
|
||||
|
||||
/* Fill section context. */
|
||||
fs_ctx->nca_ctx = out;
|
||||
fs_ctx->section_num = i;
|
||||
fs_ctx->section_type = NcaFsSectionType_Invalid; /* Placeholder. */
|
||||
fs_ctx->has_sparse_layer = (sparse_info->generation != 0);
|
||||
|
||||
/* Don't proceed if this NCA FS section isn't populated. */
|
||||
if (!ncaIsFsInfoEntryValid(fs_info)) continue;
|
||||
|
@ -187,9 +191,8 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
|
|||
fs_ctx->section_offset = NCA_FS_SECTOR_OFFSET(fs_info->start_sector);
|
||||
fs_ctx->section_size = (NCA_FS_SECTOR_OFFSET(fs_info->end_sector) - fs_ctx->section_offset);
|
||||
|
||||
/* Check if we're dealing with an invalid offset/size. */
|
||||
if (fs_ctx->section_offset < sizeof(NcaHeader) || !fs_ctx->section_size || \
|
||||
(fs_ctx->section_offset + fs_ctx->section_size) > out->content_size) continue;
|
||||
/* Check if we're dealing with an invalid start offset or an empty size. */
|
||||
if (fs_ctx->section_offset < sizeof(NcaHeader) || !fs_ctx->section_size) continue;
|
||||
|
||||
/* Determine encryption type. */
|
||||
fs_ctx->encryption_type = (out->format_version == NcaVersion_Nca0 ? NcaEncryptionType_AesXts : fs_ctx->header.encryption_type);
|
||||
|
@ -229,6 +232,24 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
|
|||
/* Check if we're dealing with an invalid section type value. */
|
||||
if (fs_ctx->section_type >= NcaFsSectionType_Invalid) continue;
|
||||
|
||||
/* Check if we're dealing with a sparse storage. */
|
||||
if (fs_ctx->has_sparse_layer)
|
||||
{
|
||||
/* Check if the sparse bucket is valid. */
|
||||
u64 raw_storage_offset = sparse_info->physical_offset;
|
||||
u64 raw_storage_size = (sparse_bucket->offset + sparse_bucket->size);
|
||||
|
||||
if (__builtin_bswap32(sparse_bucket->header.magic) != NCA_BKTR_MAGIC || sparse_bucket->header.version != NCA_BKTR_VERSION || raw_storage_offset < sizeof(NcaHeader) || \
|
||||
!raw_storage_size || ((raw_storage_offset + raw_storage_size) > out->content_size) || !sparse_bucket->header.entry_count) continue;
|
||||
|
||||
/* Set sparse table properties. */
|
||||
fs_ctx->sparse_table_offset = (sparse_info->physical_offset + sparse_bucket->offset);
|
||||
fs_ctx->sparse_table_size = sparse_bucket->size;
|
||||
} else {
|
||||
/* Check if we're within boundaries. */
|
||||
if ((fs_ctx->section_offset + fs_ctx->section_size) > out->content_size) continue;
|
||||
}
|
||||
|
||||
/* Initialize crypto data. */
|
||||
if ((!out->rights_id_available || (out->rights_id_available && out->titlekey_retrieved)) && fs_ctx->encryption_type > NcaEncryptionType_None && \
|
||||
fs_ctx->encryption_type <= NcaEncryptionType_AesCtrEx)
|
||||
|
@ -236,11 +257,22 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
|
|||
/* Initialize the partial AES counter for this section. */
|
||||
aes128CtrInitializePartialCtr(fs_ctx->ctr, fs_ctx->header.aes_ctr_upper_iv.value, fs_ctx->section_offset);
|
||||
|
||||
if (fs_ctx->has_sparse_layer)
|
||||
{
|
||||
/* Initialize the partial AES counter for the sparse info bucket table. */
|
||||
NcaAesCtrUpperIv sparse_upper_iv = {0};
|
||||
memcpy(sparse_upper_iv.value, fs_ctx->header.aes_ctr_upper_iv.value, sizeof(sparse_upper_iv.value));
|
||||
sparse_upper_iv.generation = ((u32)(sparse_info->generation) << 16);
|
||||
|
||||
aes128CtrInitializePartialCtr(fs_ctx->sparse_ctr, sparse_upper_iv.value, fs_ctx->sparse_table_offset);
|
||||
}
|
||||
|
||||
/* Initialize AES context. */
|
||||
if (out->rights_id_available)
|
||||
{
|
||||
/* AES-128-CTR is always used for FS crypto in NCAs with a rights ID. */
|
||||
aes128CtrContextCreate(&(fs_ctx->ctr_ctx), out->titlekey, fs_ctx->ctr);
|
||||
if (fs_ctx->has_sparse_layer) aes128CtrContextCreate(&(fs_ctx->sparse_ctr_ctx), out->titlekey, fs_ctx->sparse_ctr);
|
||||
} else {
|
||||
if (fs_ctx->encryption_type == NcaEncryptionType_AesXts)
|
||||
{
|
||||
|
@ -252,6 +284,7 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
|
|||
{
|
||||
/* Patch RomFS sections also use the AES-128-CTR key from the decrypted NCA key area, for some reason. */
|
||||
aes128CtrContextCreate(&(fs_ctx->ctr_ctx), out->decrypted_key_area.aes_ctr, fs_ctx->ctr);
|
||||
if (fs_ctx->has_sparse_layer) aes128CtrContextCreate(&(fs_ctx->sparse_ctr_ctx), out->decrypted_key_area.aes_ctr, fs_ctx->sparse_ctr);
|
||||
} /***else
|
||||
if (fs_ctx->encryption_type == NcaEncryptionType_AesCtr)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue