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

BKTR done, needs testing.

This commit is contained in:
Pablo Curiel 2020-04-30 04:25:03 -04:00
parent e1e38bcfc2
commit 4774aeae9c
5 changed files with 491 additions and 46 deletions

View file

@ -23,17 +23,27 @@
/* Function prototypes. */ /* Function prototypes. */
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);
NX_INLINE BktrIndirectStorageBucket *bktrGetIndirectStorageBucket(BktrIndirectStorageBlock *block, u32 bucket_num);
static BktrIndirectStorageEntry *bktrGetIndirectStorageEntry(BktrIndirectStorageBlock *block, u64 offset);
NX_INLINE BktrAesCtrExStorageBucket *bktrGetAesCtrExStorageBucket(BktrAesCtrExStorageBlock *block, u32 bucket_num);
static BktrAesCtrExStorageEntry *bktrGetAesCtrExStorageEntry(BktrAesCtrExStorageBlock *block, u64 offset);
bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ctx, NcaFsSectionContext *update_nca_fs_ctx) bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ctx, NcaFsSectionContext *update_nca_fs_ctx)
{ {
NcaContext *base_nca_ctx = NULL, *update_nca_ctx = NULL; NcaContext *base_nca_ctx = NULL, *update_nca_ctx = NULL;
if (!out || !base_nca_fs_ctx || !(base_nca_ctx = (NcaContext*)base_nca_fs_ctx->nca_ctx) || base_nca_fs_ctx->section_type != NcaFsSectionType_RomFs || \ if (!out || !base_nca_fs_ctx || !(base_nca_ctx = (NcaContext*)base_nca_fs_ctx->nca_ctx) || !base_nca_fs_ctx->header || base_nca_fs_ctx->section_type != NcaFsSectionType_RomFs || \
base_nca_fs_ctx->encryption_type == NcaEncryptionType_AesCtrEx || !update_nca_fs_ctx || !(update_nca_ctx = (NcaContext*)update_nca_fs_ctx->nca_ctx) || \ base_nca_fs_ctx->encryption_type == NcaEncryptionType_AesCtrEx || !update_nca_fs_ctx || !update_nca_fs_ctx->header || !(update_nca_ctx = (NcaContext*)update_nca_fs_ctx->nca_ctx) || \
update_nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs || update_nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx || 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.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_header.magic) != NCA_BKTR_MAGIC || \
__builtin_bswap32(update_nca_fs_ctx->header->patch_info.aes_ctr_ex_header.magic) != NCA_BKTR_MAGIC || \
(update_nca_fs_ctx->header->patch_info.indirect_offset + update_nca_fs_ctx->header->patch_info.indirect_size) != update_nca_fs_ctx->header->patch_info.aes_ctr_ex_offset || \
(update_nca_fs_ctx->header->patch_info.aes_ctr_ex_offset + update_nca_fs_ctx->header->patch_info.aes_ctr_ex_size) != update_nca_fs_ctx->section_size)
{ {
LOGFILE("Invalid parameters!"); LOGFILE("Invalid parameters!");
return false; return false;
@ -46,25 +56,151 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct
return false; return false;
} }
/* Initialize update NCA RomFS context */
if (!romfsInitializeContext(&(out->patch_romfs_ctx), update_nca_fs_ctx))
{
LOGFILE("Failed to initialize update NCA RomFS context!");
romfsFreeContext(&(out->base_romfs_ctx));
return false;
}
/* Fill context */ /* Fill context */
bool success = false; bool success = false;
out->patch_info = &(update_nca_fs_ctx->header->patch_info); NcaPatchInfo *patch_info = &(update_nca_fs_ctx->header->patch_info);
out->size = out->patch_romfs_ctx.size;
out->virtual_seek = out->base_seek = out->patch_seek = 0;
/* Allocate space for an extra (fake) indirect storage entry, to simplify our logic */
out->indirect_block = calloc(1, patch_info->indirect_size + ((0x3FF0 / sizeof(u64)) * sizeof(BktrIndirectStorageEntry)));
if (!out->indirect_block)
{
LOGFILE("Unable to allocate memory for the BKTR Indirect Storage Block!");
goto exit;
}
/* Read indirect storage block data */
if (!ncaReadFsSection(update_nca_fs_ctx, out->indirect_block, patch_info->indirect_size, patch_info->indirect_offset))
{
LOGFILE("Failed to read BKTR Indirect Storage Block data!");
goto exit;
}
/* Allocate space for an extra (fake) AesCtrEx storage entry, to simplify our logic */
out->aes_ctr_ex_block = calloc(1, patch_info->aes_ctr_ex_size + (((0x3FF0 / sizeof(u64)) + 1) * sizeof(BktrAesCtrExStorageEntry)));
if (!out->aes_ctr_ex_block)
{
LOGFILE("Unable to allocate memory for the BKTR AesCtrEx Storage Block!");
goto exit;
}
/* Read AesCtrEx storage block data */
if (!ncaReadFsSection(update_nca_fs_ctx, out->aes_ctr_ex_block, patch_info->aes_ctr_ex_size, patch_info->aes_ctr_ex_offset))
{
LOGFILE("Failed to read BKTR AesCtrEx Storage Block data!");
goto exit;
}
if (out->aes_ctr_ex_block->physical_size != patch_info->aes_ctr_ex_offset)
{
LOGFILE("Invalid BKTR AesCtrEx Storage Block size!");
goto exit;
}
/* This simplifies logic greatly... */
for(u32 i = (out->indirect_block->bucket_count - 1); i > 0; i--)
{
BktrIndirectStorageBucket tmp_bucket = {0};
memcpy(&tmp_bucket, &(out->indirect_block->indirect_storage_buckets[i]), sizeof(BktrIndirectStorageBucket));
memcpy(bktrGetIndirectStorageBucket(out->indirect_block, i), &tmp_bucket, sizeof(BktrIndirectStorageBucket));
}
for(u32 i = 0; (i + 1) < out->indirect_block->bucket_count; i++)
{
BktrIndirectStorageBucket *cur_bucket = bktrGetIndirectStorageBucket(out->indirect_block, i);
cur_bucket->indirect_storage_entries[cur_bucket->entry_count].virtual_offset = out->indirect_block->virtual_offsets[i + 1];
}
for(u32 i = (out->aes_ctr_ex_block->bucket_count - 1); i > 0; i--)
{
BktrAesCtrExStorageBucket tmp_bucket = {0};
memcpy(&tmp_bucket, &(out->aes_ctr_ex_block->aes_ctr_ex_storage_buckets[i]), sizeof(BktrAesCtrExStorageBucket));
memcpy(bktrGetAesCtrExStorageBucket(out->aes_ctr_ex_block, i), &tmp_bucket, sizeof(BktrAesCtrExStorageBucket));
}
for(u32 i = 0; (i + 1) < out->aes_ctr_ex_block->bucket_count; i++)
{
BktrAesCtrExStorageBucket *cur_bucket = bktrGetAesCtrExStorageBucket(out->aes_ctr_ex_block, i);
BktrAesCtrExStorageBucket *next_bucket = bktrGetAesCtrExStorageBucket(out->aes_ctr_ex_block, i + 1);
cur_bucket->aes_ctr_ex_storage_entries[cur_bucket->entry_count].offset = next_bucket->aes_ctr_ex_storage_entries[0].offset;
cur_bucket->aes_ctr_ex_storage_entries[cur_bucket->entry_count].generation = next_bucket->aes_ctr_ex_storage_entries[0].generation;
}
BktrIndirectStorageBucket *last_indirect_bucket = bktrGetIndirectStorageBucket(out->indirect_block, out->indirect_block->bucket_count - 1);
last_indirect_bucket->indirect_storage_entries[last_indirect_bucket->entry_count].virtual_offset = out->indirect_block->virtual_size;
BktrAesCtrExStorageBucket *last_aes_ctr_ex_bucket = bktrGetAesCtrExStorageBucket(out->aes_ctr_ex_block, out->aes_ctr_ex_block->bucket_count - 1);
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count].offset = patch_info->indirect_offset;
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count].generation = update_nca_fs_ctx->header->generation;
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count + 1].offset = update_nca_fs_ctx->section_size;
last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count + 1].generation = 0;
/* Initialize update NCA RomFS context */
/* Don't verify offsets from Patch RomFS sections, because they reflect the full, patched RomFS image */
out->patch_romfs_ctx.nca_fs_ctx = update_nca_fs_ctx;
out->patch_romfs_ctx.offset = out->offset = update_nca_fs_ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info.offset;
out->patch_romfs_ctx.size = out->size = update_nca_fs_ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info.size;
/* Read update NCA RomFS header */
if (!bktrPhysicalSectionRead(out, &(out->patch_romfs_ctx.header), sizeof(RomFileSystemHeader), out->patch_romfs_ctx.offset))
{
LOGFILE("Failed to read update NCA RomFS header!");
goto exit;
}
if (out->patch_romfs_ctx.header.cur_format.header_size != ROMFS_HEADER_SIZE)
{
LOGFILE("Invalid update NCA RomFS header size!");
goto exit;
}
/* Read directory entries table */
u64 dir_table_offset = out->patch_romfs_ctx.header.cur_format.directory_entry_offset;
out->patch_romfs_ctx.dir_table_size = out->patch_romfs_ctx.header.cur_format.directory_entry_size;
if (!dir_table_offset || !out->patch_romfs_ctx.dir_table_size)
{
LOGFILE("Invalid update NCA RomFS directory entries table!");
goto exit;
}
out->patch_romfs_ctx.dir_table = malloc(out->patch_romfs_ctx.dir_table_size);
if (!out->patch_romfs_ctx.dir_table)
{
LOGFILE("Unable to allocate memory for the update NCA RomFS directory entries table!");
goto exit;
}
if (!bktrPhysicalSectionRead(out, &(out->patch_romfs_ctx.dir_table), out->patch_romfs_ctx.dir_table_size, out->patch_romfs_ctx.offset + dir_table_offset))
{
LOGFILE("Failed to read update NCA RomFS directory entries table!");
goto exit;
}
/* Read file entries table */
u64 file_table_offset = out->patch_romfs_ctx.header.cur_format.file_entry_offset;
out->patch_romfs_ctx.file_table_size = out->patch_romfs_ctx.header.cur_format.file_entry_size;
if (!file_table_offset || !out->patch_romfs_ctx.file_table_size)
{
LOGFILE("Invalid update NCA RomFS file entries table!");
goto exit;
}
out->patch_romfs_ctx.file_table = malloc(out->patch_romfs_ctx.file_table_size);
if (!out->patch_romfs_ctx.file_table)
{
LOGFILE("Unable to allocate memory for the update NCA RomFS file entries table!");
goto exit;
}
if (!bktrPhysicalSectionRead(out, &(out->patch_romfs_ctx.file_table), out->patch_romfs_ctx.file_table_size, out->patch_romfs_ctx.offset + file_table_offset))
{
LOGFILE("Failed to read update NCA RomFS file entries table!");
goto exit;
}
/* Get file data body offset */
out->patch_romfs_ctx.body_offset = out->body_offset = out->patch_romfs_ctx.header.cur_format.body_offset;
success = true; success = true;
@ -74,6 +210,236 @@ exit:
return success; return success;
} }
bool bktrReadFileSystemData(BktrContext *ctx, void *out, u64 read_size, u64 offset)
{
if (!ctx || !ctx->size || !out || !read_size || offset >= ctx->size || (offset + read_size) > ctx->size)
{
LOGFILE("Invalid parameters!");
return false;
}
/* Read filesystem data */
if (!bktrPhysicalSectionRead(ctx, out, read_size, ctx->offset + offset))
{
LOGFILE("Failed to read Patch RomFS data!");
return false;
}
return true;
}
bool bktrReadFileEntryData(BktrContext *ctx, RomFileSystemFileEntry *file_entry, void *out, u64 read_size, u64 offset)
{
if (!ctx || !ctx->body_offset || !file_entry || !file_entry->size || file_entry->offset >= ctx->size || (file_entry->offset + file_entry->size) > ctx->size || \
!out || !read_size || offset >= file_entry->size || (offset + read_size) > file_entry->size)
{
LOGFILE("Invalid parameters!");
return false;
}
/* Read entry data */
if (!bktrReadFileSystemData(ctx, out, read_size, ctx->body_offset + file_entry->offset + offset))
{
LOGFILE("Failed to read Patch RomFS file entry data!");
return false;
}
return true;
}
static bool bktrPhysicalSectionRead(BktrContext *ctx, void *out, u64 read_size, u64 offset)
{
if (!ctx || !ctx->base_romfs_ctx.nca_fs_ctx || !ctx->indirect_block || !out || !read_size)
{
LOGFILE("Invalid parameters!");
return false;
}
BktrIndirectStorageEntry *indirect_entry = NULL, *next_indirect_entry = NULL;
u64 section_offset = 0, indirect_block_size = 0;
/* Determine which FS section to use + the actual offset to start reading from */
/* No better way to do this than to make all BKTR addresses virtual */
indirect_entry = bktrGetIndirectStorageEntry(ctx->indirect_block, offset);
if (!indirect_entry)
{
LOGFILE("Error retrieving BKTR Indirect Storage Entry at offset 0x%lX!", offset);
return false;
}
section_offset = (offset - indirect_entry->virtual_offset + indirect_entry->physical_offset);
/* Perform read operation */
bool success = false;
next_indirect_entry = (indirect_entry + 1);
if ((offset + read_size) <= next_indirect_entry->virtual_offset)
{
/* Easy path: 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)
{
success = bktrAesCtrExStorageRead(ctx, out, read_size, offset, section_offset);
} else {
success = ncaReadFsSection(ctx->base_romfs_ctx.nca_fs_ctx, out, read_size, section_offset);
}
} else {
/* Handle read that spans multiple indirect storage entries */
indirect_block_size = (next_indirect_entry->virtual_offset - offset);
success = (bktrReadFileSystemData(ctx, out, indirect_block_size, offset) && \
bktrReadFileSystemData(ctx, (u8*)out + indirect_block_size, read_size - indirect_block_size, offset + indirect_block_size));
}
return success;
}
static bool bktrAesCtrExStorageRead(BktrContext *ctx, void *out, u64 read_size, u64 virtual_offset, u64 section_offset)
{
BktrAesCtrExStorageEntry *aes_ctr_ex_entry = NULL, *next_aes_ctr_ex_entry = NULL;
if (!ctx || !ctx->patch_romfs_ctx.nca_fs_ctx || !ctx->aes_ctr_ex_block || !out || !read_size)
{
LOGFILE("Invalid parameters!");
return false;
}
aes_ctr_ex_entry = bktrGetAesCtrExStorageEntry(ctx->aes_ctr_ex_block, section_offset);
if (!aes_ctr_ex_entry)
{
LOGFILE("Error retrieving BKTR AesCtrEx Storage Entry at offset 0x%lX!", section_offset);
return false;
}
/* Perform read operation */
bool success = false;
next_aes_ctr_ex_entry = (aes_ctr_ex_entry + 1);
if ((section_offset + read_size) <= next_aes_ctr_ex_entry->offset)
{
/* Read AesCtrEx storage entry data */
success = ncaReadAesCtrExStorageFromBktrSection(ctx->patch_romfs_ctx.nca_fs_ctx, out, read_size, section_offset, aes_ctr_ex_entry->generation);
} else {
/* Handle read that spans multiple AesCtrEx storage entries */
u64 aes_ctr_ex_block_size = (next_aes_ctr_ex_entry->offset - section_offset);
success = (bktrReadFileSystemData(ctx, out, aes_ctr_ex_block_size, virtual_offset) && \
bktrReadFileSystemData(ctx, (u8*)out + aes_ctr_ex_block_size, read_size - aes_ctr_ex_block_size, virtual_offset + aes_ctr_ex_block_size));
}
return success;
}
NX_INLINE BktrIndirectStorageBucket *bktrGetIndirectStorageBucket(BktrIndirectStorageBlock *block, u32 bucket_num)
{
if (!block || bucket_num >= block->bucket_count) return NULL;
return (BktrIndirectStorageBucket*)((u8*)block->indirect_storage_buckets + ((sizeof(BktrIndirectStorageBucket) + sizeof(BktrIndirectStorageEntry)) * bucket_num));
}
static BktrIndirectStorageEntry *bktrGetIndirectStorageEntry(BktrIndirectStorageBlock *block, u64 offset)
{
if (!block || offset >= block->virtual_size)
{
LOGFILE("Invalid parameters!");
return NULL;
}
u32 bucket_num = 0;
BktrIndirectStorageBucket *bucket = NULL;
for(u32 i = 1; i < block->bucket_count; i++)
{
if (block->virtual_offsets[i] <= offset) bucket_num++;
}
bucket = bktrGetIndirectStorageBucket(block, bucket_num);
if (!bucket || !bucket->entry_count)
{
LOGFILE("Error retrieving BKTR indirect storage bucket #%u!", bucket_num);
return NULL;
}
/* Check for edge case, short circuit */
if (bucket->entry_count == 1) return &(bucket->indirect_storage_entries[0]);
/* Binary search */
u32 low = 0, high = (bucket->entry_count - 1);
while(low <= high)
{
u32 mid = ((low + high) / 2);
if (bucket->indirect_storage_entries[mid].virtual_offset > offset)
{
/* Too high */
high = (mid - 1);
} else {
/* Check for success */
if (mid == (bucket->entry_count - 1) || bucket->indirect_storage_entries[mid + 1].virtual_offset > offset) return &(bucket->indirect_storage_entries[mid]);
low = (mid + 1);
}
}
LOGFILE("Failed to find offset 0x%lX in BKTR indirect storage block!", offset);
return NULL;
}
NX_INLINE BktrAesCtrExStorageBucket *bktrGetAesCtrExStorageBucket(BktrAesCtrExStorageBlock *block, u32 bucket_num)
{
if (!block || bucket_num >= block->bucket_count) return NULL;
return (BktrAesCtrExStorageBucket*)((u8*)block->aes_ctr_ex_storage_buckets + ((sizeof(BktrAesCtrExStorageBucket) + sizeof(BktrAesCtrExStorageEntry)) * bucket_num));
}
static BktrAesCtrExStorageEntry *bktrGetAesCtrExStorageEntry(BktrAesCtrExStorageBlock *block, u64 offset)
{
if (!block || offset >= block->physical_size)
{
LOGFILE("Invalid parameters!");
return NULL;
}
u32 bucket_num = 0;
BktrAesCtrExStorageBucket *last_bucket = NULL, *bucket = NULL;
/* If offset is past the virtual, we're reading from the BKTR_HEADER subsection */
last_bucket = bktrGetAesCtrExStorageBucket(block, block->bucket_count - 1);
if (!last_bucket)
{
LOGFILE("Error retrieving last BKTR AesCtrEx storage bucket!");
return NULL;
}
if (offset >= last_bucket->aes_ctr_ex_storage_entries[last_bucket->entry_count].offset) return &(last_bucket->aes_ctr_ex_storage_entries[last_bucket->entry_count]);
for(u32 i = 1; i < block->bucket_count; i++)
{
if (block->physical_offsets[i] <= offset) bucket_num++;
}
bucket = bktrGetAesCtrExStorageBucket(block, bucket_num);
if (!bucket)
{
LOGFILE("Error retrieving BKTR AesCtrEx storage bucket #%u!", bucket_num);
return NULL;
}
/* Check for edge case, short circuit */
if (bucket->entry_count == 1) return &(bucket->aes_ctr_ex_storage_entries[0]);
/* Binary search */
u32 low = 0, high = (bucket->entry_count - 1);
while(low <= high)
{
u32 mid = ((low + high) / 2);
if (bucket->aes_ctr_ex_storage_entries[mid].offset > offset)
{
/* Too high */
high = (mid - 1);
} else {
/* Check for success */
if (mid == (bucket->entry_count - 1) || bucket->aes_ctr_ex_storage_entries[mid + 1].offset > offset) return &(bucket->aes_ctr_ex_storage_entries[mid]);
low = (mid + 1);
}
}
LOGFILE("Failed to find offset 0x%lX in BKTR AesCtrEx storage block!", offset);
return NULL;
}

