From bafe23b14edc706a353e8e642f06a15b7a87dbcc Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Tue, 30 May 2023 01:22:12 +0200 Subject: [PATCH] poc: implement PFS dumping (user titles only). Other changes include: * nca: add title_id and title_type fields to NcaContext; redefine title_version field in NcaContext; redefine ncaInitializeContext() function signature (now requiring NcmContentMetaKey and NcmContentInfo objects); modify ncaInitializeFsSectionContext() to make it skip extra boundary checks on FS sections with a bogus sparse storage. * config: rename "nsp/write_section_image" flag to "nsp/write_raw_section". * nxdt_utils: modify utilsGeneratePath() to make it also check if the first character from the filename is a slash. * pfs: modify pfsGetEntryIndexByName() to only log an error if the entry name being looked up isn't "main.npdm". --- code_templates/nxdt_rw_poc.c | 792 +++++++++++++++++++++++++-- code_templates/sd_romfs_dumper.c | 4 +- code_templates/system_title_dumper.c | 2 +- code_templates/usb_romfs_dumper.c | 4 +- code_templates/xml_generator.c | 8 +- include/core/nca.h | 8 +- romfs/default_config.json | 2 +- source/core/bfttf.c | 4 +- source/core/config.c | 6 +- source/core/nca.c | 30 +- source/core/nca_storage.c | 2 +- source/core/nxdt_bfsar.c | 2 +- source/core/nxdt_utils.c | 2 +- source/core/pfs.c | 2 +- source/core/title.c | 2 +- 15 files changed, 785 insertions(+), 85 deletions(-) diff --git a/code_templates/nxdt_rw_poc.c b/code_templates/nxdt_rw_poc.c index cc6f30f..8ccdfc6 100644 --- a/code_templates/nxdt_rw_poc.c +++ b/code_templates/nxdt_rw_poc.c @@ -122,6 +122,18 @@ typedef struct { NcaContext *nca_ctx; } NcaThreadData; +typedef struct { + SharedThreadData shared_thread_data; + PartitionFileSystemContext *pfs_ctx; + bool use_layeredfs_dir; +} PfsThreadData; + +typedef struct { + SharedThreadData shared_thread_data; + RomFileSystemContext *romfs_ctx; + bool use_layeredfs_dir; +} RomFsThreadData; + /* Function prototypes. */ static void utilsScanPads(void); @@ -157,6 +169,7 @@ static bool waitForUsb(void); static char *generateOutputGameCardFileName(const char *subdir, const char *extension, bool use_nacp_name); static char *generateOutputTitleFileName(TitleInfo *title_info, const char *subdir, const char *extension); +static char *generateOutputLayeredFsFileName(u64 title_id, const char *subdir, const char *extension); static bool dumpGameCardSecurityInformation(GameCardSecurityInformation *out); @@ -178,6 +191,13 @@ static bool saveNintendoSubmissionPackage(void *userdata); static bool saveTicket(void *userdata); static bool saveNintendoContentArchive(void *userdata); +static bool saveNintendoContentArchiveFsSection(void *userdata); + +static bool saveRawPartitionFsSection(PartitionFileSystemContext *pfs_ctx, bool use_layeredfs_dir); +static bool saveExtractedPartitionFsSection(PartitionFileSystemContext *pfs_ctx, bool use_layeredfs_dir); + +//static bool saveRawRomFsSection(RomFileSystemContext *romfs_ctx, bool use_layeredfs_dir); +//static bool saveExtractedRomFsSection(RomFileSystemContext *romfs_ctx, bool use_layeredfs_dir); static void xciReadThreadFunc(void *arg); @@ -186,6 +206,12 @@ static void extractedHfsReadThreadFunc(void *arg); static void ncaReadThreadFunc(void *arg); +static void rawPartitionFsReadThreadFunc(void *arg); +static void extractedPartitionFsReadThreadFunc(void *arg); + +//static void rawRomFsReadThreadFunc(void *arg); +//static void extractedRomFsReadThreadFunc(void *arg); + static void genericWriteThreadFunc(void *arg); static bool spanDumpThreads(ThreadFunc read_func, ThreadFunc write_func, void *arg); @@ -237,8 +263,8 @@ static void setNspAppendAuthoringToolDataOption(u32 idx); static u32 getTicketRemoveConsoleDataOption(void); static void setTicketRemoveConsoleDataOption(u32 idx); -static u32 getNcaFsWriteSectionImageOption(void); -static void setNcaFsWriteSectionImageOption(u32 idx); +static u32 getNcaFsWriteRawSectionOption(void); +static void setNcaFsWriteRawSectionOption(u32 idx); static u32 getNcaFsUseLayeredFsDirOption(void); static void setNcaFsUseLayeredFsDirOption(u32 idx); @@ -618,7 +644,7 @@ static Menu g_ticketMenu = { .elements = g_ticketMenuElements }; -static TitleInfo *g_ncaBasePatchTitleInfo = NULL; +static TitleInfo *g_ncaUserTitleInfo = NULL, *g_ncaBasePatchTitleInfo = NULL, *g_ncaBasePatchTitleInfoBkp = NULL; static char **g_ncaBasePatchOptions = NULL; static MenuElementOption g_ncaFsSectionsSubMenuBasePatchElementOption = { @@ -632,7 +658,7 @@ static MenuElement *g_ncaFsSectionsSubMenuElements[] = { &(MenuElement){ .str = "start nca fs dump", .child_menu = NULL, - .task_func = NULL, // TODO: implement nca fs dump function -- additional sparse/patch checks will go here + .task_func = &saveNintendoContentArchiveFsSection, .element_options = NULL, .userdata = NULL // Dynamically set }, @@ -644,13 +670,13 @@ static MenuElement *g_ncaFsSectionsSubMenuElements[] = { .userdata = NULL }, &(MenuElement){ - .str = "write section image", + .str = "write raw section", .child_menu = NULL, .task_func = NULL, .element_options = &(MenuElementOption){ .selected = 0, - .getter_func = &getNcaFsWriteSectionImageOption, - .setter_func = &setNcaFsWriteSectionImageOption, + .getter_func = &getNcaFsWriteRawSectionOption, + .setter_func = &setNcaFsWriteRawSectionOption, .options = g_noYesStrings }, .userdata = NULL @@ -1212,13 +1238,23 @@ int main(int argc, char *argv[]) } else if ((btn_down & (HidNpadButton_Right | HidNpadButton_StickLRight | HidNpadButton_StickRRight)) && selected_element_options) { + /* Point to the next base/patch title. */ + if (cur_menu->id == MenuId_NcaFsSectionsSubMenu && cur_menu->selected == 1) + { + if (selected_element_options->selected == 0 && g_ncaBasePatchTitleInfoBkp) + { + g_ncaBasePatchTitleInfo = g_ncaBasePatchTitleInfoBkp; + g_ncaBasePatchTitleInfoBkp = NULL; + } else + if (selected_element_options->selected > 0 && g_ncaBasePatchTitleInfo && g_ncaBasePatchTitleInfo->next) + { + g_ncaBasePatchTitleInfo = g_ncaBasePatchTitleInfo->next; + } + } + selected_element_options->selected++; if (!selected_element_options->options[selected_element_options->selected]) selected_element_options->selected--; if (selected_element_options->setter_func) selected_element_options->setter_func(selected_element_options->selected); - - /* Point to the next base/patch title. */ - if (cur_menu->id == MenuId_NcaFsSectionsSubMenu && cur_menu->selected == 1 && g_ncaBasePatchTitleInfo && g_ncaBasePatchTitleInfo->next) - g_ncaBasePatchTitleInfo = g_ncaBasePatchTitleInfo->next; } else if ((btn_down & (HidNpadButton_Left | HidNpadButton_StickLLeft | HidNpadButton_StickRLeft)) && selected_element_options) { @@ -1227,8 +1263,18 @@ int main(int argc, char *argv[]) if (selected_element_options->setter_func) selected_element_options->setter_func(selected_element_options->selected); /* Point to the previous base/patch title. */ - if (cur_menu->id == MenuId_NcaFsSectionsSubMenu && cur_menu->selected == 1 && g_ncaBasePatchTitleInfo && g_ncaBasePatchTitleInfo->previous) - g_ncaBasePatchTitleInfo = g_ncaBasePatchTitleInfo->previous; + if (cur_menu->id == MenuId_NcaFsSectionsSubMenu && cur_menu->selected == 1) + { + if (selected_element_options->selected == 0 && g_ncaBasePatchTitleInfo) + { + g_ncaBasePatchTitleInfoBkp = g_ncaBasePatchTitleInfo; + g_ncaBasePatchTitleInfo = NULL; + } else + if (selected_element_options->selected > 0 && g_ncaBasePatchTitleInfo && g_ncaBasePatchTitleInfo->previous) + { + g_ncaBasePatchTitleInfo = g_ncaBasePatchTitleInfo->previous; + } + } } else if ((btn_down & HidNpadButton_B) && cur_menu->parent) { @@ -1704,7 +1750,7 @@ void updateNcaFsSectionsList(NcaUserData *nca_user_data) /* Initialize NCA context. */ g_ncaFsSectionsMenuCtx = calloc(1, sizeof(NcaContext)); if (!ncaInitializeContext(g_ncaFsSectionsMenuCtx, title_info->storage_id, (title_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ - content_info, title_info->version.value, NULL)) return; + &(title_info->meta_key), content_info, NULL)) return; /* Generate menu elements. */ for(u32 i = 0; i < NCA_FS_HEADER_COUNT; i++) @@ -1764,7 +1810,7 @@ void freeNcaBasePatchList(void) titleFreeTitleInfo(&g_ncaBasePatchTitleInfo); } - g_ncaBasePatchTitleInfo = NULL; + g_ncaUserTitleInfo = g_ncaBasePatchTitleInfo = g_ncaBasePatchTitleInfoBkp = NULL; } void updateNcaBasePatchList(TitleUserApplicationData *user_app_data, TitleInfo *title_info, NcaFsSectionContext *nca_fs_ctx) @@ -1778,11 +1824,14 @@ void updateNcaBasePatchList(TitleUserApplicationData *user_app_data, TitleInfo * u8 section_type = nca_fs_ctx->section_type; bool unsupported = false; + u32 selected_version = 0; + /* Free all previously allocated data. */ freeNcaBasePatchList(); /* Only enable base/patch list if we're dealing with supported content types and/or FS section types. */ - if (content_type != NcmContentType_Meta && content_type != NcmContentType_Control && section_type != NcaFsSectionType_Invalid && section_type != NcaFsSectionType_PartitionFs) + if ((content_type == NcmContentType_Program || content_type == NcmContentType_Data || content_type == NcmContentType_HtmlDocument) && \ + section_type < NcaFsSectionType_Nca0RomFs && (section_type != NcaFsSectionType_PartitionFs || nca_fs_ctx->has_sparse_layer)) { /* Retrieve corresponding TitleInfo linked list for the current title type. */ switch(title_type) @@ -1816,7 +1865,7 @@ void updateNcaBasePatchList(TitleUserApplicationData *user_app_data, TitleInfo * memset(g_ncaBasePatchOptions, 0, (elem_count + 1) * sizeof(char*)); // NULL terminator /* Set first option. */ - g_ncaBasePatchOptions[0] = (unsupported ? "unsupported for this content/section type" : (elem_count < 2 ? "none available" : "no")); + g_ncaBasePatchOptions[0] = (unsupported ? "unsupported by this content/section type combo" : (elem_count < 2 ? "none available" : "no")); /* Generate base/patch strings. */ cur_title_info = g_ncaBasePatchTitleInfo; @@ -1836,6 +1885,16 @@ void updateNcaBasePatchList(TitleUserApplicationData *user_app_data, TitleInfo * cur_title_info->version.value, cur_title_info->version.application_version.release_ver, cur_title_info->version.application_version.private_ver, \ titleGetNcmStorageIdName(cur_title_info->storage_id)); + /* Make sure the highest available base/patch title is automatically selected. */ + if (cur_title_info->version.value >= selected_version && \ + (((title_type == NcmContentMetaType_Application || title_type == NcmContentMetaType_AddOnContent) && (!nca_fs_ctx->has_sparse_layer || cur_title_info->version.value >= title_info->version.value)) || \ + ((title_type == NcmContentMetaType_Patch || title_type == NcmContentMetaType_DataPatch) && cur_title_info->version.value <= title_info->version.value))) + { + g_ncaFsSectionsSubMenuBasePatchElementOption.selected = idx; + selected_version = cur_title_info->version.value; + g_ncaBasePatchTitleInfo = cur_title_info; + } + cur_title_info = cur_title_info->next; idx++; @@ -1844,6 +1903,10 @@ void updateNcaBasePatchList(TitleUserApplicationData *user_app_data, TitleInfo * g_ncaFsSectionsSubMenuBasePatchElementOption.options = g_ncaBasePatchOptions; g_ncaFsSectionsSubMenuElements[0]->userdata = nca_fs_ctx; + + g_ncaUserTitleInfo = title_info; + + g_ncaBasePatchTitleInfoBkp = (g_ncaFsSectionsSubMenuBasePatchElementOption.selected > 0 ? g_ncaBasePatchTitleInfo : NULL); } NX_INLINE bool useUsbHost(void) @@ -1999,17 +2062,12 @@ static char *generateOutputGameCardFileName(const char *subdir, const char *exte goto end; } - if (dev_idx == 1) - { - if (subdir) sprintf(prefix, "/%s", subdir); - } else { - sprintf(prefix, "%s/" OUTDIR, dev_idx == 0 ? DEVOPTAB_SDMC_DEVICE : g_umsDevices[dev_idx - 2].name); + if (dev_idx != 1) sprintf(prefix, "%s/" OUTDIR, dev_idx == 0 ? DEVOPTAB_SDMC_DEVICE : g_umsDevices[dev_idx - 2].name); - if (subdir) - { - if (subdir[0] != '/') strcat(prefix, "/"); - strcat(prefix, subdir); - } + if (subdir) + { + if (subdir[0] != '/') strcat(prefix, "/"); + strcat(prefix, subdir); } output = (use_nacp_name ? utilsGeneratePath(prefix, filename, extension) : utilsGeneratePath(prefix, extension, NULL)); @@ -2040,17 +2098,12 @@ static char *generateOutputTitleFileName(TitleInfo *title_info, const char *subd goto end; } - if (dev_idx == 1) - { - if (subdir) sprintf(prefix, "/%s", subdir); - } else { - sprintf(prefix, "%s/" OUTDIR, dev_idx == 0 ? DEVOPTAB_SDMC_DEVICE : g_umsDevices[dev_idx - 2].name); + if (dev_idx != 1) sprintf(prefix, "%s/" OUTDIR, dev_idx == 0 ? DEVOPTAB_SDMC_DEVICE : g_umsDevices[dev_idx - 2].name); - if (subdir) - { - if (subdir[0] != '/') strcat(prefix, "/"); - strcat(prefix, subdir); - } + if (subdir) + { + if (subdir[0] != '/') strcat(prefix, "/"); + strcat(prefix, subdir); } output = utilsGeneratePath(prefix, filename, extension); @@ -2063,17 +2116,42 @@ end: return output; } +static char *generateOutputLayeredFsFileName(u64 title_id, const char *subdir, const char *extension) +{ + char *prefix = NULL, *output = NULL; + u32 dev_idx = g_storageMenuElementOption.selected; + if ((subdir && !*subdir) || !extension || !*extension) + { + consolePrint("failed to generate title filename!\n"); + goto end; + } + prefix = calloc(sizeof(char), FS_MAX_PATH); + if (!prefix) + { + consolePrint("failed to generate prefix!\n"); + goto end; + } + if (dev_idx != 1) sprintf(prefix, "%s", dev_idx == 0 ? DEVOPTAB_SDMC_DEVICE : g_umsDevices[dev_idx - 2].name); + sprintf(prefix + strlen(prefix), "/atmosphere/contents/%016lX", title_id); + if (subdir) + { + if (subdir[0] != '/') strcat(prefix, "/"); + strcat(prefix, subdir); + } + output = utilsGeneratePath(prefix, extension, NULL); + if (!output) consolePrint("failed to generate output filename!\n"); +end: + if (prefix) free(prefix); - - - + return output; +} static bool dumpGameCardSecurityInformation(GameCardSecurityInformation *out) { @@ -2818,7 +2896,7 @@ static bool saveTicket(void *userdata) /* Initialize NCA context. */ if (!ncaInitializeContext(nca_ctx, title_info->storage_id, (title_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ - content_info, title_info->version.value, &tik)) + &(title_info->meta_key), content_info, &tik)) { consolePrint("nca initialize ctx failed\n"); goto end; @@ -2902,7 +2980,7 @@ static bool saveNintendoContentArchive(void *userdata) /* Initialize NCA context. */ if (!ncaInitializeContext(nca_thread_data.nca_ctx, title_info->storage_id, (title_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ - content_info, title_info->version.value, NULL)) + &(title_info->meta_key), content_info, NULL)) { consolePrint("nca initialize ctx failed\n"); goto end; @@ -2997,6 +3075,320 @@ end: return success; } + +static bool saveNintendoContentArchiveFsSection(void *userdata) +{ + NcaFsSectionContext *nca_fs_ctx = (NcaFsSectionContext*)userdata; + NcaContext *nca_ctx = (nca_fs_ctx ? nca_fs_ctx->nca_ctx : NULL); + + /* Sanity checks. */ + + if (!g_ncaUserTitleInfo || !nca_fs_ctx || !nca_ctx || !nca_fs_ctx->enabled || nca_fs_ctx->section_type > NcaFsSectionType_Nca0RomFs || \ + (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs && g_ncaBasePatchTitleInfo)) + { + consolePrint("invalid nca fs parameters!\n"); + return false; + } + + if (nca_fs_ctx->has_sparse_layer) + { + if (!g_ncaBasePatchTitleInfo) + { + consolePrint("the selected nca fs section holds a sparse storage\na matching patch of at least v%u must be selected\n", nca_ctx->title_version.value); + return false; + } else + if (g_ncaBasePatchTitleInfo->version.value < nca_ctx->title_version.value) + { + consolePrint("the selected patch doesn't meet the sparse storage version requirement!\nv%u < v%u\n", g_ncaBasePatchTitleInfo->version.value, nca_ctx->title_version.value); + return false; + } + } + + if (nca_fs_ctx->section_type == NcaFsSectionType_PatchRomFs && !g_ncaBasePatchTitleInfo) + { + consolePrint("patch romfs section selected but no base app provided\n"); + return false; + } + + u8 title_type = nca_ctx->title_type; + u8 content_type = nca_ctx->content_type; + u8 section_type = nca_fs_ctx->section_type; + + NcmContentInfo *base_patch_content_info = (g_ncaBasePatchTitleInfo ? titleGetContentInfoByTypeAndIdOffset(g_ncaBasePatchTitleInfo, content_type, nca_ctx->id_offset) : NULL); + NcaContext *base_patch_nca_ctx = NULL; + NcaFsSectionContext *base_patch_nca_fs_ctx = NULL; + + PartitionFileSystemContext pfs_ctx = {0}; + RomFileSystemContext romfs_ctx = {0}; + + bool write_raw_section = (bool)getNcaFsWriteRawSectionOption(); + bool use_layeredfs_dir = (bool)getNcaFsUseLayeredFsDirOption(); + bool success = false; + + /* Override LayeredFS flag, if needed. */ + if (use_layeredfs_dir && ((title_type != NcmContentMetaType_Application && title_type != NcmContentMetaType_Patch) || content_type != NcmContentType_Program || nca_fs_ctx->section_idx > 1)) + { + consolePrint("layeredfs setting disabled (unsupported by current content/section type combo)\n"); + use_layeredfs_dir = false; + } + + /* Initialize base/patch NCA context, if needed. */ + if (g_ncaBasePatchTitleInfo) + { + if (!base_patch_content_info) + { + consolePrint("unable to find content with type %s and id offset %u in selected base/patch title!\n", titleGetNcmContentTypeName(content_type), nca_ctx->id_offset); + goto end; + } + + base_patch_nca_ctx = calloc(1, sizeof(NcaContext)); + if (!base_patch_nca_ctx) + { + consolePrint("failed to allocate memory for base/patch nca ctx!\n"); + goto end; + } + + if (!ncaInitializeContext(base_patch_nca_ctx, g_ncaBasePatchTitleInfo->storage_id, (g_ncaBasePatchTitleInfo->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ + &(g_ncaBasePatchTitleInfo->meta_key), base_patch_content_info, NULL)) + { + consolePrint("failed to initialize base/patch nca ctx!\n"); + goto end; + } + + /* Use a matching NCA FS section entry. */ + base_patch_nca_fs_ctx = &(base_patch_nca_ctx->fs_ctx[nca_fs_ctx->section_idx]); + } + + if (section_type == NcaFsSectionType_PartitionFs) + { + /* Select the right NCA FS section context, depending on the sparse layer flag. */ + NcaFsSectionContext *pfs_nca_fs_ctx = (nca_fs_ctx->has_sparse_layer ? base_patch_nca_fs_ctx : nca_fs_ctx); + + /* Initialize PartitionFS context. */ + if (!pfsInitializeContext(&pfs_ctx, pfs_nca_fs_ctx)) + { + consolePrint("pfs initialize ctx failed!\n"); + goto end; + } + + success = (write_raw_section ? saveRawPartitionFsSection(&pfs_ctx, use_layeredfs_dir) : saveExtractedPartitionFsSection(&pfs_ctx, use_layeredfs_dir)); + } else { + /* Select the right base/patch NCA FS section contexts. */ + NcaFsSectionContext *base_nca_fs_ctx = (section_type == NcaFsSectionType_PatchRomFs ? base_patch_nca_fs_ctx : nca_fs_ctx); + NcaFsSectionContext *patch_nca_fs_ctx = (section_type == NcaFsSectionType_PatchRomFs ? nca_fs_ctx : base_patch_nca_fs_ctx); + + /* Initialize RomFS context. */ + if (!romfsInitializeContext(&romfs_ctx, base_nca_fs_ctx, patch_nca_fs_ctx)) + { + consolePrint("romfs initialize ctx failed!\n"); + goto end; + } + + //success = (write_raw_section ? saveRawRomFsSection(&romfs_ctx, use_layeredfs_dir) : saveExtractedRomFsSection(&romfs_ctx, use_layeredfs_dir)); + } + +end: + romfsFreeContext(&romfs_ctx); + + pfsFreeContext(&pfs_ctx); + + if (base_patch_nca_ctx) free(base_patch_nca_ctx); + + return success; +} + +static bool saveRawPartitionFsSection(PartitionFileSystemContext *pfs_ctx, bool use_layeredfs_dir) +{ + u64 free_space = 0; + + PfsThreadData pfs_thread_data = {0}; + SharedThreadData *shared_thread_data = &(pfs_thread_data.shared_thread_data); + + NcaContext *nca_ctx = pfs_ctx->nca_fs_ctx->nca_ctx; + + u64 title_id = nca_ctx->title_id; + u8 title_type = nca_ctx->title_type; + + char subdir[0x20] = {0}, *filename = NULL; + u32 dev_idx = g_storageMenuElementOption.selected; + + bool success = false; + + pfs_thread_data.pfs_ctx = pfs_ctx; + pfs_thread_data.use_layeredfs_dir = use_layeredfs_dir; + shared_thread_data->total_size = pfs_ctx->size; + + consolePrint("raw partitionfs section size: 0x%lX\n", pfs_ctx->size); + + if (use_layeredfs_dir) + { + /* Only use base title IDs if we're dealing with patches. */ + if (title_type == NcmContentMetaType_Patch) title_id = titleGetApplicationIdByPatchId(title_id); + filename = generateOutputLayeredFsFileName(title_id, NULL, "exefs.nsp"); + } else { + snprintf(subdir, MAX_ELEMENTS(subdir), "NCA FS/%s/Raw", nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User"); + snprintf(path, MAX_ELEMENTS(path), "/%s/section_%u.pfs0", nca_ctx->content_id_str, pfs_ctx->nca_fs_ctx->section_idx); + + TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo); + filename = generateOutputTitleFileName(title_info, subdir, path); + } + + if (!filename) goto end; + + if (dev_idx == 1) + { + if (!usbSendFileProperties(shared_thread_data->total_size, filename)) + { + consolePrint("failed to send file properties for \"%s\"!\n", filename); + goto end; + } + } else { + if (!utilsGetFileSystemStatsByPath(filename, NULL, &free_space)) + { + consolePrint("failed to retrieve free space from selected device\n"); + goto end; + } + + if (shared_thread_data->total_size >= free_space) + { + consolePrint("dump size exceeds free space\n"); + goto end; + } + + utilsCreateDirectoryTree(filename, false); + + if (dev_idx == 0) + { + if (shared_thread_data->total_size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(filename)) + { + consolePrint("failed to create concatenation file for \"%s\"!\n", filename); + goto end; + } + } else { + if (g_umsDevices[dev_idx - 2].fs_type < UsbHsFsDeviceFileSystemType_exFAT && shared_thread_data->total_size > FAT32_FILESIZE_LIMIT) + { + consolePrint("split dumps not supported for FAT12/16/32 volumes in UMS devices (yet)\n"); + goto end; + } + } + + shared_thread_data->fp = fopen(filename, "wb"); + if (!shared_thread_data->fp) + { + consolePrint("failed to open \"%s\" for writing!\n", filename); + goto end; + } + + ftruncate(fileno(shared_thread_data->fp), (off_t)shared_thread_data->total_size); + } + + consoleRefresh(); + + success = spanDumpThreads(rawPartitionFsReadThreadFunc, genericWriteThreadFunc, &pfs_thread_data); + + if (success) + { + consolePrint("successfully saved raw partitionfs section as \"%s\"\n", filename); + consoleRefresh(); + } + +end: + if (shared_thread_data->fp) + { + fclose(shared_thread_data->fp); + shared_thread_data->fp = NULL; + + if (!success && dev_idx != 1) + { + if (dev_idx == 0) + { + utilsRemoveConcatenationFile(filename); + utilsCommitSdCardFileSystemChanges(); + } else { + remove(filename); + } + } + } + + if (filename) free(filename); + + return success; +} + +static bool saveExtractedPartitionFsSection(PartitionFileSystemContext *pfs_ctx, bool use_layeredfs_dir) +{ + u64 data_size = 0; + + PfsThreadData pfs_thread_data = {0}; + SharedThreadData *shared_thread_data = &(pfs_thread_data.shared_thread_data); + + bool success = false; + + if (!pfsGetTotalDataSize(pfs_ctx, &data_size)) + { + consolePrint("failed to calculate extracted partitionfs section size!\n"); + goto end; + } + + if (!data_size) + { + consolePrint("partitionfs section is empty!\n"); + goto end; + } + + pfs_thread_data.pfs_ctx = pfs_ctx; + pfs_thread_data.use_layeredfs_dir = use_layeredfs_dir; + shared_thread_data->total_size = data_size; + + consolePrint("extracted partitionfs section size: 0x%lX\n", data_size); + consoleRefresh(); + + success = spanDumpThreads(extractedPartitionFsReadThreadFunc, genericWriteThreadFunc, &pfs_thread_data); + +end: + return success; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + static void xciReadThreadFunc(void *arg) { @@ -3436,6 +3828,309 @@ end: threadExit(); } +static void rawPartitionFsReadThreadFunc(void *arg) +{ + void *buf1 = NULL, *buf2 = NULL; + PfsThreadData *pfs_thread_data = (PfsThreadData*)arg; + SharedThreadData *shared_thread_data = (pfs_thread_data ? &(pfs_thread_data->shared_thread_data) : NULL); + PartitionFileSystemContext *pfs_ctx = (pfs_thread_data ? pfs_thread_data->pfs_ctx : NULL); + + buf1 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); + buf2 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); + + if (!pfs_thread_data || !shared_thread_data || !shared_thread_data->total_size || !pfs_ctx || !buf1 || !buf2) + { + shared_thread_data->read_error = true; + goto end; + } + + shared_thread_data->data = NULL; + shared_thread_data->data_size = 0; + + for(u64 offset = 0, blksize = BLOCK_SIZE; offset < shared_thread_data->total_size; offset += blksize) + { + if (blksize > (shared_thread_data->total_size - offset)) blksize = (shared_thread_data->total_size - offset); + + /* Check if the transfer has been cancelled by the user */ + if (shared_thread_data->transfer_cancelled) + { + condvarWakeAll(&g_writeCondvar); + break; + } + + /* Read current data chunk */ + shared_thread_data->read_error = !pfsReadPartitionData(pfs_ctx, buf1, blksize, offset); + if (shared_thread_data->read_error) + { + condvarWakeAll(&g_writeCondvar); + break; + } + + /* Wait until the previous data chunk has been written */ + mutexLock(&g_fileMutex); + + if (shared_thread_data->data_size && !shared_thread_data->write_error) condvarWait(&g_readCondvar, &g_fileMutex); + + if (shared_thread_data->write_error) + { + mutexUnlock(&g_fileMutex); + break; + } + + /* Update shared object. */ + shared_thread_data->data = buf1; + shared_thread_data->data_size = blksize; + + /* Swap buffers. */ + buf1 = buf2; + buf2 = shared_thread_data->data; + + /* Wake up the write thread to continue writing data. */ + mutexUnlock(&g_fileMutex); + condvarWakeAll(&g_writeCondvar); + } + +end: + if (buf2) free(buf2); + if (buf1) free(buf1); + + threadExit(); +} + +static void extractedPartitionFsReadThreadFunc(void *arg) +{ + void *buf1 = NULL, *buf2 = NULL; + PfsThreadData *pfs_thread_data = (PfsThreadData*)arg; + SharedThreadData *shared_thread_data = (pfs_thread_data ? &(pfs_thread_data->shared_thread_data) : NULL); + + PartitionFileSystemContext *pfs_ctx = (pfs_thread_data ? pfs_thread_data->pfs_ctx : NULL); + u32 pfs_entry_count = pfsGetEntryCount(pfs_ctx); + + char pfs_path[FS_MAX_PATH] = {0}, subdir[0x20] = {0}, *filename = NULL; + size_t filename_len = 0; + + PartitionFileSystemEntry *pfs_entry = NULL; + char *pfs_entry_name = NULL; + + NcaContext *nca_ctx = pfs_ctx->nca_fs_ctx->nca_ctx; + + u64 title_id = nca_ctx->title_id; + u8 title_type = nca_ctx->title_type; + + u64 free_space = 0; + u32 dev_idx = g_storageMenuElementOption.selected; + + buf1 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); + buf2 = usbAllocatePageAlignedBuffer(BLOCK_SIZE); + + if (pfs_thread_data->use_layeredfs_dir) + { + /* Only use base title IDs if we're dealing with patches. */ + if (title_type == NcmContentMetaType_Patch) title_id = titleGetApplicationIdByPatchId(title_id); + filename = generateOutputLayeredFsFileName(title_id, NULL, "exefs"); + } else { + snprintf(subdir, MAX_ELEMENTS(subdir), "NCA FS/%s/Extracted", nca_ctx->storage_id == NcmStorageId_BuiltInSystem ? "System" : "User"); + snprintf(pfs_path, MAX_ELEMENTS(pfs_path), "/%s/section_%u", nca_ctx->content_id_str, pfs_ctx->nca_fs_ctx->section_idx); + + TitleInfo *title_info = (title_id == g_ncaUserTitleInfo->meta_key.id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo); + filename = generateOutputTitleFileName(title_info, subdir, pfs_path); + } + + filename_len = (filename ? strlen(filename) : 0); + + if (!pfs_thread_data || !shared_thread_data || !shared_thread_data->total_size || !pfs_ctx || !pfs_entry_count || !buf1 || !buf2 || !filename) + { + shared_thread_data->read_error = true; + goto end; + } + + if (dev_idx != 1) + { + if (!utilsGetFileSystemStatsByPath(filename, NULL, &free_space)) + { + consolePrint("failed to retrieve free space from selected device\n"); + shared_thread_data->read_error = true; + goto end; + } + + if (shared_thread_data->total_size >= free_space) + { + consolePrint("dump size exceeds free space\n"); + shared_thread_data->read_error = true; + goto end; + } + } + + /* Loop through all file entries. */ + for(u32 i = 0; i < pfs_entry_count; i++) + { + /* Check if the transfer has been cancelled by the user. */ + if (shared_thread_data->transfer_cancelled) + { + condvarWakeAll(&g_writeCondvar); + break; + } + + if (dev_idx != 1) + { + /* Wait until the previous data chunk has been written */ + mutexLock(&g_fileMutex); + if (shared_thread_data->data_size && !shared_thread_data->write_error) condvarWait(&g_readCondvar, &g_fileMutex); + mutexUnlock(&g_fileMutex); + + if (shared_thread_data->write_error) break; + + /* Close file. */ + if (shared_thread_data->fp) + { + fclose(shared_thread_data->fp); + shared_thread_data->fp = NULL; + utilsCommitSdCardFileSystemChanges(); + } + } + + /* Retrieve Hash FS file entry information. */ + shared_thread_data->read_error = ((pfs_entry = pfsGetEntryByIndex(pfs_ctx, i)) == NULL || (pfs_entry_name = pfsGetEntryName(pfs_ctx, pfs_entry)) == NULL); + if (shared_thread_data->read_error) + { + condvarWakeAll(&g_writeCondvar); + break; + } + + /* Generate output path. */ + snprintf(pfs_path, MAX_ELEMENTS(pfs_path), "%s/%s", filename, pfs_entry_name); + utilsReplaceIllegalCharacters(pfs_path + filename_len + 1, dev_idx == 0); + + if (dev_idx == 1) + { + /* Wait until the previous data chunk has been written */ + mutexLock(&g_fileMutex); + if (shared_thread_data->data_size && !shared_thread_data->write_error) condvarWait(&g_readCondvar, &g_fileMutex); + mutexUnlock(&g_fileMutex); + + if (shared_thread_data->write_error) break; + + /* Send current file properties */ + shared_thread_data->read_error = !usbSendFileProperties(pfs_entry->size, pfs_path); + } else { + /* Create directory tree. */ + utilsCreateDirectoryTree(pfs_path, false); + + if (dev_idx == 0) + { + /* Create ConcatenationFile if we're dealing with a big file + SD card as the output storage. */ + if (pfs_entry->size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(pfs_path)) + { + consolePrint("failed to create concatenation file for \"%s\"!\n", pfs_path); + shared_thread_data->read_error = true; + } + } else { + /* Don't handle file chunks on FAT12/FAT16/FAT32 formatted UMS devices. */ + if (g_umsDevices[dev_idx - 2].fs_type < UsbHsFsDeviceFileSystemType_exFAT && pfs_entry->size > FAT32_FILESIZE_LIMIT) + { + consolePrint("split dumps not supported for FAT12/16/32 volumes in UMS devices (yet)\n"); + shared_thread_data->read_error = true; + } + } + + if (!shared_thread_data->read_error) + { + /* Open output file. */ + shared_thread_data->read_error = ((shared_thread_data->fp = fopen(pfs_path, "wb")) == NULL); + if (!shared_thread_data->read_error) + { + /* Set file size. */ + ftruncate(fileno(shared_thread_data->fp), (off_t)pfs_entry->size); + } else { + consolePrint("failed to open \"%s\" for writing!\n", pfs_path); + } + } + } + + if (shared_thread_data->read_error) + { + condvarWakeAll(&g_writeCondvar); + break; + } + + for(u64 offset = 0, blksize = BLOCK_SIZE; offset < pfs_entry->size; offset += blksize) + { + if (blksize > (pfs_entry->size - offset)) blksize = (pfs_entry->size - offset); + + /* Check if the transfer has been cancelled by the user. */ + if (shared_thread_data->transfer_cancelled) + { + condvarWakeAll(&g_writeCondvar); + break; + } + + /* Read current file data chunk. */ + shared_thread_data->read_error = !pfsReadEntryData(pfs_ctx, pfs_entry, buf1, blksize, offset); + if (shared_thread_data->read_error) + { + condvarWakeAll(&g_writeCondvar); + break; + } + + /* Wait until the previous file data chunk has been written. */ + mutexLock(&g_fileMutex); + + if (shared_thread_data->data_size && !shared_thread_data->write_error) condvarWait(&g_readCondvar, &g_fileMutex); + + if (shared_thread_data->write_error) + { + mutexUnlock(&g_fileMutex); + break; + } + + /* Update shared object. */ + shared_thread_data->data = buf1; + shared_thread_data->data_size = blksize; + + /* Swap buffers. */ + buf1 = buf2; + buf2 = shared_thread_data->data; + + /* Wake up the write thread to continue writing data. */ + mutexUnlock(&g_fileMutex); + condvarWakeAll(&g_writeCondvar); + } + + if (shared_thread_data->read_error || shared_thread_data->write_error || shared_thread_data->transfer_cancelled) break; + } + + if (!shared_thread_data->read_error && !shared_thread_data->write_error && !shared_thread_data->transfer_cancelled) + { + /* Wait until the previous file data chunk has been written. */ + mutexLock(&g_fileMutex); + if (shared_thread_data->data_size) condvarWait(&g_readCondvar, &g_fileMutex); + mutexUnlock(&g_fileMutex); + + consolePrint("successfully saved extracted partitionfs section data to \"%s\"\n", filename); + consoleRefresh(); + } + +end: + if (shared_thread_data->fp) + { + fclose(shared_thread_data->fp); + shared_thread_data->fp = NULL; + + if ((shared_thread_data->read_error || shared_thread_data->write_error || shared_thread_data->transfer_cancelled) && dev_idx != 1) + { + utilsDeleteDirectoryRecursively(filename); + if (dev_idx == 0) utilsCommitSdCardFileSystemChanges(); + } + } + + if (filename) free(filename); + + if (buf2) free(buf2); + if (buf1) free(buf1); + + threadExit(); +} + static void genericWriteThreadFunc(void *arg) { SharedThreadData *shared_thread_data = (SharedThreadData*)arg; // UB but we don't care @@ -3701,7 +4396,7 @@ static void nspThreadFunc(void *arg) meta_nca_ctx = &(nca_ctx[title_info->content_count - 1]); if (!ncaInitializeContext(meta_nca_ctx, title_info->storage_id, (title_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ - titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Meta, 0), title_info->version.value, &tik)) + &(title_info->meta_key), titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Meta, 0), &tik)) { consolePrint("meta nca initialize ctx failed\n"); goto end; @@ -3728,7 +4423,8 @@ static void nspThreadFunc(void *arg) if (content_info->content_type == NcmContentType_Meta) continue; NcaContext *cur_nca_ctx = &(nca_ctx[j]); - if (!ncaInitializeContext(cur_nca_ctx, title_info->storage_id, (title_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), content_info, title_info->version.value, &tik)) + if (!ncaInitializeContext(cur_nca_ctx, title_info->storage_id, (title_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ + &(title_info->meta_key), content_info, &tik)) { consolePrint("%s #%u initialize nca ctx failed\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset); goto end; @@ -4544,14 +5240,14 @@ static void setTicketRemoveConsoleDataOption(u32 idx) configSetBoolean("ticket/remove_console_data", (bool)idx); } -static u32 getNcaFsWriteSectionImageOption(void) +static u32 getNcaFsWriteRawSectionOption(void) { - return (u32)configGetBoolean("nca_fs/write_section_image"); + return (u32)configGetBoolean("nca_fs/write_raw_section"); } -static void setNcaFsWriteSectionImageOption(u32 idx) +static void setNcaFsWriteRawSectionOption(u32 idx) { - configSetBoolean("nca_fs/write_section_image", (bool)idx); + configSetBoolean("nca_fs/write_raw_section", (bool)idx); } static u32 getNcaFsUseLayeredFsDirOption(void) diff --git a/code_templates/sd_romfs_dumper.c b/code_templates/sd_romfs_dumper.c index 5dbf2ff..bb22c1d 100644 --- a/code_templates/sd_romfs_dumper.c +++ b/code_templates/sd_romfs_dumper.c @@ -589,7 +589,7 @@ int main(int argc, char *argv[]) consolePrint("selected title:\n%s (%016lX)\n\n", app_metadata[selected_idx]->lang_entry.name, app_metadata[selected_idx]->title_id + program_id_offset); if (!ncaInitializeContext(base_nca_ctx, user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ - titleGetContentInfoByTypeAndIdOffset(user_app_data.app_info, NcmContentType_Program, program_id_offset), user_app_data.app_info->version.value, NULL)) + &(user_app_data.app_info->meta_key), titleGetContentInfoByTypeAndIdOffset(user_app_data.app_info, NcmContentType_Program, program_id_offset), NULL)) { consolePrint("nca initialize base ctx failed\n"); goto out2; @@ -615,7 +615,7 @@ int main(int argc, char *argv[]) consolePrint("using patch romfs with update v%u\n", latest_patch->version.value); if (!ncaInitializeContext(update_nca_ctx, latest_patch->storage_id, (latest_patch->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ - titleGetContentInfoByTypeAndIdOffset(latest_patch, NcmContentType_Program, program_id_offset), latest_patch->version.value, NULL)) + &(latest_patch->meta_key), titleGetContentInfoByTypeAndIdOffset(latest_patch, NcmContentType_Program, program_id_offset), NULL)) { consolePrint("nca initialize update ctx failed\n"); goto out2; diff --git a/code_templates/system_title_dumper.c b/code_templates/system_title_dumper.c index 3ab3bb9..f5bdb1b 100644 --- a/code_templates/system_title_dumper.c +++ b/code_templates/system_title_dumper.c @@ -401,7 +401,7 @@ int main(int argc, char *argv[]) } else if (menu == 2) { - if (!ncaInitializeContext(nca_ctx, cur_title_info->storage_id, 0, &(cur_title_info->content_infos[nca_idx]), cur_title_info->version.value, NULL)) + if (!ncaInitializeContext(nca_ctx, cur_title_info->storage_id, 0, &(cur_title_info->meta_key), &(cur_title_info->content_infos[nca_idx]), NULL)) { consolePrint("nca initialize ctx failed\n"); error = true; diff --git a/code_templates/usb_romfs_dumper.c b/code_templates/usb_romfs_dumper.c index 024aa98..9484101 100644 --- a/code_templates/usb_romfs_dumper.c +++ b/code_templates/usb_romfs_dumper.c @@ -580,7 +580,7 @@ int main(int argc, char *argv[]) consolePrint("selected title:\n%s (%016lX)\n\n", app_metadata[selected_idx]->lang_entry.name, app_metadata[selected_idx]->title_id + program_id_offset); if (!ncaInitializeContext(base_nca_ctx, user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ - titleGetContentInfoByTypeAndIdOffset(user_app_data.app_info, NcmContentType_Program, program_id_offset), user_app_data.app_info->version.value, NULL)) + &(user_app_data.app_info->meta_key), titleGetContentInfoByTypeAndIdOffset(user_app_data.app_info, NcmContentType_Program, program_id_offset), NULL)) { consolePrint("nca initialize base ctx failed\n"); goto out2; @@ -606,7 +606,7 @@ int main(int argc, char *argv[]) consolePrint("using patch romfs with update v%u\n", latest_patch->version.value); if (!ncaInitializeContext(update_nca_ctx, latest_patch->storage_id, (latest_patch->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ - titleGetContentInfoByTypeAndIdOffset(latest_patch, NcmContentType_Program, program_id_offset), latest_patch->version.value, NULL)) + &(latest_patch->meta_key), titleGetContentInfoByTypeAndIdOffset(latest_patch, NcmContentType_Program, program_id_offset), NULL)) { consolePrint("nca initialize update ctx failed\n"); goto out2; diff --git a/code_templates/xml_generator.c b/code_templates/xml_generator.c index 1d92cad..f11546f 100644 --- a/code_templates/xml_generator.c +++ b/code_templates/xml_generator.c @@ -281,7 +281,7 @@ int main(int argc, char *argv[]) if (content_info->content_type == NcmContentType_Meta) continue; if (!ncaInitializeContext(&(nca_ctx[j]), user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ - content_info, user_app_data.app_info->version.value, &tik)) + &(user_app_data.app_info->meta_key), content_info, &tik)) { consolePrint("%s #%u initialize nca ctx failed\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset); goto out2; @@ -331,13 +331,13 @@ int main(int argc, char *argv[]) } if (!ncaInitializeContext(&(nca_ctx[meta_idx]), user_app_data.app_info->storage_id, (user_app_data.app_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \ - titleGetContentInfoByTypeAndIdOffset(user_app_data.app_info, NcmContentType_Meta, 0), user_app_data.app_info->version.value, &tik)) + &(user_app_data.app_info->meta_key), titleGetContentInfoByTypeAndIdOffset(user_app_data.app_info, NcmContentType_Meta, 0), &tik)) { - consolePrint("Meta nca initialize ctx failed\n"); + consolePrint("meta nca initialize ctx failed\n"); goto out2; } - consolePrint("Meta nca initialize ctx succeeded\n"); + consolePrint("meta nca initialize ctx succeeded\n"); if (!cnmtInitializeContext(&cnmt_ctx, &(nca_ctx[meta_idx]))) { diff --git a/include/core/nca.h b/include/core/nca.h index bd4a5b5..b27a9e9 100644 --- a/include/core/nca.h +++ b/include/core/nca.h @@ -430,7 +430,10 @@ struct _NcaContext { u8 storage_id; ///< NcmStorageId. NcmContentStorage *ncm_storage; ///< Pointer to a NcmContentStorage instance. Used to read NCA data from eMMC/SD. u64 gamecard_offset; ///< Used to read NCA data from a gamecard using a FsStorage instance when storage_id == NcmStorageId_GameCard. - NcmContentId content_id; ///< Also used to read NCA data. + u64 title_id; ///< ID from the title that owns this NCA. Retrieved from NcmContentMetaKey. Placed here for convenience. + Version title_version; ///< Version from the title that owns this NCA. Retrieved from NcmContentMetaKey. Placed here for convenience. + u8 title_type; ///< NcmContentMetaType. Retrieved from NcmContentMetaKey. Placed here for convenience. + NcmContentId content_id; ///< Content ID for this NCA. Used to read NCA data from eMMC/SD. Retrieved from NcmContentInfo. char content_id_str[0x21]; u8 hash[SHA256_HASH_SIZE]; ///< Manually calculated (if needed). char hash_str[0x41]; @@ -440,7 +443,6 @@ struct _NcaContext { char content_size_str[0x10]; ///< Placed here for convenience. u8 key_generation; ///< NcaKeyGeneration. Retrieved from the decrypted header. u8 id_offset; ///< Retrieved from NcmContentInfo. - u32 title_version; bool rights_id_available; bool titlekey_retrieved; bool valid_main_signature; @@ -490,7 +492,7 @@ void ncaFreeCryptoBuffer(void); /// If the 'tik' argument points to a valid Ticket element, it will either be updated (if it's empty) or used to read ticket data that has already been retrieved. /// If the 'tik' argument is NULL, the function will just retrieve the necessary ticket data on its own. /// If ticket data can't be retrieved, the context will still be initialized, but anything that involves working with encrypted NCA FS section blocks won't be possible (e.g. ncaReadFsSection()). -bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, const NcmContentInfo *content_info, u32 title_version, Ticket *tik); +bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, const NcmContentMetaKey *meta_key, const NcmContentInfo *content_info, Ticket *tik); /// Reads raw encrypted data from a NCA using an input context, previously initialized by ncaInitializeContext(). /// Input offset must be relative to the start of the NCA content file. diff --git a/romfs/default_config.json b/romfs/default_config.json index b6c971a..65060f9 100644 --- a/romfs/default_config.json +++ b/romfs/default_config.json @@ -25,7 +25,7 @@ "remove_console_data": true }, "nca_fs": { - "write_section_image": false, + "write_raw_section": false, "use_layeredfs_dir": false } } diff --git a/source/core/bfttf.c b/source/core/bfttf.c index 555f939..8c0c29a 100644 --- a/source/core/bfttf.c +++ b/source/core/bfttf.c @@ -100,8 +100,8 @@ bool bfttfInitialize(void) /* Initialize NCA context. */ /* NCA contexts don't need to be freed beforehand. */ /* Don't allow invalid NCA signatures. */ - bool nca_ctx_init = (ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Data, 0), \ - title_info->version.value, NULL) && nca_ctx->valid_main_signature); + bool nca_ctx_init = (ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, &(title_info->meta_key), \ + titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Data, 0), NULL) && nca_ctx->valid_main_signature); /* Free title info. */ titleFreeTitleInfo(&title_info); diff --git a/source/core/config.c b/source/core/config.c index 0b26a8f..51d2cb4 100644 --- a/source/core/config.c +++ b/source/core/config.c @@ -285,18 +285,18 @@ end: static bool configValidateJsonNcaFsObject(const struct json_object *obj) { - bool ret = false, write_section_image_found = false, use_layeredfs_dir_found = false; + bool ret = false, write_raw_section_found = false, use_layeredfs_dir_found = false; if (!jsonValidateObject(obj)) goto end; json_object_object_foreach(obj, key, val) { - CONFIG_VALIDATE_FIELD(Boolean, write_section_image); + CONFIG_VALIDATE_FIELD(Boolean, write_raw_section); CONFIG_VALIDATE_FIELD(Boolean, use_layeredfs_dir); goto end; } - ret = (write_section_image_found && use_layeredfs_dir_found); + ret = (write_raw_section_found && use_layeredfs_dir_found); end: return ret; diff --git a/source/core/nca.c b/source/core/nca.c index 503bec9..dd34838 100644 --- a/source/core/nca.c +++ b/source/core/nca.c @@ -175,14 +175,14 @@ void ncaFreeCryptoBuffer(void) } } -bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, const NcmContentInfo *content_info, u32 title_version, Ticket *tik) +bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, const NcmContentMetaKey *meta_key, const NcmContentInfo *content_info, Ticket *tik) { NcmContentStorage *ncm_storage = NULL; u8 valid_fs_section_cnt = 0; if (!out || (storage_id != NcmStorageId_GameCard && !(ncm_storage = titleGetNcmStorageByStorageId(storage_id))) || \ (storage_id == NcmStorageId_GameCard && (hfs_partition_type < HashFileSystemPartitionType_Root || hfs_partition_type >= HashFileSystemPartitionType_Count)) || \ - !content_info || content_info->content_type >= NcmContentType_DeltaFragment) + !meta_key || !content_info || content_info->content_type >= NcmContentType_DeltaFragment) { LOG_MSG_ERROR("Invalid parameters!"); return false; @@ -195,6 +195,10 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, out->storage_id = storage_id; out->ncm_storage = (out->storage_id != NcmStorageId_GameCard ? ncm_storage : NULL); + out->title_id = meta_key->id; + out->title_version.value = meta_key->version; + out->title_type = meta_key->type; + memcpy(&(out->content_id), &(content_info->content_id), sizeof(NcmContentId)); utilsGenerateHexStringFromData(out->content_id_str, sizeof(out->content_id_str), out->content_id.c, sizeof(out->content_id.c), false); @@ -202,7 +206,6 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, out->content_type = content_info->content_type; out->id_offset = content_info->id_offset; - out->title_version = title_version; ncmContentInfoSizeToU64(content_info, &(out->content_size)); utilsGenerateFormattedSizeString((double)out->content_size, out->content_size_str, sizeof(out->content_size_str)); @@ -793,7 +796,7 @@ static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx) NcaBucketInfo *compression_bucket = &(fs_ctx->header.compression_info.bucket); - bool success = false; + bool skip_extra_checks = false, success = false; /* Fill section context. */ fs_ctx->enabled = false; @@ -897,21 +900,20 @@ static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx) goto end; } - if (!raw_storage_size || !sparse_bucket->header.entry_count) + if (raw_storage_size && sparse_bucket->header.entry_count) { - /* Return true but don't set this FS section as enabled, since we can't really use it. */ - LOG_MSG_WARNING("Empty SparseInfo data detected for FS section #%u in \"%s\". Skipping FS section.", section_idx, nca_ctx->content_id_str); - success = true; - goto end; + /* Update context. */ + fs_ctx->sparse_table_offset = (sparse_info->physical_offset + sparse_bucket->offset); + fs_ctx->section_size = raw_storage_size; + } else { + /* We can't really use this section. We'll just emit a warning and proceed anyway. */ + LOG_MSG_WARNING("Empty SparseInfo data detected for FS section #%u in \"%s\". Skipping extra checks.", section_idx, nca_ctx->content_id_str); + skip_extra_checks = true; } - - /* Update context. */ - fs_ctx->sparse_table_offset = (sparse_info->physical_offset + sparse_bucket->offset); - fs_ctx->section_size = raw_storage_size; } /* Check if we're within boundaries. */ - if ((fs_ctx->section_offset + fs_ctx->section_size) > nca_ctx->content_size) + if (!skip_extra_checks && (fs_ctx->section_offset + fs_ctx->section_size) > nca_ctx->content_size) { LOG_MSG_ERROR("FS section #%u in \"%s\" is out of NCA boundaries. Skipping FS section.", section_idx, nca_ctx->content_id_str); goto end; diff --git a/source/core/nca_storage.c b/source/core/nca_storage.c index b5a9591..713c1a7 100644 --- a/source/core/nca_storage.c +++ b/source/core/nca_storage.c @@ -98,7 +98,7 @@ bool ncaStorageSetPatchOriginalSubStorage(NcaStorageContext *patch_ctx, NcaStora !(patch_nca_ctx = patch_ctx->nca_fs_ctx->nca_ctx) || !(base_nca_ctx = base_ctx->nca_fs_ctx->nca_ctx) || \ patch_ctx->nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs || base_ctx->nca_fs_ctx->section_type != NcaFsSectionType_RomFs || \ patch_nca_ctx->header.program_id != base_nca_ctx->header.program_id || patch_nca_ctx->header.content_type != base_nca_ctx->header.content_type || \ - patch_nca_ctx->id_offset != base_nca_ctx->id_offset || patch_nca_ctx->title_version < base_nca_ctx->title_version || \ + patch_nca_ctx->id_offset != base_nca_ctx->id_offset || patch_nca_ctx->title_version.value < base_nca_ctx->title_version.value || \ (patch_ctx->base_storage_type != NcaStorageBaseStorageType_Indirect && patch_ctx->base_storage_type != NcaStorageBaseStorageType_Compressed) || \ !patch_ctx->indirect_storage || !patch_ctx->aes_ctr_ex_storage || (base_ctx->base_storage_type == NcaStorageBaseStorageType_Compressed && \ patch_ctx->base_storage_type != NcaStorageBaseStorageType_Compressed)) diff --git a/source/core/nxdt_bfsar.c b/source/core/nxdt_bfsar.c index 2f7ffca..afc4786 100644 --- a/source/core/nxdt_bfsar.c +++ b/source/core/nxdt_bfsar.c @@ -107,7 +107,7 @@ bool bfsarInitialize(void) /* Initialize NCA context. */ /* Don't allow invalid NCA signatures. */ - if (!ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Program, 0), title_info->version.value, NULL) || \ + if (!ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, &(title_info->meta_key), titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Program, 0), NULL) || \ !nca_ctx->valid_main_signature) { LOG_MSG_ERROR("Failed to initialize qlaunch Program NCA context!"); diff --git a/source/core/nxdt_utils.c b/source/core/nxdt_utils.c index 810a38a..0270ccd 100644 --- a/source/core/nxdt_utils.c +++ b/source/core/nxdt_utils.c @@ -862,7 +862,7 @@ char *utilsGeneratePath(const char *prefix, const char *filename, const char *ex bool use_prefix = (prefix && *prefix); size_t prefix_len = (use_prefix ? strlen(prefix) : 0); - bool append_path_sep = (use_prefix && prefix[prefix_len - 1] != '/'); + bool append_path_sep = (use_prefix && prefix[prefix_len - 1] != '/' && *filename != '/'); bool use_extension = (extension && *extension); size_t extension_len = (use_extension ? strlen(extension) : 0); diff --git a/source/core/pfs.c b/source/core/pfs.c index 3f5a9d7..31e9468 100644 --- a/source/core/pfs.c +++ b/source/core/pfs.c @@ -191,7 +191,7 @@ bool pfsGetEntryIndexByName(PartitionFileSystemContext *ctx, const char *name, u } } - LOG_MSG_ERROR("Unable to find Partition FS entry \"%s\"!", name); + if (strcmp(name, "main.npdm") != 0) LOG_MSG_ERROR("Unable to find Partition FS entry \"%s\"!", name); return false; } diff --git a/source/core/title.c b/source/core/title.c index 10700a3..aa420f2 100644 --- a/source/core/title.c +++ b/source/core/title.c @@ -2709,7 +2709,7 @@ static char *titleGetPatchVersionString(TitleInfo *title_info) } /* Initialize NCA context. */ - if (!ncaInitializeContext(nca_ctx, storage_id, hfs_partition_type, nacp_content, title_info->version.value, NULL)) + if (!ncaInitializeContext(nca_ctx, storage_id, hfs_partition_type, &(title_info->meta_key), nacp_content, NULL)) { LOG_MSG_ERROR("Failed to initialize NCA context for Control NCA from %016lX!", title_info->meta_key.id); goto end;