diff --git a/source/bktr.c b/source/bktr.c index 3637e8d..b3a1e85 100644 --- a/source/bktr.c +++ b/source/bktr.c @@ -23,17 +23,27 @@ /* 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) { 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 || \ - base_nca_fs_ctx->encryption_type == NcaEncryptionType_AesCtrEx || !update_nca_fs_ctx || !(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 || - base_nca_ctx->header.program_id != update_nca_ctx->header.program_id) + 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_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 || \ + 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!"); return false; @@ -46,25 +56,151 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct 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 */ bool success = false; - out->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; + NcaPatchInfo *patch_info = &(update_nca_fs_ctx->header->patch_info); + /* 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; @@ -74,6 +210,236 @@ exit: 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; +} diff --git a/source/bktr.h b/source/bktr.h index 6fdfd26..a29c1fe 100644 --- a/source/bktr.h +++ b/source/bktr.h @@ -73,17 +73,11 @@ typedef struct { typedef struct { 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. - 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. - BktrIndirectStorageBlock *indirect_block; - BktrAesCtrExStorageBlock *aes_ctr_ex_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). + u64 body_offset; ///< Patched RomFS image file data body offset (relative to the start of the RomFS). + BktrIndirectStorageBlock *indirect_block; ///< BKTR Indirect Storage Block. + BktrAesCtrExStorageBlock *aes_ctr_ex_block; ///< BKTR AesCtrEx Storage Block. } BktrContext; /// Initializes a BKTR context. diff --git a/source/nca.c b/source/nca.c index f235894..97f83cc 100644 --- a/source/nca.c +++ b/source/nca.c @@ -52,6 +52,8 @@ NX_INLINE void ncaUpdateAesCtrIv(u8 *ctr, 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 _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); 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); } +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) { 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; - if (!ctx || !(nca_ctx = (NcaContext*)ctx->nca_ctx) || !ctx->header || ctx->header->hash_type != NcaHashType_HierarchicalSha256 || !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) || \ + if (!ctx || !(nca_ctx = (NcaContext*)ctx->nca_ctx) || !ctx->header || ctx->header->hash_type != NcaHashType_HierarchicalSha256 || ctx->header->encryption_type == NcaEncryptionType_AesCtrEx || \ + !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 || \ (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; - if (!ctx || !(nca_ctx = (NcaContext*)ctx->nca_ctx) || !ctx->header || ctx->header->hash_type != NcaHashType_HierarchicalIntegrity || !data || !data_size || !out || \ - data_offset >= ctx->header->hash_info.hierarchical_integrity.hash_target_layer_info.size || \ + if (!ctx || !(nca_ctx = (NcaContext*)ctx->nca_ctx) || !ctx->header || ctx->header->hash_type != NcaHashType_HierarchicalIntegrity || ctx->header->encryption_type == NcaEncryptionType_AesCtrEx || \ + !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) { LOGFILE("Invalid parameters!"); @@ -1053,6 +1061,83 @@ exit: 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) { if (lock) mutexLock(&g_ncaCryptoBufferMutex); @@ -1061,7 +1146,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const 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 || \ - 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) { 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 */ 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_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 */ out = malloc(data_size); @@ -1112,7 +1197,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const goto exit; } } else - if (ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx) + if (ctx->encryption_type == NcaEncryptionType_AesCtr) { ncaUpdateAesCtrIv(ctx->ctr, content_offset); aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr); @@ -1164,7 +1249,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const goto exit; } } else - if (ctx->encryption_type == NcaEncryptionType_AesCtr || ctx->encryption_type == NcaEncryptionType_AesCtrEx) + if (ctx->encryption_type == NcaEncryptionType_AesCtr) { ncaUpdateAesCtrIv(ctx->ctr, content_offset); aes128CtrContextResetCtr(&(ctx->ctr_ctx), ctx->ctr); diff --git a/source/nca.h b/source/nca.h index 0063739..fbfb9cb 100644 --- a/source/nca.h +++ b/source/nca.h @@ -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. /// 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); +/// 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. /// 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 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); /// Generates HierarchicalSha256 FS section patch data, which can be used to replace NCA data in content dumping operations. diff --git a/source/romfs.c b/source/romfs.c index 78695f6..5ec8f6c 100644 --- a/source/romfs.c +++ b/source/romfs.c @@ -31,10 +31,9 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_ NcaContext *nca_ctx = NULL; 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 || \ - !(nca_ctx = (NcaContext*)nca_fs_ctx->nca_ctx) || (nca_ctx->format_version == NcaVersion_Nca0 && (nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs || \ - nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalSha256)) || (nca_ctx->format_version != NcaVersion_Nca0 && (nca_fs_ctx->section_type != NcaFsSectionType_RomFs || \ - nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalIntegrity))) + if (!out || !nca_fs_ctx || !nca_fs_ctx->header || !(nca_ctx = (NcaContext*)nca_fs_ctx->nca_ctx) || (nca_ctx->format_version == NcaVersion_Nca0 && \ + (nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs || nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalSha256)) || (nca_ctx->format_version != NcaVersion_Nca0 && \ + (nca_fs_ctx->section_type != NcaFsSectionType_RomFs || nca_fs_ctx->header->hash_type != NcaHashType_HierarchicalIntegrity))) { LOGFILE("Invalid parameters!"); 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->size = nca_fs_ctx->header->hash_info.hierarchical_sha256.hash_target_layer_info.size; } else { - /* Don't verify offsets from Patch RomFS sections, because they reflect the full, patched RomFS image */ - if (nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx && !ncaValidateHierarchicalIntegrityOffsets(&(nca_fs_ctx->header->hash_info.hierarchical_integrity), nca_fs_ctx->section_size)) + if (!ncaValidateHierarchicalIntegrityOffsets(&(nca_fs_ctx->header->hash_info.hierarchical_integrity), nca_fs_ctx->section_size)) { LOGFILE("Invalid HierarchicalIntegrity block!"); 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); 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 || (nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx && (dir_table_offset >= out->size || (dir_table_offset + out->dir_table_size) > out->size))) + if (!out->dir_table_size || dir_table_offset >= out->size || (dir_table_offset + out->dir_table_size) > out->size) { LOGFILE("Invalid RomFS directory entries table!"); 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); 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 || (nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx && (file_table_offset >= out->size || (file_table_offset + out->file_table_size) > out->size))) + if (!out->file_table_size || file_table_offset >= out->size || (file_table_offset + out->file_table_size) > out->size) { LOGFILE("Invalid RomFS file entries table!"); goto exit; @@ -137,9 +133,8 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_ } /* 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); - 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!"); goto exit;