View file

@ -73,17 +73,11 @@ typedef struct {
typedef struct { typedef struct {
RomFileSystemContext base_romfs_ctx; ///< Base NCA RomFS context. RomFileSystemContext base_romfs_ctx; ///< Base NCA RomFS context.
RomFileSystemContext patch_romfs_ctx; ///< Update NCA RomFS context. Must be used with RomFS directory/file entry functions, because it holds the updated directory/file tables. RomFileSystemContext patch_romfs_ctx; ///< Update NCA RomFS context. Must be used with RomFS directory/file entry functions, because it holds the updated directory/file tables.
NcaPatchInfo *patch_info; ///< BKTR patch info block. u64 offset; ///< Patched RomFS image offset (relative to the start of the update NCA FS section).
u64 size; ///< Patched RomFS image size. u64 size; ///< Patched RomFS image size.
BktrIndirectStorageBlock *indirect_block; u64 body_offset; ///< Patched RomFS image file data body offset (relative to the start of the RomFS).
BktrAesCtrExStorageBlock *aes_ctr_ex_block; BktrIndirectStorageBlock *indirect_block; ///< BKTR Indirect Storage Block.
BktrAesCtrExStorageBlock *aes_ctr_ex_block; ///< BKTR AesCtrEx Storage Block.
u64 virtual_seek; ///< Relative to the start of the NCA FS section.
u64 base_seek; ///< Relative to the start of the NCA FS section (base NCA RomFS).
u64 patch_seek; ///< Relative to the start of the NCA FS section (update NCA BKTR).
} BktrContext; } BktrContext;
/// Initializes a BKTR context. /// Initializes a BKTR context.

View file

@ -52,6 +52,8 @@ NX_INLINE void ncaUpdateAesCtrIv(u8 *ctr, u64 offset);
NX_INLINE void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset); NX_INLINE void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset);
static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, bool lock); static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, bool lock);
static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val, bool lock);
static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset, bool lock); static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset, bool lock);
bool ncaAllocateCryptoBuffer(void) bool ncaAllocateCryptoBuffer(void)
@ -393,6 +395,11 @@ bool ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 of
return _ncaReadFsSection(ctx, out, read_size, offset, true); return _ncaReadFsSection(ctx, out, read_size, offset, true);
} }
bool ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val)
{
return _ncaReadAesCtrExStorageFromBktrSection(ctx, out, read_size, offset, ctr_val, true);
}
void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset) void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset)
{ {
return _ncaGenerateEncryptedFsSectionBlock(ctx, data, data_size, data_offset, out_block_size, out_block_offset, true); return _ncaGenerateEncryptedFsSectionBlock(ctx, data, data_size, data_offset, out_block_size, out_block_offset, true);
@ -411,8 +418,9 @@ bool ncaGenerateHierarchicalSha256Patch(NcaFsSectionContext *ctx, const void *da
bool success = false; bool success = false;
if (!ctx || !(nca_ctx = (NcaContext*)ctx->nca_ctx) || !ctx->header || ctx->header->hash_type != NcaHashType_HierarchicalSha256 || !data || !data_size || \ if (!ctx || !(nca_ctx = (NcaContext*)ctx->nca_ctx) || !ctx->header || ctx->header->hash_type != NcaHashType_HierarchicalSha256 || ctx->header->encryption_type == NcaEncryptionType_AesCtrEx || \
!(hash_block_size = ctx->header->hash_info.hierarchical_sha256.hash_block_size) || !(hash_data_layer_size = ctx->header->hash_info.hierarchical_sha256.hash_data_layer_info.size) || \ !data || !data_size || !(hash_block_size = ctx->header->hash_info.hierarchical_sha256.hash_block_size) || \
!(hash_data_layer_size = ctx->header->hash_info.hierarchical_sha256.hash_data_layer_info.size) || \
!(hash_target_layer_size = ctx->header->hash_info.hierarchical_sha256.hash_target_layer_info.size) || data_offset >= hash_target_layer_size || \ !(hash_target_layer_size = ctx->header->hash_info.hierarchical_sha256.hash_target_layer_info.size) || data_offset >= hash_target_layer_size || \
(data_offset + data_size) > hash_target_layer_size || !out) (data_offset + data_size) > hash_target_layer_size || !out)
{ {
@ -529,8 +537,8 @@ bool ncaGenerateHierarchicalIntegrityPatch(NcaFsSectionContext *ctx, const void
u8 *hash_data_block = NULL, *hash_target_block = NULL; u8 *hash_data_block = NULL, *hash_target_block = NULL;
if (!ctx || !(nca_ctx = (NcaContext*)ctx->nca_ctx) || !ctx->header || ctx->header->hash_type != NcaHashType_HierarchicalIntegrity || !data || !data_size || !out || \ if (!ctx || !(nca_ctx = (NcaContext*)ctx->nca_ctx) || !ctx->header || ctx->header->hash_type != NcaHashType_HierarchicalIntegrity || ctx->header->encryption_type == NcaEncryptionType_AesCtrEx || \
data_offset >= ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info.size || \ !data || !data_size || !out || data_offset >= ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info.size || \
(data_offset + data_size) > ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info.size) (data_offset + data_size) > ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info.size)
{ {
LOGFILE("Invalid parameters!"); LOGFILE("Invalid parameters!");
@ -1053,6 +1061,83 @@ exit:
return ret; return ret;
} }
static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val, bool lock)
{
if (lock) mutexLock(&g_ncaCryptoBufferMutex);
bool ret = false;
if (!g_ncaCryptoBuffer || !ctx || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < NCA_FULL_HEADER_LENGTH || ctx->section_type != NcaFsSectionType_PatchRomFs || \
ctx->encryption_type != NcaEncryptionType_AesCtrEx || !ctx->header || !out || !read_size || offset >= ctx->section_size || (offset + read_size) > ctx->section_size)
{
LOGFILE("Invalid NCA FS section header parameters!");
goto exit;
}
NcaContext *nca_ctx = (NcaContext*)ctx->nca_ctx;
u64 content_offset = (ctx->section_offset + 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;
if (!strlen(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \
content_offset >= nca_ctx->content_size || (content_offset + read_size) > nca_ctx->content_size)
{
LOGFILE("Invalid NCA header parameters!");
goto exit;
}
/* Optimization for reads that are aligned to the AES-CTR sector size */
if (!(content_offset % AES_BLOCK_SIZE) && !(read_size % AES_BLOCK_SIZE))
{
/* Read data */
if (!ncaReadContentFile(nca_ctx, out, read_size, content_offset))
{
LOGFILE("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (aligned)", read_size, content_offset, nca_ctx->content_id_str, ctx->section_num);
goto exit;
}
/* Decrypt data */
ncaUpdateAesCtrExIv(ctx->ctr, ctr_val, content_offset);
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
aes128CtrCrypt(&(ctx->ctr_ctx), out, out, read_size);
ret = true;
goto exit;
}
/* Calculate offsets and block sizes */
block_start_offset = ALIGN_DOWN(content_offset, AES_BLOCK_SIZE);
block_end_offset = ALIGN_UP(content_offset + read_size, AES_BLOCK_SIZE);
block_size = (block_end_offset - block_start_offset);
data_start_offset = (content_offset - block_start_offset);
chunk_size = (block_size > NCA_CRYPTO_BUFFER_SIZE ? NCA_CRYPTO_BUFFER_SIZE : block_size);
out_chunk_size = (block_size > NCA_CRYPTO_BUFFER_SIZE ? (NCA_CRYPTO_BUFFER_SIZE - data_start_offset) : read_size);
/* Read data */
if (!ncaReadContentFile(nca_ctx, g_ncaCryptoBuffer, chunk_size, block_start_offset))
{
LOGFILE("Failed to read 0x%lX bytes encrypted data block at offset 0x%lX from NCA \"%s\" FS section #%u! (unaligned)", chunk_size, block_start_offset, nca_ctx->content_id_str, ctx->section_num);
goto exit;
}
/* Decrypt data */
ncaUpdateAesCtrExIv(ctx->ctr, ctr_val, block_start_offset);
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
aes128CtrCrypt(&(ctx->ctr_ctx), g_ncaCryptoBuffer, g_ncaCryptoBuffer, chunk_size);
/* Copy decrypted data */
memcpy(out, g_ncaCryptoBuffer + data_start_offset, out_chunk_size);
ret = (block_size > NCA_CRYPTO_BUFFER_SIZE ? _ncaReadAesCtrExStorageFromBktrSection(ctx, (u8*)out + out_chunk_size, read_size - out_chunk_size, offset + out_chunk_size, ctr_val, false) : true);
exit:
if (lock) mutexUnlock(&g_ncaCryptoBufferMutex);
return ret;
}
static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset, bool lock) static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset, bool lock)
{ {
if (lock) mutexLock(&g_ncaCryptoBufferMutex); if (lock) mutexLock(&g_ncaCryptoBufferMutex);
@ -1061,7 +1146,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
bool success = false; bool success = false;
if (!g_ncaCryptoBuffer || !ctx || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < NCA_FULL_HEADER_LENGTH || ctx->section_type >= NcaFsSectionType_Invalid || \ if (!g_ncaCryptoBuffer || !ctx || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < NCA_FULL_HEADER_LENGTH || ctx->section_type >= NcaFsSectionType_Invalid || \
ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type > NcaEncryptionType_AesCtrEx || !ctx->header || !data || !data_size || data_offset >= ctx->section_size || \ ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type >= NcaEncryptionType_AesCtrEx || !ctx->header || !data || !data_size || data_offset >= ctx->section_size || \
(data_offset + data_size) > ctx->section_size || !out_block_size || !out_block_offset) (data_offset + data_size) > ctx->section_size || !out_block_size || !out_block_offset)
{ {
LOGFILE("Invalid NCA FS section header parameters!"); LOGFILE("Invalid NCA FS section header parameters!");
@ -1087,7 +1172,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
/* Optimization for blocks from plaintext FS sections or blocks that are aligned to the AES-CTR / AES-XTS sector size */ /* Optimization for blocks from plaintext FS sections or blocks that are aligned to the AES-CTR / AES-XTS sector size */
if (ctx->encryption_type == NcaEncryptionType_None || \ if (ctx->encryption_type == NcaEncryptionType_None || \
(ctx->encryption_type == NcaEncryptionType_AesXts && !(content_offset % NCA_AES_XTS_SECTOR_SIZE) && !(data_size % NCA_AES_XTS_SECTOR_SIZE)) || \ (ctx->encryption_type == NcaEncryptionType_AesXts && !(content_offset % NCA_AES_XTS_SECTOR_SIZE) && !(data_size % NCA_AES_XTS_SECTOR_SIZE)) || \
((ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx) && !(content_offset % AES_BLOCK_SIZE) && !(data_size % AES_BLOCK_SIZE))) (ctx->encryption_type == NcaEncryptionType_AesCtr && !(content_offset % AES_BLOCK_SIZE) && !(data_size % AES_BLOCK_SIZE)))
{ {
/* Allocate memory */ /* Allocate memory */
out = malloc(data_size); out = malloc(data_size);
@ -1112,7 +1197,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
goto exit; goto exit;
} }
} else } else
if (ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx) if (ctx->encryption_type == NcaEncryptionType_AesCtr)
{ {
ncaUpdateAesCtrIv(ctx->ctr, content_offset); ncaUpdateAesCtrIv(ctx->ctr, content_offset);
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr); aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);
@ -1164,7 +1249,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const
goto exit; goto exit;
} }
} else } else
if (ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx) if (ctx->encryption_type == NcaEncryptionType_AesCtr)
{ {
ncaUpdateAesCtrIv(ctx->ctr, content_offset); ncaUpdateAesCtrIv(ctx->ctr, content_offset);
aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr); aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr);

View file

@ -321,13 +321,18 @@ bool ncaReadContentFile(NcaContext *ctx, void *out, u64 read_size, u64 offset);
/// Reads decrypted data from a NCA FS section using an input context. /// Reads decrypted data from a NCA FS section using an input context.
/// Input offset must be relative to the start of the NCA FS section. /// Input offset must be relative to the start of the NCA FS section.
/// If dealing with Patch RomFS sections, this function should only be used when *not* reading the BKTR AesCtrEx storage. /// If dealing with Patch RomFS sections, this function should only be used when *not* reading BKTR AesCtrEx storage data. Use ncaReadAesCtrExStorageFromBktrSection() for that.
bool ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset); bool ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset);
/// Reads decrypted BKTR AesCtrEx storage data from a NCA Patch RomFS section using an input context and a AesCtrEx CTR value.
/// Input offset must be relative to the start of the NCA FS section.
bool ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val);
/// Returns a pointer to a heap-allocated buffer used to encrypt the input plaintext data, based on the encryption type used by the input NCA FS section, as well as its offset and size. /// Returns a pointer to a heap-allocated buffer used to encrypt the input plaintext data, based on the encryption type used by the input NCA FS section, as well as its offset and size.
/// Input offset must be relative to the start of the NCA FS section. /// Input offset must be relative to the start of the NCA FS section.
/// Output size and offset are guaranteed to be aligned to the AES sector size used by the encryption type from the FS section. /// Output size and offset are guaranteed to be aligned to the AES sector size used by the encryption type from the FS section.
/// Output offset is relative to the start of the NCA content file, making it easier to use the output encrypted block to seamlessly replace data while dumping a NCA. /// Output offset is relative to the start of the NCA content file, making it easier to use the output encrypted block to seamlessly replace data while dumping a NCA.
/// This function isn't compatible with Patch RomFS sections.
void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset); void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset);
/// Generates HierarchicalSha256 FS section patch data, which can be used to replace NCA data in content dumping operations. /// Generates HierarchicalSha256 FS section patch data, which can be used to replace NCA data in content dumping operations.

