1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-12-22 16:42:11 +00:00

poc: add system title dumping capabilities

system_title_dumper PoC build is no more.

Other changes include:

* smc: add missing assert for SmcGenerateAesKekOption struct.
* title: update titleGenerateFileName() to add support for system titles.
This commit is contained in:
Pablo Curiel 2023-07-21 23:56:50 +02:00
parent cd73c6360f
commit a8a348afc8
4 changed files with 129 additions and 612 deletions

View file

@ -79,7 +79,8 @@ typedef enum {
MenuId_Nca = 11,
MenuId_NcaFsSections = 12,
MenuId_NcaFsSectionsSubMenu = 13,
MenuId_Count = 14
MenuId_SystemTitles = 14,
MenuId_Count = 15
} MenuId;
typedef struct
@ -149,8 +150,8 @@ static u32 menuGetElementCount(const Menu *menu);
void freeStorageList(void);
void updateStorageList(void);
void freeTitleList(void);
void updateTitleList(void);
void freeTitleList(Menu *menu);
void updateTitleList(Menu *menu, Menu *submenu, bool is_system);
void freeNcaList(void);
void updateNcaList(TitleInfo *title_info);
@ -810,7 +811,7 @@ static MenuElement *g_userTitlesSubMenuElements[] = {
NULL
};
// Dynamically set as child_menu for all g_userTitlesMenuElements entries.
// Dynamically set as child_menu for all g_userTitlesMenu entries.
static Menu g_userTitlesSubMenu = {
.id = MenuId_UserTitlesSubMenu,
.parent = NULL,
@ -819,9 +820,7 @@ static Menu g_userTitlesSubMenu = {
.elements = g_userTitlesSubMenuElements
};
static MenuElement **g_userTitlesMenuElements = NULL;
// Dynamically populated using g_userTitlesMenuElements.
// Dynamically populated.
static Menu g_userTitlesMenu = {
.id = MenuId_UserTitles,
.parent = NULL,
@ -830,6 +829,15 @@ static Menu g_userTitlesMenu = {
.elements = NULL
};
// Dynamically populated.
static Menu g_systemTitlesMenu = {
.id = MenuId_SystemTitles,
.parent = NULL,
.selected = 0,
.scroll = 0,
.elements = NULL
};
static MenuElement *g_rootMenuElements[] = {
&(MenuElement){
.str = "gamecard menu",
@ -853,7 +861,7 @@ static MenuElement *g_rootMenuElements[] = {
},
&(MenuElement){
.str = "system titles menu",
.child_menu = NULL,
.child_menu = &g_systemTitlesMenu,
.task_func = NULL,
.element_options = NULL,
.userdata = NULL
@ -894,7 +902,8 @@ int main(int argc, char *argv[])
updateStorageList();
updateTitleList();
updateTitleList(&g_userTitlesMenu, &g_userTitlesSubMenu, false);
updateTitleList(&g_systemTitlesMenu, &g_ncaMenu, true);
Menu *cur_menu = &g_rootMenu;
u32 element_count = menuGetElementCount(cur_menu), page_size = 30;
@ -906,6 +915,8 @@ int main(int argc, char *argv[])
TitleInfo *title_info = NULL;
u32 title_info_idx = 0, title_info_count = 0;
bool is_system = false;
while(appletMainLoop())
{
MenuElement *selected_element = cur_menu->elements[cur_menu->selected];
@ -929,7 +940,7 @@ int main(int argc, char *argv[])
consolePrint("press + to exit\n");
consolePrint("______________________________\n\n");
if (cur_menu->id == MenuId_UserTitles)
if (cur_menu->id == MenuId_UserTitles || cur_menu->id == MenuId_SystemTitles)
{
app_metadata = (TitleApplicationMetadata*)selected_element->userdata;
@ -937,14 +948,17 @@ int main(int argc, char *argv[])
consolePrint("selected title: %016lX - %s\n", app_metadata->title_id, selected_element->str);
consolePrint("______________________________\n\n");
} else
if (cur_menu->id >= MenuId_UserTitlesSubMenu && cur_menu->id < MenuId_Count)
if (cur_menu->id >= MenuId_UserTitlesSubMenu && cur_menu->id < MenuId_SystemTitles)
{
consolePrint("title info:\n\n");
consolePrint("name: %s\n", app_metadata->lang_entry.name);
consolePrint("publisher: %s\n", app_metadata->lang_entry.author);
if (cur_menu->id == MenuId_UserTitlesSubMenu || cur_menu->id == MenuId_NSPTitleTypes || cur_menu->id == MenuId_TicketTitleTypes || \
cur_menu->id == MenuId_NcaTitleTypes) consolePrint("title id: %016lX\n", app_metadata->title_id);
consolePrint("______________________________\n\n");
if (!is_system)
{
consolePrint("title info:\n\n");
consolePrint("name: %s\n", app_metadata->lang_entry.name);
consolePrint("publisher: %s\n", app_metadata->lang_entry.author);
if (cur_menu->id == MenuId_UserTitlesSubMenu || cur_menu->id == MenuId_NSPTitleTypes || cur_menu->id == MenuId_TicketTitleTypes || \
cur_menu->id == MenuId_NcaTitleTypes) consolePrint("title id: %016lX\n", app_metadata->title_id);
consolePrint("______________________________\n\n");
}
if (cur_menu->id == MenuId_NSP || cur_menu->id == MenuId_Ticket || cur_menu->id == MenuId_Nca || \
cur_menu->id == MenuId_NcaFsSections || cur_menu->id == MenuId_NcaFsSectionsSubMenu)
@ -957,6 +971,7 @@ int main(int argc, char *argv[])
}
consolePrint("selected title info:\n\n");
if (is_system) consolePrint("name: %s\n", app_metadata->lang_entry.name);
consolePrint("title id: %016lX\n", title_info->meta_key.id);
consolePrint("type: %s\n", titleGetNcmContentMetaTypeName(title_info->meta_key.type));
consolePrint("source storage: %s\n", titleGetNcmStorageIdName(title_info->storage_id));
@ -1004,7 +1019,7 @@ int main(int argc, char *argv[])
MenuElement *cur_element = cur_menu->elements[i];
MenuElementOption *cur_options = cur_element->element_options;
TitleApplicationMetadata *cur_app_metadata = (cur_menu->id == MenuId_UserTitles ? (TitleApplicationMetadata*)cur_element->userdata : NULL);
TitleApplicationMetadata *cur_app_metadata = ((cur_menu->id == MenuId_UserTitles || cur_menu->id == MenuId_SystemTitles) ? (TitleApplicationMetadata*)cur_element->userdata : NULL);
consolePrint("%s", i == cur_menu->selected ? " -> " : " ");
if (cur_app_metadata) consolePrint("%016lX - ", cur_app_metadata->title_id);
@ -1051,7 +1066,7 @@ int main(int argc, char *argv[])
if (titleIsGameCardInfoUpdated())
{
updateTitleList();
updateTitleList(&g_userTitlesMenu, &g_userTitlesSubMenu, false);
data_update = true;
break;
}
@ -1083,7 +1098,7 @@ int main(int argc, char *argv[])
} else
if (child_menu->id == MenuId_NSP || child_menu->id == MenuId_Ticket || child_menu->id == MenuId_Nca)
{
u32 title_type = *((u32*)selected_element->userdata);
u32 title_type = (cur_menu->id != MenuId_SystemTitles ? *((u32*)selected_element->userdata) : NcmContentMetaType_Unknown);
switch(title_type)
{
@ -1100,7 +1115,8 @@ int main(int argc, char *argv[])
title_info = user_app_data.aoc_patch_info;
break;
default:
title_info = NULL;
/* Get TitleInfo element on demand. */
title_info = titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, app_metadata->title_id);
break;
}
@ -1115,6 +1131,8 @@ int main(int argc, char *argv[])
consolePrint("failed to generate nca list\n");
error = true;
}
if (!error && cur_menu->id == MenuId_SystemTitles) is_system = true;
}
if (!error)
@ -1123,9 +1141,14 @@ int main(int argc, char *argv[])
title_info_idx = 1;
}
} else {
consolePrint("\nthe selected title doesn't have available %s data\n", \
title_type == NcmContentMetaType_Application ? "base application" : \
(title_type == NcmContentMetaType_Patch ? "update" : (title_type == NcmContentMetaType_AddOnContent ? "dlc" : "dlc update")));
if (cur_menu->id == MenuId_SystemTitles)
{
consolePrint("\nunable to retrieve data for system title %016lX\n", app_metadata->title_id);
} else {
consolePrint("\nthe selected title doesn't have available %s data\n", \
title_type == NcmContentMetaType_Application ? "base application" : \
(title_type == NcmContentMetaType_Patch ? "update" : (title_type == NcmContentMetaType_AddOnContent ? "dlc" : "dlc update")));
}
error = true;
}
@ -1278,7 +1301,7 @@ int main(int argc, char *argv[])
} else
if ((btn_down & HidNpadButton_B) && cur_menu->parent)
{
if (cur_menu->id == MenuId_UserTitles)
if (cur_menu->id == MenuId_UserTitles || cur_menu->id == MenuId_SystemTitles)
{
app_metadata = NULL;
} else
@ -1304,6 +1327,12 @@ int main(int argc, char *argv[])
if (cur_menu->id == MenuId_Nca)
{
freeNcaList();
if (is_system)
{
titleFreeTitleInfo(&title_info);
is_system = false;
}
} else
if (cur_menu->id == MenuId_NcaFsSections)
{
@ -1360,7 +1389,8 @@ int main(int argc, char *argv[])
freeNcaList();
freeTitleList();
freeTitleList(&g_systemTitlesMenu);
freeTitleList(&g_userTitlesMenu);
freeStorageList();
@ -1462,7 +1492,6 @@ void freeStorageList(void)
void updateStorageList(void)
{
char **tmp = NULL;
u32 elem_count = 0, idx = 0;
/* Free all previously allocated data. */
@ -1472,13 +1501,8 @@ void updateStorageList(void)
g_umsDevices = umsGetDevices(&g_umsDeviceCount);
elem_count = (2 + g_umsDeviceCount); // sd card, usb host, ums devices
/* Reallocate buffer. */
tmp = realloc(g_storageOptions, (elem_count + 1) * sizeof(char*)); // NULL terminator
g_storageOptions = tmp;
tmp = NULL;
memset(g_storageOptions, 0, (elem_count + 1) * sizeof(char*)); // NULL terminator
/* Allocate buffer. */
g_storageOptions = calloc(elem_count + 1, sizeof(char*)); // NULL terminator
/* Generate UMS device strings. */
for(u32 i = 0; i < elem_count; i++)
@ -1531,62 +1555,61 @@ void updateStorageList(void)
g_storageMenuElementOption.options = g_storageOptions;
}
void freeTitleList(void)
void freeTitleList(Menu *menu)
{
/* Free all previously allocated data. */
if (g_userTitlesMenuElements)
{
for(u32 i = 0; g_userTitlesMenuElements[i] != NULL; i++) free(g_userTitlesMenuElements[i]);
if (!menu) return;
free(g_userTitlesMenuElements);
g_userTitlesMenuElements = NULL;
MenuElement **elements = menu->elements;
/* Free all previously allocated data. */
if (elements)
{
for(u32 i = 0; elements[i]; i++) free(elements[i]);
free(elements);
}
g_userTitlesMenu.scroll = 0;
g_userTitlesMenu.selected = 0;
g_userTitlesMenu.elements = NULL;
menu->scroll = 0;
menu->selected = 0;
menu->elements = NULL;
}
void updateTitleList(void)
void updateTitleList(Menu *menu, Menu *submenu, bool is_system)
{
if (!menu || !submenu) return;
u32 app_count = 0, idx = 0;
TitleApplicationMetadata **app_metadata = NULL;
MenuElement **tmp = NULL;
MenuElement **elements = NULL;
/* Free all previously allocated data. */
freeTitleList();
freeTitleList(menu);
/* Get application metadata entries. */
app_metadata = titleGetApplicationMetadataEntries(false, &app_count);
app_metadata = titleGetApplicationMetadataEntries(is_system, &app_count);
if (!app_metadata || !app_count) goto end;
/* Reallocate buffer. */
tmp = realloc(g_userTitlesMenuElements, (app_count + 1) * sizeof(MenuElement*)); // NULL terminator
g_userTitlesMenuElements = tmp;
tmp = NULL;
memset(g_userTitlesMenuElements, 0, (app_count + 1) * sizeof(MenuElement*)); // NULL terminator
/* Allocate buffer. */
elements = calloc(app_count + 1, sizeof(MenuElement*)); // NULL terminator
/* Generate menu elements. */
for(u32 i = 0; i < app_count; i++)
{
TitleApplicationMetadata *cur_app_metadata = app_metadata[i];
if (!g_userTitlesMenuElements[idx])
if (!elements[idx])
{
g_userTitlesMenuElements[idx] = calloc(1, sizeof(MenuElement));
if (!g_userTitlesMenuElements[idx]) continue;
elements[idx] = calloc(1, sizeof(MenuElement));
if (!elements[idx]) continue;
}
g_userTitlesMenuElements[idx]->str = cur_app_metadata->lang_entry.name;
g_userTitlesMenuElements[idx]->child_menu = &g_userTitlesSubMenu;
g_userTitlesMenuElements[idx]->userdata = cur_app_metadata;
elements[idx]->str = cur_app_metadata->lang_entry.name;
elements[idx]->child_menu = submenu;
elements[idx]->userdata = cur_app_metadata;
idx++;
}
g_userTitlesMenu.elements = g_userTitlesMenuElements;
menu->elements = elements;
end:
if (app_metadata) free(app_metadata);
@ -1621,19 +1644,13 @@ void updateNcaList(TitleInfo *title_info)
{
u32 content_count = title_info->content_count, idx = 0;
NcmContentInfo *content_infos = title_info->content_infos;
MenuElement **tmp = NULL;
char nca_id_str[0x21] = {0};
/* Free all previously allocated data. */
freeNcaList();
/* Reallocate buffer. */
tmp = realloc(g_ncaMenuElements, (content_count + 2) * sizeof(MenuElement*)); // Output storage, NULL terminator
g_ncaMenuElements = tmp;
tmp = NULL;
memset(g_ncaMenuElements, 0, (content_count + 2) * sizeof(MenuElement*)); // Output storage, NULL terminator
/* Allocate buffer. */
g_ncaMenuElements = calloc(content_count + 2, sizeof(MenuElement*)); // Output storage, NULL terminator
/* Generate menu elements. */
for(u32 i = 0; i < content_count; i++)
@ -1733,19 +1750,13 @@ void updateNcaFsSectionsList(NcaUserData *nca_user_data)
{
TitleInfo *title_info = nca_user_data->title_info;
NcmContentInfo *content_info = &(title_info->content_infos[nca_user_data->content_idx]);
MenuElement **tmp = NULL;
u32 idx = 0;
/* Free all previously allocated data. */
freeNcaFsSectionsList();
/* Reallocate buffer. */
tmp = realloc(g_ncaFsSectionsMenuElements, (NCA_FS_HEADER_COUNT + 1) * sizeof(MenuElement*)); // NULL terminator
g_ncaFsSectionsMenuElements = tmp;
tmp = NULL;
memset(g_ncaFsSectionsMenuElements, 0, (NCA_FS_HEADER_COUNT + 1) * sizeof(MenuElement*)); // NULL terminator
/* Allocate buffer. */
g_ncaFsSectionsMenuElements = calloc(NCA_FS_HEADER_COUNT + 1, sizeof(MenuElement*)); // NULL terminator
/* Initialize NCA context. */
g_ncaFsSectionsMenuCtx = calloc(1, sizeof(NcaContext));
@ -1815,7 +1826,6 @@ void freeNcaBasePatchList(void)
void updateNcaBasePatchList(TitleUserApplicationData *user_app_data, TitleInfo *title_info, NcaFsSectionContext *nca_fs_ctx)
{
char **tmp = NULL;
u32 elem_count = 1, idx = 1; // "no" option
TitleInfo *cur_title_info = NULL;
@ -1847,6 +1857,7 @@ void updateNcaBasePatchList(TitleUserApplicationData *user_app_data, TitleInfo *
g_ncaBasePatchTitleInfo = titleGetAddOnContentBaseOrPatchList(title_info);
break;
default:
unsupported = true;
break;
}
} else {
@ -1856,13 +1867,8 @@ void updateNcaBasePatchList(TitleUserApplicationData *user_app_data, TitleInfo *
/* Calculate element count. */
elem_count += titleGetCountFromInfoBlock(g_ncaBasePatchTitleInfo);
/* Reallocate buffer. */
tmp = realloc(g_ncaBasePatchOptions, (elem_count + 1) * sizeof(char*)); // NULL terminator
g_ncaBasePatchOptions = tmp;
tmp = NULL;
memset(g_ncaBasePatchOptions, 0, (elem_count + 1) * sizeof(char*)); // NULL terminator
/* Allocate buffer. */
g_ncaBasePatchOptions = calloc(elem_count + 1, sizeof(char*)); // NULL terminator
/* Set first option. */
g_ncaBasePatchOptions[0] = (unsupported ? "unsupported by this content/section type combo" : (elem_count < 2 ? "none available" : "no"));
@ -3128,7 +3134,8 @@ static bool saveNintendoContentArchiveFsSection(void *userdata)
/* 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)) || \
(title_type < NcmContentMetaType_Application || \
((title_type == NcmContentMetaType_Application || title_type == NcmContentMetaType_Patch) && (content_type != NcmContentType_Program || nca_fs_ctx->section_idx > 1)) || \
((title_type == NcmContentMetaType_AddOnContent || title_type == NcmContentMetaType_DataPatch) && (content_type != NcmContentType_Data || nca_fs_ctx->section_idx != 0))))
{
consolePrint("layeredfs setting disabled (unsupported by current content/section type combo)\n");

View file

@ -1,502 +0,0 @@
/*
* main.c
*
* Copyright (c) 2020-2023, DarkMatterCore <pabloacurielz@gmail.com>.
*
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
*
* nxdumptool is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nxdumptool is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "nxdt_utils.h"
#include "nca.h"
#include "title.h"
#include "pfs.h"
#include "romfs.h"
#define BLOCK_SIZE 0x800000
#define OUTPATH "sdmc:/systitle_dumps"
bool g_borealisInitialized = false;
static PadState g_padState = {0};
static u8 *buf = NULL;
static FILE *filefd = NULL;
static char path[FS_MAX_PATH * 2] = {0};
static void utilsScanPads(void)
{
padUpdate(&g_padState);
}
static u64 utilsGetButtonsDown(void)
{
return padGetButtonsDown(&g_padState);
}
static u64 utilsGetButtonsHeld(void)
{
return padGetButtons(&g_padState);
}
static void utilsWaitForButtonPress(u64 flag)
{
/* Don't consider stick movement as button inputs. */
if (!flag) flag = ~(HidNpadButton_StickLLeft | HidNpadButton_StickLRight | HidNpadButton_StickLUp | HidNpadButton_StickLDown | HidNpadButton_StickRLeft | HidNpadButton_StickRRight | \
HidNpadButton_StickRUp | HidNpadButton_StickRDown);
while(appletMainLoop())
{
utilsScanPads();
if (utilsGetButtonsDown() & flag) break;
}
}
static void consolePrint(const char *text, ...)
{
va_list v;
va_start(v, text);
vfprintf(stdout, text, v);
va_end(v);
consoleUpdate(NULL);
}
static void dumpPartitionFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx)
{
if (!buf || !info || !nca_fs_ctx) return;
u32 pfs_entry_count = 0;
PartitionFileSystemContext pfs_ctx = {0};
PartitionFileSystemEntry *pfs_entry = NULL;
char *pfs_entry_name = NULL;
size_t path_len = 0;
*path = '\0';
if (!pfsInitializeContext(&pfs_ctx, nca_fs_ctx))
{
consolePrint("pfs initialize ctx failed!\n");
goto end;
}
if (!(pfs_entry_count = pfsGetEntryCount(&pfs_ctx)))
{
consolePrint("pfs entry count is zero!\n");
goto end;
}
snprintf(path, sizeof(path), OUTPATH "/%016lX - %s/%s (%s)/Section %u (%s)", info->meta_key.id, info->app_metadata->lang_entry.name, ((NcaContext*)nca_fs_ctx->nca_ctx)->content_id_str, \
titleGetNcmContentTypeName(((NcaContext*)nca_fs_ctx->nca_ctx)->content_type), nca_fs_ctx->section_idx, ncaGetFsSectionTypeName(nca_fs_ctx));
utilsCreateDirectoryTree(path, true);
path_len = strlen(path);
for(u32 i = 0; i < pfs_entry_count; i++)
{
if (!(pfs_entry = pfsGetEntryByIndex(&pfs_ctx, i)) || !(pfs_entry_name = pfsGetEntryNameByIndex(&pfs_ctx, i)) || !strlen(pfs_entry_name))
{
consolePrint("pfs get entry / get name #%u failed!\n", i);
goto end;
}
path[path_len] = '\0';
strcat(path, "/");
strcat(path, pfs_entry_name);
utilsReplaceIllegalCharacters(path + path_len + 1, true);
filefd = fopen(path, "wb");
if (!filefd)
{
consolePrint("failed to create \"%s\"!\n", path);
goto end;
}
/* Set file size. */
ftruncate(fileno(filefd), (off_t)pfs_entry->size);
consolePrint("dumping \"%s\"...\n", pfs_entry_name);
u64 blksize = BLOCK_SIZE;
for(u64 j = 0; j < pfs_entry->size; j += blksize)
{
if (blksize > (pfs_entry->size - j)) blksize = (pfs_entry->size - j);
if (!pfsReadEntryData(&pfs_ctx, pfs_entry, buf, blksize, j))
{
consolePrint("failed to read 0x%lX block from offset 0x%lX!\n", blksize, j);
goto end;
}
fwrite(buf, 1, blksize, filefd);
}
fclose(filefd);
filefd = NULL;
}
consolePrint("pfs dump complete\n");
end:
if (filefd)
{
fclose(filefd);
remove(path);
}
if (*path) utilsCommitSdCardFileSystemChanges();
pfsFreeContext(&pfs_ctx);
}
static void dumpRomFs(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx)
{
if (!buf || !info || !nca_fs_ctx) return;
RomFileSystemContext romfs_ctx = {0};
RomFileSystemFileEntry *romfs_file_entry = NULL;
size_t path_len = 0;
*path = '\0';
if (!romfsInitializeContext(&romfs_ctx, nca_fs_ctx, NULL))
{
consolePrint("romfs initialize ctx failed!\n");
goto end;
}
snprintf(path, sizeof(path), OUTPATH "/%016lX - %s/%s (%s)/Section %u (%s)", info->meta_key.id, info->app_metadata->lang_entry.name, ((NcaContext*)nca_fs_ctx->nca_ctx)->content_id_str, \
titleGetNcmContentTypeName(((NcaContext*)nca_fs_ctx->nca_ctx)->content_type), nca_fs_ctx->section_idx, ncaGetFsSectionTypeName(nca_fs_ctx));
utilsCreateDirectoryTree(path, true);
path_len = strlen(path);
while(romfsCanMoveToNextFileEntry(&romfs_ctx))
{
if (!(romfs_file_entry = romfsGetCurrentFileEntry(&romfs_ctx)) || \
!romfsGeneratePathFromFileEntry(&romfs_ctx, romfs_file_entry, path + path_len, sizeof(path) - path_len, RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly))
{
consolePrint("romfs get entry / generate path failed for 0x%lX!\n", romfs_ctx.cur_file_offset);
goto end;
}
utilsCreateDirectoryTree(path, false);
filefd = fopen(path, "wb");
if (!filefd)
{
consolePrint("failed to create \"%s\"!\n", path);
goto end;
}
/* Set file size. */
ftruncate(fileno(filefd), (off_t)romfs_file_entry->size);
consolePrint("dumping \"%s\"...\n", path + path_len);
u64 blksize = BLOCK_SIZE;
for(u64 j = 0; j < romfs_file_entry->size; j += blksize)
{
if (blksize > (romfs_file_entry->size - j)) blksize = (romfs_file_entry->size - j);
if (!romfsReadFileEntryData(&romfs_ctx, romfs_file_entry, buf, blksize, j))
{
consolePrint("failed to read 0x%lX block from offset 0x%lX!\n", blksize, j);
goto end;
}
fwrite(buf, 1, blksize, filefd);
}
fclose(filefd);
filefd = NULL;
if (!romfsMoveToNextFileEntry(&romfs_ctx))
{
consolePrint("failed to move to next file entry!\n");
goto end;
}
}
consolePrint("romfs dump complete\n");
end:
if (filefd)
{
fclose(filefd);
remove(path);
}
if (*path) utilsCommitSdCardFileSystemChanges();
romfsFreeContext(&romfs_ctx);
}
static void dumpFsSection(TitleInfo *info, NcaFsSectionContext *nca_fs_ctx)
{
if (!buf || !info || !nca_fs_ctx) return;
switch(nca_fs_ctx->section_type)
{
case NcaFsSectionType_PartitionFs:
dumpPartitionFs(info, nca_fs_ctx);
break;
case NcaFsSectionType_RomFs:
case NcaFsSectionType_Nca0RomFs:
dumpRomFs(info, nca_fs_ctx);
break;
default:
consolePrint("invalid section type!\n");
break;
}
}
int main(int argc, char *argv[])
{
int ret = EXIT_SUCCESS;
if (!utilsInitializeResources(argc, (const char**)argv))
{
ret = EXIT_FAILURE;
goto out;
}
/* Configure input. */
/* Up to 8 different, full controller inputs. */
/* Individual Joy-Cons not supported. */
padConfigureInput(8, HidNpadStyleSet_NpadFullCtrl);
padInitializeWithMask(&g_padState, 0x1000000FFUL);
consoleInit(NULL);
u32 app_count = 0;
TitleApplicationMetadata **app_metadata = NULL;
TitleInfo *cur_title_info = NULL;
u32 selected_idx = 0, menu = 0, page_size = 30, scroll = 0;
u32 title_idx = 0, title_scroll = 0, nca_idx = 0;
char nca_id_str[0x21] = {0};
NcaContext *nca_ctx = NULL;
bool applet_status = true;
app_metadata = titleGetApplicationMetadataEntries(true, &app_count);
if (!app_metadata || !app_count)
{
consolePrint("app metadata failed\n");
goto out2;
}
consolePrint("app metadata succeeded\n");
buf = malloc(BLOCK_SIZE);
if (!buf)
{
consolePrint("buf failed\n");
goto out2;
}
consolePrint("buf succeeded\n");
nca_ctx = calloc(1, sizeof(NcaContext));
if (!nca_ctx)
{
consolePrint("nca ctx buf failed\n");
goto out2;
}
consolePrint("nca ctx buf succeeded\n");
utilsSleep(1);
while((applet_status = appletMainLoop()))
{
consoleClear();
printf("select a %s.", menu == 0 ? "system title to view its contents" : (menu == 1 ? "content" : "fs section"));
printf("\npress b to %s.\n\n", menu == 0 ? "exit" : "go back");
if (menu == 0)
{
printf("title: %u / %u\n", selected_idx + 1, app_count);
printf("selected title: %016lX - %s\n\n", app_metadata[selected_idx]->title_id, app_metadata[selected_idx]->lang_entry.name);
}
if (menu >= 1) printf("selected title: %016lX - %s\n\n", app_metadata[title_idx]->title_id, app_metadata[title_idx]->lang_entry.name);
if (menu == 2) printf("selected content: %s (%s)\n\n", nca_id_str, titleGetNcmContentTypeName(cur_title_info->content_infos[nca_idx].content_type));
u32 max_val = (menu == 0 ? app_count : (menu == 1 ? cur_title_info->content_count : NCA_FS_HEADER_COUNT));
for(u32 i = scroll; i < max_val; i++)
{
if (i >= (scroll + page_size)) break;
printf("%s", i == selected_idx ? " -> " : " ");
if (menu == 0)
{
printf("%016lX - %s\n", app_metadata[i]->title_id, app_metadata[i]->lang_entry.name);
} else
if (menu == 1)
{
utilsGenerateHexStringFromData(nca_id_str, sizeof(nca_id_str), cur_title_info->content_infos[i].content_id.c, sizeof(cur_title_info->content_infos[i].content_id.c), false);
printf("%s (%s)\n", nca_id_str, titleGetNcmContentTypeName(cur_title_info->content_infos[i].content_type));
} else
if (menu == 2)
{
printf("fs section #%u (%s)\n", i + 1, ncaGetFsSectionTypeName(&(nca_ctx->fs_ctx[i])));
}
}
printf("\n");
consoleUpdate(NULL);
u64 btn_down = 0, btn_held = 0;
while((applet_status = appletMainLoop()))
{
utilsScanPads();
btn_down = utilsGetButtonsDown();
btn_held = utilsGetButtonsHeld();
if (btn_down || btn_held) break;
}
if (!applet_status) break;
if (btn_down & HidNpadButton_A)
{
bool error = false;
if (menu == 0)
{
title_idx = selected_idx;
title_scroll = scroll;
} else
if (menu == 1)
{
nca_idx = selected_idx;
utilsGenerateHexStringFromData(nca_id_str, sizeof(nca_id_str), cur_title_info->content_infos[nca_idx].content_id.c, sizeof(cur_title_info->content_infos[nca_idx].content_id.c), false);
}
menu++;
if (menu == 1)
{
cur_title_info = titleGetInfoFromStorageByTitleId(NcmStorageId_BuiltInSystem, app_metadata[title_idx]->title_id);
if (!cur_title_info)
{
consolePrint("failed to get title info\n");
error = true;
}
} else
if (menu == 2)
{
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;
}
} else
if (menu == 3)
{
consoleClear();
utilsSetLongRunningProcessState(true);
dumpFsSection(cur_title_info, &(nca_ctx->fs_ctx[selected_idx]));
utilsSetLongRunningProcessState(false);
}
if (error || menu >= 3)
{
consolePrint("press any button to continue\n");
utilsWaitForButtonPress(0);
menu--;
} else {
selected_idx = scroll = 0;
}
} else
if ((btn_down & HidNpadButton_Down) || (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown)))
{
selected_idx++;
if (selected_idx >= max_val)
{
if (btn_down & HidNpadButton_Down)
{
selected_idx = scroll = 0;
} else {
selected_idx = (max_val - 1);
}
} else
if (selected_idx >= (scroll + (page_size / 2)) && max_val > (scroll + page_size))
{
scroll++;
}
} else
if ((btn_down & HidNpadButton_Up) || (btn_held & (HidNpadButton_StickLUp | HidNpadButton_StickRUp)))
{
selected_idx--;
if (selected_idx == UINT32_MAX)
{
if (btn_down & HidNpadButton_Up)
{
selected_idx = (max_val - 1);
scroll = (max_val >= page_size ? (max_val - page_size) : 0);
} else {
selected_idx = 0;
}
} else
if (selected_idx < (scroll + (page_size / 2)) && scroll > 0)
{
scroll--;
}
} else
if (btn_down & HidNpadButton_B)
{
menu--;
if (menu == UINT32_MAX)
{
break;
} else {
selected_idx = (menu == 0 ? title_idx : nca_idx);
scroll = (menu == 0 ? title_scroll : 0);
if (menu == 0) titleFreeTitleInfo(&cur_title_info);
}
}
if (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp)) svcSleepThread(50000000); // 50 ms
}
if (!applet_status) menu = UINT32_MAX;
out2:
if (menu != UINT32_MAX)
{
consolePrint("press any button to exit\n");
utilsWaitForButtonPress(0);
}
if (nca_ctx) free(nca_ctx);
if (buf) free(buf);
if (app_metadata) free(app_metadata);
out:
utilsCloseResources();
consoleExit(NULL);
return ret;
}

View file

@ -60,6 +60,8 @@ typedef struct {
};
} SmcGenerateAesKekOption;
NXDT_ASSERT(SmcGenerateAesKekOption, 0x4);
/// Helper inline functions.
NX_INLINE void smcPrepareGenerateAesKekOption(bool is_device_unique, u32 key_type_idx, u32 seal_key_idx, SmcGenerateAesKekOption *out)

View file

@ -29,6 +29,8 @@
#define TITLE_STORAGE_COUNT 4 /* GameCard, BuiltInSystem, BuiltInUser, SdCard. */
#define TITLE_STORAGE_INDEX(storage_id) ((storage_id) - NcmStorageId_GameCard)
#define NCM_CMT_APP_OFFSET 0x7A
/* Type definitions. */
typedef struct {
@ -83,25 +85,31 @@ static const char *g_titleNcmContentTypeNames[] = {
};
static const char *g_titleNcmContentMetaTypeNames[] = {
[NcmContentMetaType_Unknown] = "Unknown",
[NcmContentMetaType_SystemProgram] = "SystemProgram",
[NcmContentMetaType_SystemData] = "SystemData",
[NcmContentMetaType_SystemUpdate] = "SystemUpdate",
[NcmContentMetaType_BootImagePackage] = "BootImagePackage",
[NcmContentMetaType_BootImagePackageSafe] = "BootImagePackageSafe",
[NcmContentMetaType_Application - 0x7A] = "Application",
[NcmContentMetaType_Patch - 0x7A] = "Patch",
[NcmContentMetaType_AddOnContent - 0x7A] = "AddOnContent",
[NcmContentMetaType_Delta - 0x7A] = "Delta",
[NcmContentMetaType_DataPatch - 0x7A] = "DataPatch"
[NcmContentMetaType_Unknown] = "Unknown",
[NcmContentMetaType_SystemProgram] = "SystemProgram",
[NcmContentMetaType_SystemData] = "SystemData",
[NcmContentMetaType_SystemUpdate] = "SystemUpdate",
[NcmContentMetaType_BootImagePackage] = "BootImagePackage",
[NcmContentMetaType_BootImagePackageSafe] = "BootImagePackageSafe",
[NcmContentMetaType_Application - NCM_CMT_APP_OFFSET] = "Application",
[NcmContentMetaType_Patch - NCM_CMT_APP_OFFSET] = "Patch",
[NcmContentMetaType_AddOnContent - NCM_CMT_APP_OFFSET] = "AddOnContent",
[NcmContentMetaType_Delta - NCM_CMT_APP_OFFSET] = "Delta",
[NcmContentMetaType_DataPatch - NCM_CMT_APP_OFFSET] = "DataPatch"
};
static const char *g_filenameTypeStrings[] = {
[NcmContentMetaType_Application - 0x80] = "BASE",
[NcmContentMetaType_Patch - 0x80] = "UPD",
[NcmContentMetaType_AddOnContent - 0x80] = "DLC",
[NcmContentMetaType_Delta - 0x80] = "DELTA",
[NcmContentMetaType_DataPatch - 0x80] = "DLCUPD"
[NcmContentMetaType_Unknown] = "UNK",
[NcmContentMetaType_SystemProgram] = "SYSPRG",
[NcmContentMetaType_SystemData] = "SYSDAT",
[NcmContentMetaType_SystemUpdate] = "SYSUPD",
[NcmContentMetaType_BootImagePackage] = "BIP",
[NcmContentMetaType_BootImagePackageSafe] = "BIPS",
[NcmContentMetaType_Application - NCM_CMT_APP_OFFSET] = "BASE",
[NcmContentMetaType_Patch - NCM_CMT_APP_OFFSET] = "UPD",
[NcmContentMetaType_AddOnContent - NCM_CMT_APP_OFFSET] = "DLC",
[NcmContentMetaType_Delta - NCM_CMT_APP_OFFSET] = "DELTA",
[NcmContentMetaType_DataPatch - NCM_CMT_APP_OFFSET] = "DLCUPD"
};
/* Info retrieved from https://switchbrew.org/wiki/Title_list. */
@ -1072,15 +1080,17 @@ bool titleIsGameCardInfoUpdated(void)
char *titleGenerateFileName(TitleInfo *title_info, u8 naming_convention, u8 illegal_char_replace_type)
{
if (!title_info || title_info->meta_key.type < NcmContentMetaType_Application || title_info->meta_key.type > NcmContentMetaType_DataPatch || \
naming_convention > TitleNamingConvention_IdAndVersionOnly || (naming_convention == TitleNamingConvention_Full && \
illegal_char_replace_type > TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly))
if (!title_info || (title_info->meta_key.type > NcmContentMetaType_BootImagePackageSafe && title_info->meta_key.type < NcmContentMetaType_Application) || \
title_info->meta_key.type > NcmContentMetaType_DataPatch || naming_convention > TitleNamingConvention_IdAndVersionOnly || \
(naming_convention == TitleNamingConvention_Full && illegal_char_replace_type > TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly))
{
LOG_MSG_ERROR("Invalid parameters!");
return NULL;
}
u8 type = (title_info->meta_key.type - 0x80);
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;
/* Generate filename for this title. */
@ -1101,11 +1111,11 @@ char *titleGenerateFileName(TitleInfo *title_info, u8 naming_convention, u8 ille
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]);
sprintf(title_name + strlen(title_name), "[%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]);
sprintf(title_name, "%016lX_v%u_%s", title_info->meta_key.id, title_info->meta_key.version, g_filenameTypeStrings[type_idx]);
}
/* Duplicate generated filename. */
@ -1251,7 +1261,7 @@ const char *titleGetNcmContentTypeName(u8 content_type)
const char *titleGetNcmContentMetaTypeName(u8 content_meta_type)
{
if ((content_meta_type > NcmContentMetaType_BootImagePackageSafe && content_meta_type < NcmContentMetaType_Application) || content_meta_type > NcmContentMetaType_DataPatch) return NULL;
return (content_meta_type <= NcmContentMetaType_BootImagePackageSafe ? g_titleNcmContentMetaTypeNames[content_meta_type] : g_titleNcmContentMetaTypeNames[content_meta_type - 0x7A]);
return (content_meta_type <= NcmContentMetaType_BootImagePackageSafe ? g_titleNcmContentMetaTypeNames[content_meta_type] : g_titleNcmContentMetaTypeNames[content_meta_type - NCM_CMT_APP_OFFSET]);
}
NX_INLINE void titleFreeApplicationMetadata(void)