From 8b0ed7601113d41aed0cd30786707048c0f3ef26 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Wed, 29 Jun 2022 14:41:58 +0200 Subject: [PATCH] Fix bugs + improve bktr interface. * Check for sparse layers in nsp_dumper and xml_generator PoCs before attempting to initialize content-type-specific contexts. * system_title_dumper now builds. * gamecardCloseStorageArea() no longer returns prematurely on application exit. * Fix section size for sparse sections in ncaInitializeFsSectionContext(). * Fix ncaFsSectionValidateHashDataBoundaries(). * Implement bktrInitializeIndirectStorage() and bktrInitializeAesCtrExStorage(). --- code_templates/nsp_dumper.c | 147 +++++++-------- code_templates/system_title_dumper.c | 4 +- code_templates/xml_generator.c | 5 +- include/core/bktr.h | 18 +- source/core/bktr.c | 255 ++++++++++++++++++--------- source/core/gamecard.c | 4 +- source/core/nca.c | 60 +++---- 7 files changed, 286 insertions(+), 207 deletions(-) diff --git a/code_templates/nsp_dumper.c b/code_templates/nsp_dumper.c index d0a7bd7..7d8c72f 100644 --- a/code_templates/nsp_dumper.c +++ b/code_templates/nsp_dumper.c @@ -312,85 +312,88 @@ static void dump_thread_func(void *arg) goto end; } - switch(content_info->content_type) + if (!cur_nca_ctx->fs_ctx[0].has_sparse_layer) { - case NcmContentType_Program: + switch(content_info->content_type) { - // don't proceed if we didn't allocate programinfo ctx - if (!program_count || !program_info_ctx) break; - - ProgramInfoContext *cur_program_info_ctx = &(program_info_ctx[program_idx]); - - if (!programInfoInitializeContext(cur_program_info_ctx, cur_nca_ctx)) + case NcmContentType_Program: { - consolePrint("initialize program info ctx failed (%s)\n", cur_nca_ctx->content_id_str); - goto end; + // don't proceed if we didn't allocate programinfo ctx or if we're dealing with a sparse layer + if (!program_count || !program_info_ctx) break; + + ProgramInfoContext *cur_program_info_ctx = &(program_info_ctx[program_idx]); + + if (!programInfoInitializeContext(cur_program_info_ctx, cur_nca_ctx)) + { + consolePrint("initialize program info ctx failed (%s)\n", cur_nca_ctx->content_id_str); + goto end; + } + + if (!programInfoGenerateAuthoringToolXml(cur_program_info_ctx)) + { + consolePrint("program info xml failed (%s)\n", cur_nca_ctx->content_id_str); + goto end; + } + + program_idx++; + + consolePrint("initialize program info ctx succeeded (%s)\n", cur_nca_ctx->content_id_str); + + break; } - - if (append_authoringtool_data && !programInfoGenerateAuthoringToolXml(cur_program_info_ctx)) + case NcmContentType_Control: { - consolePrint("program info xml failed (%s)\n", cur_nca_ctx->content_id_str); - goto end; + // don't proceed if we didn't allocate nacp ctx + if (!control_count || !nacp_ctx) break; + + NacpContext *cur_nacp_ctx = &(nacp_ctx[control_idx]); + + if (!nacpInitializeContext(cur_nacp_ctx, cur_nca_ctx)) + { + consolePrint("initialize nacp ctx failed (%s)\n", cur_nca_ctx->content_id_str); + goto end; + } + + if (!nacpGenerateNcaPatch(cur_nacp_ctx, patch_sua, patch_screenshot, patch_video_capture, patch_hdcp)) + { + consolePrint("nacp nca patch failed (%s)\n", cur_nca_ctx->content_id_str); + goto end; + } + + if (append_authoringtool_data && !nacpGenerateAuthoringToolXml(cur_nacp_ctx, title_info->version.value, cnmtGetRequiredTitleVersion(&cnmt_ctx))) + { + consolePrint("nacp xml failed (%s)\n", cur_nca_ctx->content_id_str); + goto end; + } + + control_idx++; + + consolePrint("initialize nacp ctx succeeded (%s)\n", cur_nca_ctx->content_id_str); + + break; } - - program_idx++; - - consolePrint("initialize program info ctx succeeded (%s)\n", cur_nca_ctx->content_id_str); - - break; + case NcmContentType_LegalInformation: + { + // don't proceed if we didn't allocate legalinfo ctx + if (!legal_info_count || !legal_info_ctx) break; + + LegalInfoContext *cur_legal_info_ctx = &(legal_info_ctx[legal_info_idx]); + + if (!legalInfoInitializeContext(cur_legal_info_ctx, cur_nca_ctx)) + { + consolePrint("initialize legal info ctx failed (%s)\n", cur_nca_ctx->content_id_str); + goto end; + } + + legal_info_idx++; + + consolePrint("initialize legal info ctx succeeded (%s)\n", cur_nca_ctx->content_id_str); + + break; + } + default: + break; } - case NcmContentType_Control: - { - // don't proceed if we didn't allocate nacp ctx - if (!control_count || !nacp_ctx) break; - - NacpContext *cur_nacp_ctx = &(nacp_ctx[control_idx]); - - if (!nacpInitializeContext(cur_nacp_ctx, cur_nca_ctx)) - { - consolePrint("initialize nacp ctx failed (%s)\n", cur_nca_ctx->content_id_str); - goto end; - } - - if (!nacpGenerateNcaPatch(cur_nacp_ctx, patch_sua, patch_screenshot, patch_video_capture, patch_hdcp)) - { - consolePrint("nacp nca patch failed (%s)\n", cur_nca_ctx->content_id_str); - goto end; - } - - if (append_authoringtool_data && !nacpGenerateAuthoringToolXml(cur_nacp_ctx, title_info->version.value, cnmtGetRequiredTitleVersion(&cnmt_ctx))) - { - consolePrint("nacp xml failed (%s)\n", cur_nca_ctx->content_id_str); - goto end; - } - - control_idx++; - - consolePrint("initialize nacp ctx succeeded (%s)\n", cur_nca_ctx->content_id_str); - - break; - } - case NcmContentType_LegalInformation: - { - // don't proceed if we didn't allocate legalinfo ctx - if (!legal_info_count || !legal_info_ctx) break; - - LegalInfoContext *cur_legal_info_ctx = &(legal_info_ctx[legal_info_idx]); - - if (!legalInfoInitializeContext(cur_legal_info_ctx, cur_nca_ctx)) - { - consolePrint("initialize legal info ctx failed (%s)\n", cur_nca_ctx->content_id_str); - goto end; - } - - legal_info_idx++; - - consolePrint("initialize legal info ctx succeeded (%s)\n", cur_nca_ctx->content_id_str); - - break; - } - default: - break; } if (!ncaEncryptHeader(cur_nca_ctx)) diff --git a/code_templates/system_title_dumper.c b/code_templates/system_title_dumper.c index 030d05e..6b1b127 100644 --- a/code_templates/system_title_dumper.c +++ b/code_templates/system_title_dumper.c @@ -98,7 +98,7 @@ static void dumpPartitionFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx) } snprintf(path, sizeof(path), OUTPATH "/%016lX - %s/%s (%s)/Section %u (%s)", info->meta_key.id, info->app_metadata->lang_entry.name, ((NcaContext*)nca_fs_ctx->nca_ctx)->content_id_str, \ - titleGetNcmContentTypeName(((NcaContext*)nca_fs_ctx->nca_ctx)->content_type), nca_fs_ctx->section_num, ncaGetFsSectionTypeName(nca_fs_ctx)); + titleGetNcmContentTypeName(((NcaContext*)nca_fs_ctx->nca_ctx)->content_type), nca_fs_ctx->section_idx, ncaGetFsSectionTypeName(nca_fs_ctx)); utilsCreateDirectoryTree(path, true); path_len = strlen(path); @@ -174,7 +174,7 @@ static void dumpRomFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx) } snprintf(path, sizeof(path), OUTPATH "/%016lX - %s/%s (%s)/Section %u (%s)", info->meta_key.id, info->app_metadata->lang_entry.name, ((NcaContext*)nca_fs_ctx->nca_ctx)->content_id_str, \ - titleGetNcmContentTypeName(((NcaContext*)nca_fs_ctx->nca_ctx)->content_type), nca_fs_ctx->section_num, ncaGetFsSectionTypeName(nca_fs_ctx)); + titleGetNcmContentTypeName(((NcaContext*)nca_fs_ctx->nca_ctx)->content_type), nca_fs_ctx->section_idx, ncaGetFsSectionTypeName(nca_fs_ctx)); utilsCreateDirectoryTree(path, true); path_len = strlen(path); diff --git a/code_templates/xml_generator.c b/code_templates/xml_generator.c index 65a39dc..08993ba 100644 --- a/code_templates/xml_generator.c +++ b/code_templates/xml_generator.c @@ -289,6 +289,8 @@ int main(int argc, char *argv[]) consolePrint("%s #%u initialize nca ctx succeeded\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset); + if (nca_ctx[j].fs_ctx[0].has_sparse_layer) continue; + switch(content_info->content_type) { case NcmContentType_Program: @@ -352,9 +354,6 @@ int main(int argc, char *argv[]) { consolePrint("cnmt xml succeeded\n"); - //sprintf(path, "sdmc:/at_xml/%016lX/%s.cnmt", app_metadata[selected_idx]->title_id, cnmt_ctx.nca_ctx->content_id_str); - //writeFile(cnmt_ctx.raw_data, cnmt_ctx.raw_data_size, path); - sprintf(path, "sdmc:/at_xml/%016lX/%s.cnmt.xml", app_metadata[selected_idx]->title_id, cnmt_ctx.nca_ctx->content_id_str); writeFile(cnmt_ctx.authoring_tool_xml, cnmt_ctx.authoring_tool_xml_size, path); } else { diff --git a/include/core/bktr.h b/include/core/bktr.h index 4958e12..a1dd68e 100644 --- a/include/core/bktr.h +++ b/include/core/bktr.h @@ -94,14 +94,15 @@ typedef struct { NXDT_ASSERT(BktrAesCtrExStorageBlock, 0x4000); 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. - u64 offset; ///< Patched RomFS image offset (relative to the start of the update NCA FS section). - u64 size; ///< Patched RomFS image size. - 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. - bool missing_base_romfs; ///< If true, only Patch RomFS data is used. Needed for games with base Program NCAs without a RomFS section (e.g. Fortnite, World of Tanks Blitz, etc.). + 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. + u64 offset; ///< Patched RomFS image offset (relative to the start of the update NCA FS section). + u64 size; ///< Patched RomFS image size. + 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. + bool missing_base_romfs; ///< If true, only Patch RomFS data is used. Needed for games with base Program NCAs without a RomFS section (e.g. Fortnite, World of Tanks Blitz, etc.). + BktrIndirectStorageBlock *base_indirect_block; ///< Base NCA Indirect Storage Block (sparse layer), if available. } BktrContext; /// Initializes a BKTR context. @@ -127,6 +128,7 @@ NX_INLINE void bktrFreeContext(BktrContext *ctx) romfsFreeContext(&(ctx->patch_romfs_ctx)); if (ctx->indirect_block) free(ctx->indirect_block); if (ctx->aes_ctr_ex_block) free(ctx->aes_ctr_ex_block); + if (ctx->base_indirect_block) free(ctx->base_indirect_block); memset(ctx, 0, sizeof(BktrContext)); } diff --git a/source/core/bktr.c b/source/core/bktr.c index 3c87868..e93ab9a 100644 --- a/source/core/bktr.c +++ b/source/core/bktr.c @@ -22,9 +22,13 @@ #include "nxdt_utils.h" #include "bktr.h" +#include "aes.h" /* Function prototypes. */ +static bool bktrInitializeIndirectStorage(NcaFsSectionContext *nca_fs_ctx, bool patch_indirect_bucket, BktrIndirectStorageBlock **out_indirect_block); +static bool bktrInitializeAesCtrExStorage(NcaFsSectionContext *nca_fs_ctx, BktrAesCtrExStorageBlock **out_aes_ctr_ex_block); + 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); @@ -37,6 +41,7 @@ static BktrAesCtrExStorageEntry *bktrGetAesCtrExStorageEntry(BktrAesCtrExStorage bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ctx, NcaFsSectionContext *update_nca_fs_ctx) { NcaContext *base_nca_ctx = NULL, *update_nca_ctx = NULL; + bool success = false, dump_patch_romfs_header = false; if (!out || !base_nca_fs_ctx || !(base_nca_ctx = (NcaContext*)base_nca_fs_ctx->nca_ctx) || \ !update_nca_fs_ctx || !update_nca_fs_ctx->enabled || !(update_nca_ctx = (NcaContext*)update_nca_fs_ctx->nca_ctx) || \ @@ -60,7 +65,6 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct /* Update missing base NCA RomFS status. */ out->missing_base_romfs = (!base_nca_fs_ctx->enabled || base_nca_fs_ctx->section_type != NcaFsSectionType_RomFs); - if (!out->missing_base_romfs) { if (!base_nca_fs_ctx->has_sparse_layer) @@ -73,90 +77,21 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct } } else { /* Initializing the base NCA RomFS on its own is impossible if we're dealing with a sparse layer. */ - /* Let's just handle everything here. */ - LOG_MSG("We got here, that's gotta be something."); - return false; + /* 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; + + /* Update the NCA FS section context from the base RomFS context. */ + out->base_romfs_ctx.nca_fs_ctx = base_nca_fs_ctx; } } - /* Fill context. */ - bool success = false, dump_patch_romfs_header = false; - NcaPatchInfo *patch_info = &(update_nca_fs_ctx->header.patch_info); + /* Initialize Patch RomFS indirect storage. */ + if (!bktrInitializeIndirectStorage(update_nca_fs_ctx, true, &(out->indirect_block))) goto end; - /* Allocate space for an extra (fake) indirect storage entry, to simplify our logic. */ - out->indirect_block = calloc(1, patch_info->indirect_bucket.size + ((0x3FF0 / sizeof(u64)) * sizeof(BktrIndirectStorageEntry))); - if (!out->indirect_block) - { - LOG_MSG("Unable to allocate memory for the BKTR Indirect Storage Block!"); - goto end; - } + /* Initialize Patch RomFS AesCtrEx storage. */ + if (!bktrInitializeAesCtrExStorage(update_nca_fs_ctx, &(out->aes_ctr_ex_block))) goto end; - /* Read indirect storage block data. */ - if (!ncaReadFsSection(update_nca_fs_ctx, out->indirect_block, patch_info->indirect_bucket.size, patch_info->indirect_bucket.offset)) - { - LOG_MSG("Failed to read BKTR Indirect Storage Block data!"); - goto end; - } - - /* 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_bucket.size + (((0x3FF0 / sizeof(u64)) + 1) * sizeof(BktrAesCtrExStorageEntry))); - if (!out->aes_ctr_ex_block) - { - LOG_MSG("Unable to allocate memory for the BKTR AesCtrEx Storage Block!"); - goto end; - } - - /* Read AesCtrEx storage block data. */ - if (!ncaReadFsSection(update_nca_fs_ctx, out->aes_ctr_ex_block, patch_info->aes_ctr_ex_bucket.size, patch_info->aes_ctr_ex_bucket.offset)) - { - LOG_MSG("Failed to read BKTR AesCtrEx Storage Block data!"); - goto end; - } - - if (out->aes_ctr_ex_block->physical_size != patch_info->aes_ctr_ex_bucket.offset) - { - LOG_DATA(out->aes_ctr_ex_block, patch_info->aes_ctr_ex_bucket.size, "Invalid BKTR AesCtrEx Storage Block size! AesCtrEx Storage Block dump:"); - goto end; - } - - /* 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); - BktrAesCtrExStorageBucket *last_aes_ctr_ex_bucket = bktrGetAesCtrExStorageBucket(out->aes_ctr_ex_block, out->aes_ctr_ex_block->bucket_count - 1); - last_indirect_bucket->indirect_storage_entries[last_indirect_bucket->entry_count].virtual_offset = out->indirect_block->virtual_size; - last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count].offset = patch_info->indirect_bucket.offset; - last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count].generation = update_nca_fs_ctx->header.aes_ctr_upper_iv.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. */ + /* Initialize Patch 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; @@ -169,7 +104,7 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct out->patch_romfs_ctx.offset = out->offset; out->patch_romfs_ctx.size = out->size; - /* Read update NCA RomFS header. */ + /* Read Patch RomFS header. */ if (!bktrPhysicalSectionRead(out, &(out->patch_romfs_ctx.header), sizeof(RomFileSystemHeader), out->patch_romfs_ctx.offset)) { LOG_MSG("Failed to read update NCA RomFS header!"); @@ -183,7 +118,7 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct goto end; } - /* Read directory entries table. */ + /* Read Patch RomFS 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; @@ -207,7 +142,7 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct goto end; } - /* Read file entries table. */ + /* Read Patch RomFS 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; @@ -231,7 +166,7 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct goto end; } - /* Get file data body offset. */ + /* Get Patch RomFS file data body offset. */ out->patch_romfs_ctx.body_offset = out->body_offset = out->patch_romfs_ctx.header.cur_format.body_offset; success = true; @@ -240,7 +175,6 @@ end: if (!success) { if (dump_patch_romfs_header) LOG_DATA(&(out->patch_romfs_ctx.header), sizeof(RomFileSystemHeader), "Update RomFS header dump:"); - bktrFreeContext(out); } @@ -321,9 +255,140 @@ bool bktrIsFileEntryUpdated(BktrContext *ctx, RomFileSystemFileEntry *file_entry return true; } +static bool bktrInitializeIndirectStorage(NcaFsSectionContext *nca_fs_ctx, bool patch_indirect_bucket, BktrIndirectStorageBlock **out_indirect_block) +{ + if (!nca_fs_ctx || (patch_indirect_bucket && nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs) || \ + (!patch_indirect_bucket && (nca_fs_ctx->section_type != NcaFsSectionType_RomFs || !nca_fs_ctx->has_sparse_layer)) || !out_indirect_block) + { + LOG_MSG("Invalid parameters!"); + return false; + } + + NcaBucketInfo *indirect_bucket = (patch_indirect_bucket ? &(nca_fs_ctx->header.patch_info.indirect_bucket) : &(nca_fs_ctx->header.sparse_info.bucket)); + BktrIndirectStorageBlock *indirect_block = NULL; + bool success = false; + + /* Allocate space for an extra (fake) indirect storage entry, to simplify our logic. */ + indirect_block = calloc(1, indirect_bucket->size + ((0x3FF0 / sizeof(u64)) * sizeof(BktrIndirectStorageEntry))); + if (!indirect_block) + { + LOG_MSG("Unable to allocate memory for the BKTR Indirect Storage Block! (%s).", patch_indirect_bucket ? "patch" : "sparse"); + goto end; + } + + /* Read indirect storage block data. */ + if ((patch_indirect_bucket && !ncaReadFsSection(nca_fs_ctx, indirect_block, indirect_bucket->size, indirect_bucket->offset)) || \ + (!patch_indirect_bucket && !ncaReadContentFile((NcaContext*)nca_fs_ctx->nca_ctx, indirect_block, nca_fs_ctx->sparse_table_size, nca_fs_ctx->sparse_table_offset))) + { + LOG_MSG("Failed to read BKTR Indirect Storage Block data! (%s).", patch_indirect_bucket ? "patch" : "sparse"); + goto end; + } + + /* Decrypt indirect block, if needed. */ + if (!patch_indirect_bucket) + { + aes128CtrUpdatePartialCtr(nca_fs_ctx->sparse_ctr, nca_fs_ctx->sparse_table_offset); + aes128CtrContextResetCtr(&(nca_fs_ctx->sparse_ctr_ctx), nca_fs_ctx->sparse_ctr); + aes128CtrCrypt(&(nca_fs_ctx->sparse_ctr_ctx), indirect_block, indirect_block, nca_fs_ctx->sparse_table_size); + } + + /* This simplifies logic greatly... */ + for(u32 i = (indirect_block->bucket_count - 1); i > 0; i--) + { + BktrIndirectStorageBucket tmp_bucket = {0}; + memcpy(&tmp_bucket, &(indirect_block->indirect_storage_buckets[i]), sizeof(BktrIndirectStorageBucket)); + memcpy(bktrGetIndirectStorageBucket(indirect_block, i), &tmp_bucket, sizeof(BktrIndirectStorageBucket)); + } + + for(u32 i = 0; (i + 1) < indirect_block->bucket_count; i++) + { + BktrIndirectStorageBucket *cur_bucket = bktrGetIndirectStorageBucket(indirect_block, i); + cur_bucket->indirect_storage_entries[cur_bucket->entry_count].virtual_offset = indirect_block->virtual_offsets[i + 1]; + } + + BktrIndirectStorageBucket *last_indirect_bucket = bktrGetIndirectStorageBucket(indirect_block, indirect_block->bucket_count - 1); + last_indirect_bucket->indirect_storage_entries[last_indirect_bucket->entry_count].virtual_offset = indirect_block->virtual_size; + + /* Update output values. */ + *out_indirect_block = indirect_block; + success = true; + +end: + if (!success && indirect_block) free(indirect_block); + + return success; +} + +static bool bktrInitializeAesCtrExStorage(NcaFsSectionContext *nca_fs_ctx, BktrAesCtrExStorageBlock **out_aes_ctr_ex_block) +{ + if (!nca_fs_ctx || nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs || !out_aes_ctr_ex_block) + { + LOG_MSG("Invalid parameters!"); + return false; + } + + NcaBucketInfo *aes_ctr_ex_bucket = &(nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket); + BktrAesCtrExStorageBlock *aes_ctr_ex_block = NULL; + bool success = false; + + /* Allocate space for an extra (fake) AesCtrEx storage entry, to simplify our logic. */ + aes_ctr_ex_block = calloc(1, aes_ctr_ex_bucket->size + (((0x3FF0 / sizeof(u64)) + 1) * sizeof(BktrAesCtrExStorageEntry))); + if (!aes_ctr_ex_block) + { + LOG_MSG("Unable to allocate memory for the BKTR AesCtrEx Storage Block!"); + goto end; + } + + /* Read AesCtrEx storage block data. */ + if (!ncaReadFsSection(nca_fs_ctx, aes_ctr_ex_block, aes_ctr_ex_bucket->size, aes_ctr_ex_bucket->offset)) + { + LOG_MSG("Failed to read BKTR AesCtrEx Storage Block data!"); + goto end; + } + + if (aes_ctr_ex_block->physical_size != aes_ctr_ex_bucket->offset) + { + LOG_DATA(aes_ctr_ex_block, aes_ctr_ex_bucket->size, "Invalid BKTR AesCtrEx Storage Block size! AesCtrEx Storage Block dump:"); + goto end; + } + + /* This simplifies logic greatly... */ + for(u32 i = (aes_ctr_ex_block->bucket_count - 1); i > 0; i--) + { + BktrAesCtrExStorageBucket tmp_bucket = {0}; + memcpy(&tmp_bucket, &(aes_ctr_ex_block->aes_ctr_ex_storage_buckets[i]), sizeof(BktrAesCtrExStorageBucket)); + memcpy(bktrGetAesCtrExStorageBucket(aes_ctr_ex_block, i), &tmp_bucket, sizeof(BktrAesCtrExStorageBucket)); + } + + for(u32 i = 0; (i + 1) < aes_ctr_ex_block->bucket_count; i++) + { + BktrAesCtrExStorageBucket *cur_bucket = bktrGetAesCtrExStorageBucket(aes_ctr_ex_block, i); + BktrAesCtrExStorageBucket *next_bucket = bktrGetAesCtrExStorageBucket(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; + } + + BktrAesCtrExStorageBucket *last_aes_ctr_ex_bucket = bktrGetAesCtrExStorageBucket(aes_ctr_ex_block, 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 = nca_fs_ctx->header.patch_info.indirect_bucket.offset; + last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count].generation = nca_fs_ctx->header.aes_ctr_upper_iv.generation; + last_aes_ctr_ex_bucket->aes_ctr_ex_storage_entries[last_aes_ctr_ex_bucket->entry_count + 1].offset = 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; + + /* Update output values. */ + *out_aes_ctr_ex_block = aes_ctr_ex_block; + success = true; + +end: + if (!success && aes_ctr_ex_block) free(aes_ctr_ex_block); + + return success; +} + static bool bktrPhysicalSectionRead(BktrContext *ctx, void *out, u64 read_size, u64 offset) { - if (!ctx || (!ctx->missing_base_romfs && !ctx->base_romfs_ctx.nca_fs_ctx) || !ctx->indirect_block || !out || !read_size) + if (!ctx || (!ctx->missing_base_romfs && ((!ctx->base_romfs_ctx.nca_fs_ctx->has_sparse_layer && !ctx->base_romfs_ctx.nca_fs_ctx) || \ + (ctx->base_romfs_ctx.nca_fs_ctx->has_sparse_layer && !ctx->base_indirect_block))) || !ctx->indirect_block || !out || !read_size) { LOG_MSG("Invalid parameters!"); return false; @@ -357,8 +422,22 @@ static bool bktrPhysicalSectionRead(BktrContext *ctx, void *out, u64 read_size, } else if (!ctx->missing_base_romfs) { - 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 (!ctx->base_romfs_ctx.nca_fs_ctx->has_sparse_layer) + { + success = ncaReadFsSection(ctx->base_romfs_ctx.nca_fs_ctx, out, read_size, section_offset); + if (!success) LOG_MSG("Failed to read 0x%lX bytes block from base RomFS at offset 0x%lX!", read_size, section_offset); + } else { + + + + //ctx->base_indirect_block + + + LOG_MSG("Attempting to read 0x%lX bytes block from sparse layer at offset 0x%lX!", read_size, section_offset); + + + + } } else { LOG_MSG("Attempting to read 0x%lX bytes block from non-existent base RomFS at offset 0x%lX!", read_size, section_offset); } diff --git a/source/core/gamecard.c b/source/core/gamecard.c index 7049e86..67f047b 100644 --- a/source/core/gamecard.c +++ b/source/core/gamecard.c @@ -838,6 +838,8 @@ end: static void gamecardFreeInfo(bool clear_status) { + gamecardCloseStorageArea(true); + memset(&g_gameCardHeader, 0, sizeof(GameCardHeader)); memset(&g_gameCardInfoArea, 0, sizeof(GameCardInfo)); @@ -864,8 +866,6 @@ static void gamecardFreeInfo(bool clear_status) g_gameCardHfsCount = 0; - gamecardCloseStorageArea(true); - if (clear_status) g_gameCardStatus = GameCardStatus_NotInserted; } diff --git a/source/core/nca.c b/source/core/nca.c index 11827a7..9d3c23a 100644 --- a/source/core/nca.c +++ b/source/core/nca.c @@ -708,19 +708,17 @@ static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx) bool success = false; - /* Clear FS section context. */ - memset(fs_ctx, 0, sizeof(NcaFsSectionContext)); - /* Fill section context. */ fs_ctx->nca_ctx = nca_ctx; fs_ctx->section_idx = section_idx; fs_ctx->section_type = NcaFsSectionType_Invalid; /* Placeholder. */ fs_ctx->has_sparse_layer = (sparse_info->generation != 0); + fs_ctx->enabled = false; /* Don't proceed if this NCA FS section isn't populated. */ if (!ncaIsFsInfoEntryValid(fs_info)) { - LOG_MSG("Invalid FsInfo entry for section #%u in \"%s\". Skipping FS section.", section_idx, nca_ctx->content_id_str); + //LOG_MSG("Invalid FsInfo entry for section #%u in \"%s\". Skipping FS section.", section_idx, nca_ctx->content_id_str); goto end; } @@ -828,8 +826,7 @@ static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx) } /* Update section size. */ - fs_ctx->section_size = (MIN(fs_ctx->section_offset, raw_storage_offset) + (MAX(fs_ctx->section_offset, raw_storage_offset) - \ - MIN(fs_ctx->section_offset, raw_storage_offset)) + raw_storage_size); + fs_ctx->section_size = raw_storage_size; } /* Check if we're within boundaries. */ @@ -839,24 +836,6 @@ static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx) goto end; } - /* Validate HashData boundaries. */ - if (!ncaFsSectionValidateHashDataBoundaries(fs_ctx)) goto end; - - /* Get hash layer region size (offset must always be 0). */ - fs_ctx->hash_region.offset = 0; - if (!ncaGetFsSectionHashTargetProperties(fs_ctx, NULL, &(fs_ctx->hash_region.size))) - { - LOG_MSG("Invalid hash type for FS section #%u in \"%s\" (0x%02X). Skipping FS section.", fs_ctx->section_idx, nca_ctx->content_id_str, fs_ctx->hash_type); - goto end; - } - - /* Check if we're within boundaries. */ - if (fs_ctx->hash_region.size > fs_ctx->section_size || (fs_ctx->section_offset + fs_ctx->hash_region.size) > nca_ctx->content_size) - { - LOG_MSG("Hash layer region for FS section #%u in \"%s\" is out of NCA boundaries. Skipping FS section.", section_idx, nca_ctx->content_id_str); - goto end; - } - /* Determine FS section type. */ /* TODO: should NcaHashType_None be handled here as well? */ switch(fs_ctx->header.fs_type) @@ -906,6 +885,24 @@ static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx) goto end; } + /* Validate HashData boundaries. */ + if (!ncaFsSectionValidateHashDataBoundaries(fs_ctx)) goto end; + + /* Get hash layer region size (offset must always be 0). */ + fs_ctx->hash_region.offset = 0; + if (!ncaGetFsSectionHashTargetProperties(fs_ctx, &(fs_ctx->hash_region.size), NULL)) + { + LOG_MSG("Invalid hash type for FS section #%u in \"%s\" (0x%02X). Skipping FS section.", fs_ctx->section_idx, nca_ctx->content_id_str, fs_ctx->hash_type); + goto end; + } + + /* Check if we're within boundaries. */ + if (fs_ctx->hash_region.size > fs_ctx->section_size || (fs_ctx->section_offset + fs_ctx->hash_region.size) > nca_ctx->content_size) + { + LOG_MSG("Hash layer region for FS section #%u in \"%s\" is out of NCA boundaries. Skipping FS section.", section_idx, nca_ctx->content_id_str); + goto end; + } + /* Check if we should skip hash layer decryption while reading this FS section. */ fs_ctx->skip_hash_layer_crypto = (fs_ctx->encryption_type == NcaEncryptionType_AesCtrSkipLayerHash || fs_ctx->encryption_type == NcaEncryptionType_AesCtrExSkipLayerHash); if (fs_ctx->skip_hash_layer_crypto && fs_ctx->hash_type == NcaHashType_None) @@ -927,7 +924,6 @@ static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx) NcaAesCtrUpperIv sparse_upper_iv = {0}; memcpy(sparse_upper_iv.value, fs_ctx->header.aes_ctr_upper_iv.value, sizeof(sparse_upper_iv.value)); sparse_upper_iv.generation = ((u32)(sparse_info->generation) << 16); - aes128CtrInitializePartialCtr(fs_ctx->sparse_ctr, sparse_upper_iv.value, fs_ctx->sparse_table_offset); } @@ -987,7 +983,7 @@ static bool ncaFsSectionValidateHashDataBoundaries(NcaFsSectionContext *ctx) { /* Validate all hash regions boundaries. Skip the last one if a sparse layer is used. */ NcaRegion *hash_region = &(hash_data->hash_region[i]); - if (hash_region->offset != accum || !hash_region->size || \ + if (hash_region->offset < accum || !hash_region->size || \ ((i < (hash_data->hash_region_count - 1) || !ctx->has_sparse_layer) && (hash_region->offset + hash_region->size) > ctx->section_size)) { LOG_MSG("HierarchicalSha256 region #%u for FS section #%u in \"%s\" is out of NCA boundaries. Skipping FS section.", \ @@ -996,7 +992,7 @@ static bool ncaFsSectionValidateHashDataBoundaries(NcaFsSectionContext *ctx) break; } - accum += hash_region->size; + accum = (hash_region->offset + hash_region->size); } success = valid; @@ -1017,10 +1013,10 @@ static bool ncaFsSectionValidateHashDataBoundaries(NcaFsSectionContext *ctx) for(u32 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++) { - /* Validate all level informations boundaries. Skip the last one if a sparse layer is used. */ - NcaHierarchicalIntegrityVerificationLevelInformation *level_information = &(hash_data->info_level_hash.level_information[i]); - if (level_information->offset != accum || !level_information->size || !level_information->block_order || \ - ((i < (NCA_IVFC_LEVEL_COUNT - 1) || !ctx->has_sparse_layer) && (level_information->offset + level_information->size) > ctx->section_size)) + /* Validate all level informations boundaries. Skip the last one if we're dealing with a Patch RomFS, or if a sparse layer is used. */ + NcaHierarchicalIntegrityVerificationLevelInformation *lvl_info = &(hash_data->info_level_hash.level_information[i]); + if (lvl_info->offset < accum || !lvl_info->size || !lvl_info->block_order || ((i < (NCA_IVFC_LEVEL_COUNT - 1) || \ + (!ctx->has_sparse_layer && ctx->section_type != NcaFsSectionType_PatchRomFs)) && (lvl_info->offset + lvl_info->size) > ctx->section_size)) { LOG_MSG("HierarchicalIntegrity level #%u for FS section #%u in \"%s\" is out of NCA boundaries. Skipping FS section.", \ i, ctx->section_idx, nca_ctx->content_id_str); @@ -1028,7 +1024,7 @@ static bool ncaFsSectionValidateHashDataBoundaries(NcaFsSectionContext *ctx) break; } - accum += level_information->size; + accum = (lvl_info->offset + lvl_info->size); } success = valid;