1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-26 12:12:02 +00:00

Add support for sparse NCAs.

This commit is contained in:
Pablo Curiel 2022-06-30 18:46:45 +02:00
parent 8b0ed76011
commit 8d81528619
3 changed files with 78 additions and 32 deletions

View file

@ -373,7 +373,7 @@ typedef struct {
u8 encryption_type; ///< NcaEncryptionType. u8 encryption_type; ///< NcaEncryptionType.
u8 section_type; ///< NcaFsSectionType. u8 section_type; ///< NcaFsSectionType.
bool skip_hash_layer_crypto; ///< Set to true if hash layer decryption should be skipped while reading section data. 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. ///< 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. 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. 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. 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. 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. ///< NSP-related fields.
bool header_written; ///< Set to true after this FS section header has been written to an output dump. bool header_written; ///< Set to true after this FS section header has been written to an output dump.

View file

@ -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 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 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); NX_INLINE BktrIndirectStorageBucket *bktrGetIndirectStorageBucket(BktrIndirectStorageBlock *block, u32 bucket_num);
static BktrIndirectStorageEntry *bktrGetIndirectStorageEntry(BktrIndirectStorageBlock *block, u64 offset); static BktrIndirectStorageEntry *bktrGetIndirectStorageEntry(BktrIndirectStorageBlock *block, u64 offset);
@ -76,8 +77,7 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct
return false; return false;
} }
} else { } else {
/* Initializing the base NCA RomFS on its own is impossible if we're dealing with a sparse layer. */ /* Initialize the sparse layer's indirect storage. Don't lose time trying to initialize a RomFS context for an incomplete storage. */
/* Let's just handle everything here, starting with initializing the sparse layer's indirect storage. */
if (!bktrInitializeIndirectStorage(base_nca_fs_ctx, false, &(out->base_indirect_block))) return false; if (!bktrInitializeIndirectStorage(base_nca_fs_ctx, false, &(out->base_indirect_block))) return false;
/* Update the NCA FS section context from the base RomFS context. */ /* 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. */ /* Decrypt indirect block, if needed. */
if (!patch_indirect_bucket) if (!patch_indirect_bucket) aes128CtrCrypt(&(nca_fs_ctx->sparse_ctr_ctx), indirect_block, indirect_block, nca_fs_ctx->sparse_table_size);
{
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);
}
/* This simplifies logic greatly... */ /* This simplifies logic greatly... */
for(u32 i = (indirect_block->bucket_count - 1); i > 0; i--) 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) if ((offset + read_size) <= next_indirect_entry->virtual_offset)
{ {
/* Read only within the current indirect storage entry. */ /* 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) 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); 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); if (!success) LOG_MSG("Failed to read 0x%lX bytes block from BKTR AesCtrEx storage at offset 0x%lX!", read_size, section_offset);
} else } 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) 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); 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); if (!success) LOG_MSG("Failed to read 0x%lX bytes block from base RomFS at offset 0x%lX!", read_size, section_offset);
} else { } else {
/* 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);
//ctx->base_indirect_block
LOG_MSG("Attempting to read 0x%lX bytes block from sparse layer at offset 0x%lX!", read_size, section_offset);
} }
} else { } else {
LOG_MSG("Attempting to read 0x%lX bytes block from non-existent base RomFS at offset 0x%lX!", read_size, section_offset); 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; 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) NX_INLINE BktrIndirectStorageBucket *bktrGetIndirectStorageBucket(BktrIndirectStorageBlock *block, u32 bucket_num)
{ {
if (!block || bucket_num >= block->bucket_count) return NULL; if (!block || bucket_num >= block->bucket_count) return NULL;

View file

@ -421,26 +421,20 @@ const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx)
const char *str = "Invalid"; const char *str = "Invalid";
bool is_exefs = false; 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); is_exefs = (nca_ctx->content_type == NcmContentType_Program && ctx->section_idx == 0);
switch(ctx->section_type) switch(ctx->section_type)
{ {
case NcaFsSectionType_PartitionFs: 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; break;
case NcaFsSectionType_RomFs: case NcaFsSectionType_RomFs:
str = (ctx->has_sparse_layer ? "RomFS (update required)" : "RomFS"); str = "RomFS";
break; break;
case NcaFsSectionType_PatchRomFs: case NcaFsSectionType_PatchRomFs:
str = "Patch RomFS (base required)"; str = "Patch RomFS";
break; break;
case NcaFsSectionType_Nca0RomFs: case NcaFsSectionType_Nca0RomFs:
str = "NCA0 RomFS"; str = "NCA0 RomFS";
@ -1055,6 +1049,9 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size
NcaContext *nca_ctx = (NcaContext*)ctx->nca_ctx; NcaContext *nca_ctx = (NcaContext*)ctx->nca_ctx;
u64 content_offset = (ctx->section_offset + offset); 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 block_start_offset = 0, block_end_offset = 0, block_size = 0;
u64 data_start_offset = 0, chunk_size = 0, out_chunk_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. */ /* 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); ret = (read_size ? _ncaReadFsSection(ctx, (u8*)out + block_size, read_size - block_size, offset + block_size) : true);
goto end; goto end;
} else } else
@ -1134,7 +1132,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size
} else } else
if (ctx->encryption_type >= NcaEncryptionType_AesCtr && ctx->encryption_type <= NcaEncryptionType_AesCtrExSkipLayerHash) 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); aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
aes128CtrCrypt(&(ctx->ctr_ctx), out, out, read_size); aes128CtrCrypt(&(ctx->ctr_ctx), out, out, read_size);
} }
@ -1175,7 +1173,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size
} else } else
if (ctx->encryption_type >= NcaEncryptionType_AesCtr && ctx->encryption_type <= NcaEncryptionType_AesCtrExSkipLayerHash) 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); aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
aes128CtrCrypt(&(ctx->ctr_ctx), g_ncaCryptoBuffer, g_ncaCryptoBuffer, chunk_size); 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. */ /* Copy decrypted data. */
memcpy(out, g_ncaCryptoBuffer + data_start_offset, out_chunk_size); 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); 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: end:
if (ctx->has_sparse_layer) ctx->cur_sparse_virtual_offset = 0;
return ret; return ret;
} }