View file

@ -31,10 +31,9 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_
NcaContext *nca_ctx = NULL; NcaContext *nca_ctx = NULL;
u64 dir_table_offset = 0, file_table_offset = 0; u64 dir_table_offset = 0, file_table_offset = 0;
if (!out || !nca_fs_ctx || (nca_fs_ctx->section_type != NcaFsSectionType_RomFs && nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs) || !nca_fs_ctx->header || \ if (!out || !nca_fs_ctx || !nca_fs_ctx->header || !(nca_ctx = (NcaContext*)nca_fs_ctx->nca_ctx) || (nca_ctx->format_version == NcaVersion_Nca0 && \
!(nca_ctx = (NcaContext*)nca_fs_ctx->nca_ctx) || (nca_ctx->format_version == NcaVersion_Nca0 && (nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs || \ (nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs || nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalSha256)) || (nca_ctx->format_version != NcaVersion_Nca0 && \
nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalSha256)) || (nca_ctx->format_version != NcaVersion_Nca0 && (nca_fs_ctx->section_type != NcaFsSectionType_RomFs || \ (nca_fs_ctx->section_type != NcaFsSectionType_RomFs || nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalIntegrity)))
nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalIntegrity)))
{ {
LOGFILE("Invalid parameters!"); LOGFILE("Invalid parameters!");
return false; return false;
@ -61,8 +60,7 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_
out->offset = nca_fs_ctx->header->hash_info.hierarchical_sha256.hash_target_layer_info.offset; out->offset = nca_fs_ctx->header->hash_info.hierarchical_sha256.hash_target_layer_info.offset;
out->size = nca_fs_ctx->header->hash_info.hierarchical_sha256.hash_target_layer_info.size; out->size = nca_fs_ctx->header->hash_info.hierarchical_sha256.hash_target_layer_info.size;
} else { } else {
/* Don't verify offsets from Patch RomFS sections, because they reflect the full, patched RomFS image */ if (!ncaValidateHierarchicalIntegrityOffsets(&(nca_fs_ctx->header->hash_info.hierarchical_integrity), nca_fs_ctx->section_size))
if (nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx && !ncaValidateHierarchicalIntegrityOffsets(&(nca_fs_ctx->header->hash_info.hierarchical_integrity), nca_fs_ctx->section_size))
{ {
LOGFILE("Invalid HierarchicalIntegrity block!"); LOGFILE("Invalid HierarchicalIntegrity block!");
return false; return false;
@ -92,8 +90,7 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_
dir_table_offset = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.directory_entry_offset : out->header.cur_format.directory_entry_offset); dir_table_offset = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.directory_entry_offset : out->header.cur_format.directory_entry_offset);
out->dir_table_size = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.directory_entry_size : out->header.cur_format.directory_entry_size); out->dir_table_size = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.directory_entry_size : out->header.cur_format.directory_entry_size);
/* Don't verify offsets from Patch RomFS sections, because they reflect the full, patched RomFS image */ if (!out->dir_table_size || dir_table_offset >= out->size || (dir_table_offset + out->dir_table_size) > out->size)
if (!out->dir_table_size || (nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx && (dir_table_offset >= out->size || (dir_table_offset + out->dir_table_size) > out->size)))
{ {
LOGFILE("Invalid RomFS directory entries table!"); LOGFILE("Invalid RomFS directory entries table!");
return false; return false;
@ -116,8 +113,7 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_
file_table_offset = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.file_entry_offset : out->header.cur_format.file_entry_offset); file_table_offset = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.file_entry_offset : out->header.cur_format.file_entry_offset);
out->file_table_size = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.file_entry_size : out->header.cur_format.file_entry_size); out->file_table_size = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.file_entry_size : out->header.cur_format.file_entry_size);
/* Don't verify offsets from Patch RomFS sections, because they reflect the full, patched RomFS image */ if (!out->file_table_size || file_table_offset >= out->size || (file_table_offset + out->file_table_size) > out->size)
if (!out->file_table_size || (nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx && (file_table_offset >= out->size || (file_table_offset + out->file_table_size) > out->size)))
{ {
LOGFILE("Invalid RomFS file entries table!"); LOGFILE("Invalid RomFS file entries table!");
goto exit; goto exit;
@ -137,9 +133,8 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_
} }
/* Get file data body offset */ /* Get file data body offset */
/* Don't verify offsets from Patch RomFS sections, because they reflect the full, patched RomFS image */
out->body_offset = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.body_offset : out->header.cur_format.body_offset); out->body_offset = (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs ? (u64)out->header.old_format.body_offset : out->header.cur_format.body_offset);
if (nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx && out->body_offset >= out->size) if (out->body_offset >= out->size)
{ {
LOGFILE("Invalid RomFS file data body!"); LOGFILE("Invalid RomFS file data body!");
goto exit; goto exit;