mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-26 04:02:11 +00:00
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:
parent
c585e8f9d3
commit
62c15ca7cf
2 changed files with 171 additions and 42 deletions
|
@ -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))))
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue