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.
This commit is contained in:
Pablo Curiel 2023-12-31 18:16:49 +01:00
parent c585e8f9d3
commit 62c15ca7cf
2 changed files with 171 additions and 42 deletions

View File

@ -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))))

View File

@ -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)