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:
parent
8b0ed76011
commit
8d81528619
3 changed files with 78 additions and 32 deletions
|
@ -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.
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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" : "Partition FS");
|
||||||
{
|
|
||||||
str = (is_exefs ? "ExeFS (update required)" : "Partition FS (update required)");
|
|
||||||
} else {
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue