From 8d81528619fdd88859defca6468578056eb246cd Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Thu, 30 Jun 2022 18:46:45 +0200 Subject: [PATCH] Add support for sparse NCAs. --- include/core/nca.h | 3 +- source/core/bktr.c | 81 +++++++++++++++++++++++++++++++++++----------- source/core/nca.c | 26 ++++++++------- 3 files changed, 78 insertions(+), 32 deletions(-) diff --git a/include/core/nca.h b/include/core/nca.h index d22bbee..edcc3b0 100644 --- a/include/core/nca.h +++ b/include/core/nca.h @@ -373,7 +373,7 @@ typedef struct { u8 encryption_type; ///< NcaEncryptionType. u8 section_type; ///< NcaFsSectionType. bool skip_hash_layer_crypto; ///< Set to true if hash layer decryption should be skipped while reading section data. - NcaRegion hash_region; /// Holds the properties for the full hash layer region that precedes the actual FS section data. + NcaRegion hash_region; ///< Holds the properties for the full hash layer region that precedes the actual FS section data. ///< Crypto-related fields. u8 ctr[AES_BLOCK_SIZE]; ///< Used internally by NCA functions to update the AES-128-CTR context IV based on the desired offset. @@ -387,6 +387,7 @@ typedef struct { u64 sparse_table_size; ///< header.sparse_info.bucket.size. Placed here for convenience. u8 sparse_ctr[AES_BLOCK_SIZE]; ///< AES-128-CTR IV used for sparse table decryption. Aes128CtrContext sparse_ctr_ctx; ///< AES-128-CTR context used for sparse table decryption. + u64 cur_sparse_virtual_offset; ///< Current sparse layer virtual offset. Used for content decryption if a sparse layer is available. ///< NSP-related fields. bool header_written; ///< Set to true after this FS section header has been written to an output dump. diff --git a/source/core/bktr.c b/source/core/bktr.c index e93ab9a..ff789d7 100644 --- a/source/core/bktr.c +++ b/source/core/bktr.c @@ -31,6 +31,7 @@ static bool bktrInitializeAesCtrExStorage(NcaFsSectionContext *nca_fs_ctx, BktrA static bool bktrPhysicalSectionRead(BktrContext *ctx, void *out, u64 read_size, u64 offset); static bool bktrAesCtrExStorageRead(BktrContext *ctx, void *out, u64 read_size, u64 virtual_offset, u64 section_offset); +static bool bktrPhysicalSectionSparseRead(BktrContext *ctx, void *out, u64 read_size, u64 offset); NX_INLINE BktrIndirectStorageBucket *bktrGetIndirectStorageBucket(BktrIndirectStorageBlock *block, u32 bucket_num); static BktrIndirectStorageEntry *bktrGetIndirectStorageEntry(BktrIndirectStorageBlock *block, u64 offset); @@ -76,8 +77,7 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct return false; } } else { - /* Initializing the base NCA RomFS on its own is impossible if we're dealing with a sparse layer. */ - /* Let's just handle everything here, starting with initializing the sparse layer's indirect storage. */ + /* Initialize the sparse layer's indirect storage. Don't lose time trying to initialize a RomFS context for an incomplete storage. */ if (!bktrInitializeIndirectStorage(base_nca_fs_ctx, false, &(out->base_indirect_block))) return false; /* Update the NCA FS section context from the base RomFS context. */ @@ -285,12 +285,7 @@ static bool bktrInitializeIndirectStorage(NcaFsSectionContext *nca_fs_ctx, bool } /* Decrypt indirect block, if needed. */ - if (!patch_indirect_bucket) - { - aes128CtrUpdatePartialCtr(nca_fs_ctx->sparse_ctr, nca_fs_ctx->sparse_table_offset); - aes128CtrContextResetCtr(&(nca_fs_ctx->sparse_ctr_ctx), nca_fs_ctx->sparse_ctr); - aes128CtrCrypt(&(nca_fs_ctx->sparse_ctr_ctx), indirect_block, indirect_block, nca_fs_ctx->sparse_table_size); - } + if (!patch_indirect_bucket) aes128CtrCrypt(&(nca_fs_ctx->sparse_ctr_ctx), indirect_block, indirect_block, nca_fs_ctx->sparse_table_size); /* This simplifies logic greatly... */ for(u32 i = (indirect_block->bucket_count - 1); i > 0; i--) @@ -414,9 +409,9 @@ static bool bktrPhysicalSectionRead(BktrContext *ctx, void *out, u64 read_size, if ((offset + read_size) <= next_indirect_entry->virtual_offset) { /* Read only within the current indirect storage entry. */ - /* If we're not dealing with an indirect storage entry with a patch index, just retrieve the data from the base RomFS. */ if (indirect_entry->indirect_storage_index == BktrIndirectStorageIndex_Patch) { + /* Retrieve data from the Patch RomFS' AesCtrEx storage. */ success = bktrAesCtrExStorageRead(ctx, out, read_size, offset, section_offset); if (!success) LOG_MSG("Failed to read 0x%lX bytes block from BKTR AesCtrEx storage at offset 0x%lX!", read_size, section_offset); } else @@ -424,19 +419,13 @@ static bool bktrPhysicalSectionRead(BktrContext *ctx, void *out, u64 read_size, { if (!ctx->base_romfs_ctx.nca_fs_ctx->has_sparse_layer) { + /* Retrieve data from the base NCA. */ success = ncaReadFsSection(ctx->base_romfs_ctx.nca_fs_ctx, out, read_size, section_offset); if (!success) LOG_MSG("Failed to read 0x%lX bytes block from base RomFS at offset 0x%lX!", read_size, section_offset); } else { - - - - //ctx->base_indirect_block - - - LOG_MSG("Attempting to read 0x%lX bytes block from sparse layer at offset 0x%lX!", read_size, section_offset); - - - + /* Retrieve data from the sparse layer in the base NCA. */ + success = bktrPhysicalSectionSparseRead(ctx, out, read_size, section_offset); + if (!success) LOG_MSG("Failed to read 0x%lX bytes block from sparse layer at offset 0x%lX!", read_size, section_offset); } } else { LOG_MSG("Attempting to read 0x%lX bytes block from non-existent base RomFS at offset 0x%lX!", read_size, section_offset); @@ -489,6 +478,60 @@ static bool bktrAesCtrExStorageRead(BktrContext *ctx, void *out, u64 read_size, return success; } +static bool bktrPhysicalSectionSparseRead(BktrContext *ctx, void *out, u64 read_size, u64 offset) +{ + NcaFsSectionContext *nca_fs_ctx = NULL; + + if (!ctx || !(nca_fs_ctx = ctx->base_romfs_ctx.nca_fs_ctx) || !nca_fs_ctx->has_sparse_layer || !ctx->base_indirect_block || !out || !read_size) + { + LOG_MSG("Invalid parameters!"); + return false; + } + + BktrIndirectStorageEntry *indirect_entry = NULL, *next_indirect_entry = NULL; + u64 section_offset = 0, indirect_block_size = 0; + + indirect_entry = bktrGetIndirectStorageEntry(ctx->base_indirect_block, offset); + if (!indirect_entry) + { + LOG_MSG("Error retrieving BKTR Indirect Storage Entry at offset 0x%lX!", offset); + return false; + } + + next_indirect_entry = (indirect_entry + 1); + section_offset = (offset - indirect_entry->virtual_offset + indirect_entry->physical_offset); + + /* Perform read operation. */ + bool success = false; + if ((offset + read_size) <= next_indirect_entry->virtual_offset) + { + /* Read only within the current indirect storage entry. */ + if (indirect_entry->indirect_storage_index == BktrIndirectStorageIndex_Patch) + { + /* Fill output buffer with zeroes. */ + memset(out, 0, read_size); + success = true; + } else { + /* Set the virtual offset used for data decryption within the sparse layer. */ + /* This will be used by ncaReadFsSection(). */ + nca_fs_ctx->cur_sparse_virtual_offset = offset; + + /* Retrieve data from the base NCA's sparse layer. */ + success = ncaReadFsSection(nca_fs_ctx, out, read_size, section_offset); + if (!success) LOG_MSG("Failed to read 0x%lX bytes block from sparse layer at offset 0x%lX!", read_size, section_offset); + } + } else { + /* Handle reads that span multiple indirect storage entries. */ + indirect_block_size = (next_indirect_entry->virtual_offset - offset); + + success = (bktrPhysicalSectionSparseRead(ctx, out, indirect_block_size, offset) && \ + bktrPhysicalSectionSparseRead(ctx, (u8*)out + indirect_block_size, read_size - indirect_block_size, offset + indirect_block_size)); + if (!success) LOG_MSG("Failed to read 0x%lX bytes block from multiple BKTR indirect storage entries at offset 0x%lX!", read_size, offset); + } + + return success; +} + NX_INLINE BktrIndirectStorageBucket *bktrGetIndirectStorageBucket(BktrIndirectStorageBlock *block, u32 bucket_num) { if (!block || bucket_num >= block->bucket_count) return NULL; diff --git a/source/core/nca.c b/source/core/nca.c index 9d3c23a..bd386ec 100644 --- a/source/core/nca.c +++ b/source/core/nca.c @@ -421,26 +421,20 @@ const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx) const char *str = "Invalid"; bool is_exefs = false; - if (!ctx || !ctx->enabled || !(nca_ctx = (NcaContext*)ctx->nca_ctx)) return str; + if (!ctx || !(nca_ctx = (NcaContext*)ctx->nca_ctx)) return str; is_exefs = (nca_ctx->content_type == NcmContentType_Program && ctx->section_idx == 0); switch(ctx->section_type) { case NcaFsSectionType_PartitionFs: - if (ctx->has_sparse_layer) - { - str = (is_exefs ? "ExeFS (update required)" : "Partition FS (update required)"); - } else { - str = (is_exefs ? "ExeFS" : "Partition FS"); - } - + str = (is_exefs ? "ExeFS" : "Partition FS"); break; case NcaFsSectionType_RomFs: - str = (ctx->has_sparse_layer ? "RomFS (update required)" : "RomFS"); + str = "RomFS"; break; case NcaFsSectionType_PatchRomFs: - str = "Patch RomFS (base required)"; + str = "Patch RomFS"; break; case NcaFsSectionType_Nca0RomFs: str = "NCA0 RomFS"; @@ -1055,6 +1049,9 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size NcaContext *nca_ctx = (NcaContext*)ctx->nca_ctx; u64 content_offset = (ctx->section_offset + offset); + u64 sparse_virtual_offset = ((ctx->has_sparse_layer && ctx->cur_sparse_virtual_offset) ? (ctx->section_offset + ctx->cur_sparse_virtual_offset) : 0); + u64 iv_offset = (sparse_virtual_offset ? sparse_virtual_offset : content_offset); + u64 block_start_offset = 0, block_end_offset = 0, block_size = 0; u64 data_start_offset = 0, chunk_size = 0, out_chunk_size = 0; @@ -1081,6 +1078,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size } /* Read remaining encrypted data, if needed. Use FS-section-relative offset. */ + if (sparse_virtual_offset) ctx->cur_sparse_virtual_offset += block_size; ret = (read_size ? _ncaReadFsSection(ctx, (u8*)out + block_size, read_size - block_size, offset + block_size) : true); goto end; } else @@ -1134,7 +1132,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size } else if (ctx->encryption_type >= NcaEncryptionType_AesCtr && ctx->encryption_type <= NcaEncryptionType_AesCtrExSkipLayerHash) { - aes128CtrUpdatePartialCtr(ctx->ctr, content_offset); + aes128CtrUpdatePartialCtr(ctx->ctr, iv_offset); aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr); aes128CtrCrypt(&(ctx->ctr_ctx), out, out, read_size); } @@ -1175,7 +1173,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size } else if (ctx->encryption_type >= NcaEncryptionType_AesCtr && ctx->encryption_type <= NcaEncryptionType_AesCtrExSkipLayerHash) { - aes128CtrUpdatePartialCtr(ctx->ctr, block_start_offset); + aes128CtrUpdatePartialCtr(ctx->ctr, ALIGN_DOWN(iv_offset, AES_BLOCK_SIZE)); aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr); aes128CtrCrypt(&(ctx->ctr_ctx), g_ncaCryptoBuffer, g_ncaCryptoBuffer, chunk_size); } @@ -1183,9 +1181,13 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size /* Copy decrypted data. */ memcpy(out, g_ncaCryptoBuffer + data_start_offset, out_chunk_size); + /* Perform another read if required. */ + if (sparse_virtual_offset && block_size > NCA_CRYPTO_BUFFER_SIZE) ctx->cur_sparse_virtual_offset += out_chunk_size; ret = (block_size > NCA_CRYPTO_BUFFER_SIZE ? _ncaReadFsSection(ctx, (u8*)out + out_chunk_size, read_size - out_chunk_size, offset + out_chunk_size) : true); end: + if (ctx->has_sparse_layer) ctx->cur_sparse_virtual_offset = 0; + return ret; }