From 62c15ca7cf520b5fae9847c98a9da7fa617261a4 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Sun, 31 Dec 2023 18:16:49 +0100 Subject: [PATCH] poc: add browser for gamecard HFS partitions Just in time for New Year's Eve. Other changes include: * title: use snprintf() in filename generation functions + reduce array sizes. --- code_templates/nxdt_rw_poc.c | 168 ++++++++++++++++++++++++++++++----- source/core/title.c | 45 ++++++---- 2 files changed, 171 insertions(+), 42 deletions(-) diff --git a/code_templates/nxdt_rw_poc.c b/code_templates/nxdt_rw_poc.c index 44522aa..21270da 100644 --- a/code_templates/nxdt_rw_poc.c +++ b/code_templates/nxdt_rw_poc.c @@ -70,19 +70,20 @@ typedef enum { MenuId_Root = 0, MenuId_GameCard = 1, MenuId_XCI = 2, - MenuId_HFS = 3, - MenuId_UserTitles = 4, - MenuId_UserTitlesSubMenu = 5, - MenuId_NSPTitleTypes = 6, - MenuId_NSP = 7, - MenuId_TicketTitleTypes = 8, - MenuId_Ticket = 9, - MenuId_NcaTitleTypes = 10, - MenuId_Nca = 11, - MenuId_NcaFsSections = 12, - MenuId_NcaFsSectionsSubMenu = 13, - MenuId_SystemTitles = 14, - MenuId_Count = 15 + MenuId_DumpHFS = 3, + MenuId_BrowseHFS = 4, + MenuId_UserTitles = 5, + MenuId_UserTitlesSubMenu = 6, + MenuId_NSPTitleTypes = 7, + MenuId_NSP = 8, + MenuId_TicketTitleTypes = 9, + MenuId_Ticket = 10, + MenuId_NcaTitleTypes = 11, + MenuId_Nca = 12, + MenuId_NcaFsSections = 13, + MenuId_NcaFsSectionsSubMenu = 14, + MenuId_SystemTitles = 15, + MenuId_Count = 16 } MenuId; typedef struct @@ -213,6 +214,7 @@ static bool saveGameCardUid(void *userdata); static bool saveGameCardHfsPartition(void *userdata); static bool saveGameCardRawHfsPartition(HashFileSystemContext *hfs_ctx); static bool saveGameCardExtractedHfsPartition(HashFileSystemContext *hfs_ctx); +static bool browseGameCardHfsPartition(void *userdata); static bool saveConsoleLafwBlob(void *userdata); @@ -411,7 +413,7 @@ static u32 g_hfsLogoPartition = HashFileSystemPartitionType_Logo; static u32 g_hfsNormalPartition = HashFileSystemPartitionType_Normal; static u32 g_hfsSecurePartition = HashFileSystemPartitionType_Secure; -static MenuElement *g_gameCardHfsMenuElements[] = { +static MenuElement *g_gameCardHfsDumpMenuElements[] = { &(MenuElement){ .str = "dump root hfs partition", .child_menu = NULL, @@ -464,6 +466,46 @@ static MenuElement *g_gameCardHfsMenuElements[] = { NULL }; +static MenuElement *g_gameCardHfsBrowseMenuElements[] = { + &(MenuElement){ + .str = "browse root hfs partition", + .child_menu = NULL, + .task_func = &browseGameCardHfsPartition, + .element_options = NULL, + .userdata = &g_hfsRootPartition + }, + &(MenuElement){ + .str = "browse update hfs partition", + .child_menu = NULL, + .task_func = &browseGameCardHfsPartition, + .element_options = NULL, + .userdata = &g_hfsUpdatePartition + }, + &(MenuElement){ + .str = "browse logo hfs partition", + .child_menu = NULL, + .task_func = &browseGameCardHfsPartition, + .element_options = NULL, + .userdata = &g_hfsLogoPartition + }, + &(MenuElement){ + .str = "browse normal hfs partition", + .child_menu = NULL, + .task_func = &browseGameCardHfsPartition, + .element_options = NULL, + .userdata = &g_hfsNormalPartition + }, + &(MenuElement){ + .str = "browse secure hfs partition", + .child_menu = NULL, + .task_func = &browseGameCardHfsPartition, + .element_options = NULL, + .userdata = &g_hfsSecurePartition + }, + &g_storageMenuElement, + NULL +}; + static MenuElement *g_gameCardMenuElements[] = { &(MenuElement){ .str = "dump gamecard image (xci)", @@ -530,11 +572,24 @@ static MenuElement *g_gameCardMenuElements[] = { &(MenuElement){ .str = "dump hfs partitions (optional)", .child_menu = &(Menu){ - .id = MenuId_HFS, + .id = MenuId_DumpHFS, .parent = NULL, .selected = 0, .scroll = 0, - .elements = g_gameCardHfsMenuElements + .elements = g_gameCardHfsDumpMenuElements + }, + .task_func = NULL, + .element_options = NULL, + .userdata = NULL + }, + &(MenuElement){ + .str = "browse hfs partitions (optional)", + .child_menu = &(Menu){ + .id = MenuId_BrowseHFS, + .parent = NULL, + .selected = 0, + .scroll = 0, + .elements = g_gameCardHfsBrowseMenuElements }, .task_func = NULL, .element_options = NULL, @@ -1276,16 +1331,28 @@ int main(int argc, char *argv[]) } else if (selected_element->task_func) { + bool show_button_prompt = true; + consoleClear(); /* Wait for gamecard (if needed). */ - if (((cur_menu->id >= MenuId_GameCard && cur_menu->id <= MenuId_HFS) || (title_info && title_info->storage_id == NcmStorageId_GameCard)) && !waitForGameCard()) + if (((cur_menu->id >= MenuId_GameCard && cur_menu->id <= MenuId_BrowseHFS) || (title_info && title_info->storage_id == NcmStorageId_GameCard)) && !waitForGameCard()) { if (g_appletStatus) continue; break; } - if (cur_menu->id > MenuId_Root && (cur_menu->id != MenuId_NcaFsSectionsSubMenu || cur_menu->selected != 1)) + if ((cur_menu->id == MenuId_NcaFsSectionsSubMenu && cur_menu->selected != 1) || cur_menu->id == MenuId_BrowseHFS) + { + show_button_prompt = false; + + /* Ignore result. */ + selected_element->task_func(selected_element->userdata); + + /* Update free space. */ + if (!useUsbHost()) updateStorageList(); + } else + if (cur_menu->id > MenuId_Root) { /* Wait for USB session (if needed). */ if (useUsbHost() && !waitForUsb()) @@ -1303,12 +1370,9 @@ int main(int argc, char *argv[]) } utilsSetLongRunningProcessState(false); - } else { - /* Ignore result. */ - selected_element->task_func(selected_element->userdata); } - if (g_appletStatus && (cur_menu->id != MenuId_NcaFsSectionsSubMenu || cur_menu->selected != 1)) + if (g_appletStatus && show_button_prompt) { /* Display prompt. */ consolePrint("press any button to continue"); @@ -2919,6 +2983,60 @@ end: return success; } +static bool browseGameCardHfsPartition(void *userdata) +{ + u32 hfs_partition_type = (userdata ? *((u32*)userdata) : HashFileSystemPartitionType_None); + HashFileSystemContext hfs_ctx = {0}; + char mount_name[DEVOPTAB_MOUNT_NAME_LENGTH] = {0}, subdir[0x20] = {0}, *base_out_path = NULL; + + bool success = false; + + if (hfs_partition_type < HashFileSystemPartitionType_Root || hfs_partition_type > HashFileSystemPartitionType_Secure) + { + consolePrint("invalid hfs partition type! (%u)\n", hfs_partition_type); + goto end; + } + + if (!gamecardGetHashFileSystemContext(hfs_partition_type, &hfs_ctx)) + { + consolePrint("get hfs ctx failed! this partition type may not exist within the inserted gamecard\n"); + goto end; + } + + /* Mount devoptab device. */ + snprintf(mount_name, MAX_ELEMENTS(mount_name), "hfs%s", hfs_ctx.name); + + if (!devoptabMountHashFileSystemDevice(&hfs_ctx, mount_name)) + { + consolePrint("hfs ctx devoptab mount failed!\n"); + goto end; + } + + /* Generate output base path. */ + snprintf(subdir, MAX_ELEMENTS(subdir), "/%s", hfs_ctx.name); + base_out_path = generateOutputGameCardFileName("HFS/Extracted", subdir, true); + if (!base_out_path) goto end; + + /* Display file browser. */ + success = fsBrowser(mount_name, base_out_path); + + /* Unmount devoptab device. */ + devoptabUnmountDevice(mount_name); + +end: + /* Free data. */ + if (base_out_path) free(base_out_path); + hfsFreeContext(&hfs_ctx); + + if (!success && g_appletStatus) + { + consolePrint("press any button to continue\n"); + utilsWaitForButtonPress(0); + } + + return success; +} + static bool saveConsoleLafwBlob(void *userdata) { NX_IGNORE_ARG(userdata); @@ -3529,7 +3647,9 @@ static bool fsBrowser(const char *mount_name, const char *base_out_path) depth++; } else { /* Dump file. */ + utilsSetLongRunningProcessState(true); fsBrowserDumpFile(dir_path, selected_entry, base_out_path); + utilsSetLongRunningProcessState(false); } } else if (btn_down & HidNpadButton_B) @@ -3592,7 +3712,9 @@ static bool fsBrowser(const char *mount_name, const char *base_out_path) if ((btn_down & HidNpadButton_Y) && entries_count && highlighted) { /* Dump highlighted entries. */ + utilsSetLongRunningProcessState(true); fsBrowserDumpHighlightedEntries(dir_path, entries, entries_count, base_out_path); + utilsSetLongRunningProcessState(false); /* Unhighlight all entries. */ for(u32 i = 0; i < entries_count; i++) entries[i].highlight = false; @@ -3683,7 +3805,7 @@ static bool fsBrowserGetDirEntries(const char *dir_path, FsBrowserEntry **out_en while((dt = readdir(dp))) { /* Skip "." and ".." entries. */ - if (!strcmp(dt->d_name, ".") || !strcmp(dt->d_name, "..") != 0) continue; + if (!strcmp(dt->d_name, ".") || !strcmp(dt->d_name, "..")) continue; /* Reallocate directory entries buffer. */ if (!(entries_tmp = realloc(entries, (count + 1) * sizeof(FsBrowserEntry)))) diff --git a/source/core/title.c b/source/core/title.c index b5d97ec..80ea88f 100644 --- a/source/core/title.c +++ b/source/core/title.c @@ -1096,31 +1096,35 @@ char *titleGenerateFileName(TitleInfo *title_info, u8 naming_convention, u8 ille u8 type_idx = title_info->meta_key.type; if (type_idx >= NcmContentMetaType_Application) type_idx -= NCM_CMT_APP_OFFSET; - char title_name[0x400] = {0}, *version_str = NULL, *filename = NULL; + char title_name[0x300] = {0}, *filename = NULL; + size_t title_name_len = 0; /* Generate filename for this title. */ if (naming_convention == TitleNamingConvention_Full) { if (title_info->app_metadata && *(title_info->app_metadata->lang_entry.name)) { - /* Retrieve display version string if we're dealing with a Patch. */ - if (title_info->meta_key.type == NcmContentMetaType_Patch) version_str = titleGetPatchVersionString(title_info); + snprintf(title_name, MAX_ELEMENTS(title_name), "%s ", title_info->app_metadata->lang_entry.name); - sprintf(title_name, "%s ", title_info->app_metadata->lang_entry.name); + /* Retrieve display version string if we're dealing with a Patch. */ + char *version_str = (title_info->meta_key.type == NcmContentMetaType_Patch ? titleGetPatchVersionString(title_info) : NULL); if (version_str) { - sprintf(title_name + strlen(title_name), "%s ", version_str); + title_name_len = strlen(title_name); + snprintf(title_name + title_name_len, MAX_ELEMENTS(title_name) - title_name_len, "%s ", version_str); free(version_str); } if (illegal_char_replace_type) utilsReplaceIllegalCharacters(title_name, illegal_char_replace_type == TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly); } - sprintf(title_name + strlen(title_name), "[%016lX][v%u][%s]", title_info->meta_key.id, title_info->meta_key.version, g_filenameTypeStrings[type_idx]); + title_name_len = strlen(title_name); + snprintf(title_name + title_name_len, MAX_ELEMENTS(title_name) - title_name_len, "[%016lX][v%u][%s]", title_info->meta_key.id, title_info->meta_key.version, \ + g_filenameTypeStrings[type_idx]); } else if (naming_convention == TitleNamingConvention_IdAndVersionOnly) { - sprintf(title_name, "%016lX_v%u_%s", title_info->meta_key.id, title_info->meta_key.version, g_filenameTypeStrings[type_idx]); + snprintf(title_name, MAX_ELEMENTS(title_name), "%016lX_v%u_%s", title_info->meta_key.id, title_info->meta_key.version, g_filenameTypeStrings[type_idx]); } /* Duplicate generated filename. */ @@ -1141,8 +1145,8 @@ char *titleGenerateGameCardFileName(u8 naming_convention, u8 illegal_char_replac u32 title_count = title_storage->title_count; GameCardHeader gc_header = {0}; - size_t cur_filename_len = 0; - char app_name[0x400] = {0}; + size_t cur_filename_len = 0, app_name_len = 0; + char app_name[0x300] = {0}; bool error = false; if (!g_titleInterfaceInit || !g_titleGameCardAvailable || naming_convention > TitleNamingConvention_IdAndVersionOnly || \ @@ -1170,8 +1174,8 @@ char *titleGenerateGameCardFileName(u8 naming_convention, u8 illegal_char_replac if (j == i) continue; TitleInfo *cur_title_info = titles[j]; - if (!cur_title_info || cur_title_info->meta_key.type != NcmContentMetaType_Patch || !titleCheckIfPatchIdBelongsToApplicationId(app_info->meta_key.id, cur_title_info->meta_key.id) || \ - cur_title_info->meta_key.version <= app_version) continue; + if (!cur_title_info || cur_title_info->meta_key.type != NcmContentMetaType_Patch || \ + !titleCheckIfPatchIdBelongsToApplicationId(app_info->meta_key.id, cur_title_info->meta_key.id) || cur_title_info->meta_key.version <= app_version) continue; patch_info = cur_title_info; app_version = cur_title_info->meta_key.version; @@ -1186,30 +1190,33 @@ char *titleGenerateGameCardFileName(u8 naming_convention, u8 illegal_char_replac if (app_info->app_metadata && *(app_info->app_metadata->lang_entry.name)) { - /* Retrieve display version string if the inserted gamecard holds a patch for the current user application. */ - char *version_str = NULL; - if (patch_info) version_str = titleGetPatchVersionString(patch_info); + app_name_len = strlen(app_name); + snprintf(app_name + app_name_len, MAX_ELEMENTS(app_name) - app_name_len, "%s ", app_info->app_metadata->lang_entry.name); - sprintf(app_name + strlen(app_name), "%s ", app_info->app_metadata->lang_entry.name); + /* Retrieve display version string if the inserted gamecard holds a patch for the current user application. */ + char *version_str = (patch_info ? titleGetPatchVersionString(patch_info) : NULL); if (version_str) { - sprintf(app_name + strlen(app_name), "%s ", version_str); + app_name_len = strlen(app_name); + snprintf(app_name + app_name_len, MAX_ELEMENTS(app_name) - app_name_len, "%s ", version_str); free(version_str); } if (illegal_char_replace_type) utilsReplaceIllegalCharacters(app_name, illegal_char_replace_type == TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly); } - sprintf(app_name + strlen(app_name), "[%016lX][v%u]", app_info->meta_key.id, app_version); + app_name_len = strlen(app_name); + snprintf(app_name + app_name_len, MAX_ELEMENTS(app_name) - app_name_len, "[%016lX][v%u]", app_info->meta_key.id, app_version); } else if (naming_convention == TitleNamingConvention_IdAndVersionOnly) { if (cur_filename_len) strcat(app_name, "+"); - sprintf(app_name + strlen(app_name), "%016lX_v%u", app_info->meta_key.id, app_version); + app_name_len = strlen(app_name); + snprintf(app_name + app_name_len, MAX_ELEMENTS(app_name) - app_name_len, "%016lX_v%u", app_info->meta_key.id, app_version); } /* Reallocate output buffer. */ - size_t app_name_len = strlen(app_name); + app_name_len = strlen(app_name); char *tmp_filename = realloc(filename, (cur_filename_len + app_name_len + 1) * sizeof(char)); if (!tmp_filename)