mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-22 18:26:39 +00:00
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".
This commit is contained in:
parent
0e70eb0912
commit
bafe23b14e
15 changed files with 785 additions and 85 deletions
|
@ -122,6 +122,18 @@ typedef struct {
|
||||||
NcaContext *nca_ctx;
|
NcaContext *nca_ctx;
|
||||||
} NcaThreadData;
|
} 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. */
|
/* Function prototypes. */
|
||||||
|
|
||||||
static void utilsScanPads(void);
|
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 *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 *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);
|
static bool dumpGameCardSecurityInformation(GameCardSecurityInformation *out);
|
||||||
|
|
||||||
|
@ -178,6 +191,13 @@ static bool saveNintendoSubmissionPackage(void *userdata);
|
||||||
static bool saveTicket(void *userdata);
|
static bool saveTicket(void *userdata);
|
||||||
|
|
||||||
static bool saveNintendoContentArchive(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);
|
static void xciReadThreadFunc(void *arg);
|
||||||
|
|
||||||
|
@ -186,6 +206,12 @@ static void extractedHfsReadThreadFunc(void *arg);
|
||||||
|
|
||||||
static void ncaReadThreadFunc(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 void genericWriteThreadFunc(void *arg);
|
||||||
|
|
||||||
static bool spanDumpThreads(ThreadFunc read_func, ThreadFunc write_func, 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 u32 getTicketRemoveConsoleDataOption(void);
|
||||||
static void setTicketRemoveConsoleDataOption(u32 idx);
|
static void setTicketRemoveConsoleDataOption(u32 idx);
|
||||||
|
|
||||||
static u32 getNcaFsWriteSectionImageOption(void);
|
static u32 getNcaFsWriteRawSectionOption(void);
|
||||||
static void setNcaFsWriteSectionImageOption(u32 idx);
|
static void setNcaFsWriteRawSectionOption(u32 idx);
|
||||||
|
|
||||||
static u32 getNcaFsUseLayeredFsDirOption(void);
|
static u32 getNcaFsUseLayeredFsDirOption(void);
|
||||||
static void setNcaFsUseLayeredFsDirOption(u32 idx);
|
static void setNcaFsUseLayeredFsDirOption(u32 idx);
|
||||||
|
@ -618,7 +644,7 @@ static Menu g_ticketMenu = {
|
||||||
.elements = g_ticketMenuElements
|
.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 char **g_ncaBasePatchOptions = NULL;
|
||||||
|
|
||||||
static MenuElementOption g_ncaFsSectionsSubMenuBasePatchElementOption = {
|
static MenuElementOption g_ncaFsSectionsSubMenuBasePatchElementOption = {
|
||||||
|
@ -632,7 +658,7 @@ static MenuElement *g_ncaFsSectionsSubMenuElements[] = {
|
||||||
&(MenuElement){
|
&(MenuElement){
|
||||||
.str = "start nca fs dump",
|
.str = "start nca fs dump",
|
||||||
.child_menu = NULL,
|
.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,
|
.element_options = NULL,
|
||||||
.userdata = NULL // Dynamically set
|
.userdata = NULL // Dynamically set
|
||||||
},
|
},
|
||||||
|
@ -644,13 +670,13 @@ static MenuElement *g_ncaFsSectionsSubMenuElements[] = {
|
||||||
.userdata = NULL
|
.userdata = NULL
|
||||||
},
|
},
|
||||||
&(MenuElement){
|
&(MenuElement){
|
||||||
.str = "write section image",
|
.str = "write raw section",
|
||||||
.child_menu = NULL,
|
.child_menu = NULL,
|
||||||
.task_func = NULL,
|
.task_func = NULL,
|
||||||
.element_options = &(MenuElementOption){
|
.element_options = &(MenuElementOption){
|
||||||
.selected = 0,
|
.selected = 0,
|
||||||
.getter_func = &getNcaFsWriteSectionImageOption,
|
.getter_func = &getNcaFsWriteRawSectionOption,
|
||||||
.setter_func = &setNcaFsWriteSectionImageOption,
|
.setter_func = &setNcaFsWriteRawSectionOption,
|
||||||
.options = g_noYesStrings
|
.options = g_noYesStrings
|
||||||
},
|
},
|
||||||
.userdata = NULL
|
.userdata = NULL
|
||||||
|
@ -1212,13 +1238,23 @@ int main(int argc, char *argv[])
|
||||||
} else
|
} else
|
||||||
if ((btn_down & (HidNpadButton_Right | HidNpadButton_StickLRight | HidNpadButton_StickRRight)) && selected_element_options)
|
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++;
|
selected_element_options->selected++;
|
||||||
if (!selected_element_options->options[selected_element_options->selected]) 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);
|
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
|
} else
|
||||||
if ((btn_down & (HidNpadButton_Left | HidNpadButton_StickLLeft | HidNpadButton_StickRLeft)) && selected_element_options)
|
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);
|
if (selected_element_options->setter_func) selected_element_options->setter_func(selected_element_options->selected);
|
||||||
|
|
||||||
/* Point to the previous base/patch title. */
|
/* Point to the previous base/patch title. */
|
||||||
if (cur_menu->id == MenuId_NcaFsSectionsSubMenu && cur_menu->selected == 1 && g_ncaBasePatchTitleInfo && g_ncaBasePatchTitleInfo->previous)
|
if (cur_menu->id == MenuId_NcaFsSectionsSubMenu && cur_menu->selected == 1)
|
||||||
g_ncaBasePatchTitleInfo = g_ncaBasePatchTitleInfo->previous;
|
{
|
||||||
|
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
|
} else
|
||||||
if ((btn_down & HidNpadButton_B) && cur_menu->parent)
|
if ((btn_down & HidNpadButton_B) && cur_menu->parent)
|
||||||
{
|
{
|
||||||
|
@ -1704,7 +1750,7 @@ void updateNcaFsSectionsList(NcaUserData *nca_user_data)
|
||||||
/* Initialize NCA context. */
|
/* Initialize NCA context. */
|
||||||
g_ncaFsSectionsMenuCtx = calloc(1, sizeof(NcaContext));
|
g_ncaFsSectionsMenuCtx = calloc(1, sizeof(NcaContext));
|
||||||
if (!ncaInitializeContext(g_ncaFsSectionsMenuCtx, title_info->storage_id, (title_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \
|
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. */
|
/* Generate menu elements. */
|
||||||
for(u32 i = 0; i < NCA_FS_HEADER_COUNT; i++)
|
for(u32 i = 0; i < NCA_FS_HEADER_COUNT; i++)
|
||||||
|
@ -1764,7 +1810,7 @@ void freeNcaBasePatchList(void)
|
||||||
titleFreeTitleInfo(&g_ncaBasePatchTitleInfo);
|
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)
|
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;
|
u8 section_type = nca_fs_ctx->section_type;
|
||||||
bool unsupported = false;
|
bool unsupported = false;
|
||||||
|
|
||||||
|
u32 selected_version = 0;
|
||||||
|
|
||||||
/* Free all previously allocated data. */
|
/* Free all previously allocated data. */
|
||||||
freeNcaBasePatchList();
|
freeNcaBasePatchList();
|
||||||
|
|
||||||
/* Only enable base/patch list if we're dealing with supported content types and/or FS section types. */
|
/* 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. */
|
/* Retrieve corresponding TitleInfo linked list for the current title type. */
|
||||||
switch(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
|
memset(g_ncaBasePatchOptions, 0, (elem_count + 1) * sizeof(char*)); // NULL terminator
|
||||||
|
|
||||||
/* Set first option. */
|
/* 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. */
|
/* Generate base/patch strings. */
|
||||||
cur_title_info = g_ncaBasePatchTitleInfo;
|
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, \
|
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));
|
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;
|
cur_title_info = cur_title_info->next;
|
||||||
|
|
||||||
idx++;
|
idx++;
|
||||||
|
@ -1844,6 +1903,10 @@ void updateNcaBasePatchList(TitleUserApplicationData *user_app_data, TitleInfo *
|
||||||
g_ncaFsSectionsSubMenuBasePatchElementOption.options = g_ncaBasePatchOptions;
|
g_ncaFsSectionsSubMenuBasePatchElementOption.options = g_ncaBasePatchOptions;
|
||||||
|
|
||||||
g_ncaFsSectionsSubMenuElements[0]->userdata = nca_fs_ctx;
|
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)
|
NX_INLINE bool useUsbHost(void)
|
||||||
|
@ -1999,17 +2062,12 @@ static char *generateOutputGameCardFileName(const char *subdir, const char *exte
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dev_idx == 1)
|
if (dev_idx != 1) sprintf(prefix, "%s/" OUTDIR, dev_idx == 0 ? DEVOPTAB_SDMC_DEVICE : g_umsDevices[dev_idx - 2].name);
|
||||||
{
|
|
||||||
if (subdir) sprintf(prefix, "/%s", subdir);
|
|
||||||
} else {
|
|
||||||
sprintf(prefix, "%s/" OUTDIR, dev_idx == 0 ? DEVOPTAB_SDMC_DEVICE : g_umsDevices[dev_idx - 2].name);
|
|
||||||
|
|
||||||
if (subdir)
|
if (subdir)
|
||||||
{
|
{
|
||||||
if (subdir[0] != '/') strcat(prefix, "/");
|
if (subdir[0] != '/') strcat(prefix, "/");
|
||||||
strcat(prefix, subdir);
|
strcat(prefix, subdir);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
output = (use_nacp_name ? utilsGeneratePath(prefix, filename, extension) : utilsGeneratePath(prefix, extension, NULL));
|
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;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dev_idx == 1)
|
if (dev_idx != 1) sprintf(prefix, "%s/" OUTDIR, dev_idx == 0 ? DEVOPTAB_SDMC_DEVICE : g_umsDevices[dev_idx - 2].name);
|
||||||
{
|
|
||||||
if (subdir) sprintf(prefix, "/%s", subdir);
|
|
||||||
} else {
|
|
||||||
sprintf(prefix, "%s/" OUTDIR, dev_idx == 0 ? DEVOPTAB_SDMC_DEVICE : g_umsDevices[dev_idx - 2].name);
|
|
||||||
|
|
||||||
if (subdir)
|
if (subdir)
|
||||||
{
|
{
|
||||||
if (subdir[0] != '/') strcat(prefix, "/");
|
if (subdir[0] != '/') strcat(prefix, "/");
|
||||||
strcat(prefix, subdir);
|
strcat(prefix, subdir);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
output = utilsGeneratePath(prefix, filename, extension);
|
output = utilsGeneratePath(prefix, filename, extension);
|
||||||
|
@ -2063,17 +2116,42 @@ end:
|
||||||
return output;
|
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)
|
static bool dumpGameCardSecurityInformation(GameCardSecurityInformation *out)
|
||||||
{
|
{
|
||||||
|
@ -2818,7 +2896,7 @@ static bool saveTicket(void *userdata)
|
||||||
|
|
||||||
/* Initialize NCA context. */
|
/* Initialize NCA context. */
|
||||||
if (!ncaInitializeContext(nca_ctx, title_info->storage_id, (title_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \
|
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");
|
consolePrint("nca initialize ctx failed\n");
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -2902,7 +2980,7 @@ static bool saveNintendoContentArchive(void *userdata)
|
||||||
|
|
||||||
/* Initialize NCA context. */
|
/* Initialize NCA context. */
|
||||||
if (!ncaInitializeContext(nca_thread_data.nca_ctx, title_info->storage_id, (title_info->storage_id == NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0), \
|
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");
|
consolePrint("nca initialize ctx failed\n");
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -2997,6 +3075,320 @@ end:
|
||||||
|
|
||||||
return success;
|
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)
|
static void xciReadThreadFunc(void *arg)
|
||||||
{
|
{
|
||||||
|
@ -3436,6 +3828,309 @@ end:
|
||||||
threadExit();
|
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)
|
static void genericWriteThreadFunc(void *arg)
|
||||||
{
|
{
|
||||||
SharedThreadData *shared_thread_data = (SharedThreadData*)arg; // UB but we don't care
|
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]);
|
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), \
|
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");
|
consolePrint("meta nca initialize ctx failed\n");
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -3728,7 +4423,8 @@ static void nspThreadFunc(void *arg)
|
||||||
if (content_info->content_type == NcmContentType_Meta) continue;
|
if (content_info->content_type == NcmContentType_Meta) continue;
|
||||||
|
|
||||||
NcaContext *cur_nca_ctx = &(nca_ctx[j]);
|
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);
|
consolePrint("%s #%u initialize nca ctx failed\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset);
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -4544,14 +5240,14 @@ static void setTicketRemoveConsoleDataOption(u32 idx)
|
||||||
configSetBoolean("ticket/remove_console_data", (bool)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)
|
static u32 getNcaFsUseLayeredFsDirOption(void)
|
||||||
|
|
|
@ -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);
|
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), \
|
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");
|
consolePrint("nca initialize base ctx failed\n");
|
||||||
goto out2;
|
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);
|
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), \
|
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");
|
consolePrint("nca initialize update ctx failed\n");
|
||||||
goto out2;
|
goto out2;
|
||||||
|
|
|
@ -401,7 +401,7 @@ int main(int argc, char *argv[])
|
||||||
} else
|
} else
|
||||||
if (menu == 2)
|
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");
|
consolePrint("nca initialize ctx failed\n");
|
||||||
error = true;
|
error = true;
|
||||||
|
|
|
@ -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);
|
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), \
|
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");
|
consolePrint("nca initialize base ctx failed\n");
|
||||||
goto out2;
|
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);
|
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), \
|
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");
|
consolePrint("nca initialize update ctx failed\n");
|
||||||
goto out2;
|
goto out2;
|
||||||
|
|
|
@ -281,7 +281,7 @@ int main(int argc, char *argv[])
|
||||||
if (content_info->content_type == NcmContentType_Meta) continue;
|
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), \
|
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);
|
consolePrint("%s #%u initialize nca ctx failed\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset);
|
||||||
goto out2;
|
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), \
|
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;
|
goto out2;
|
||||||
}
|
}
|
||||||
|
|
||||||
consolePrint("Meta nca initialize ctx succeeded\n");
|
consolePrint("meta nca initialize ctx succeeded\n");
|
||||||
|
|
||||||
if (!cnmtInitializeContext(&cnmt_ctx, &(nca_ctx[meta_idx])))
|
if (!cnmtInitializeContext(&cnmt_ctx, &(nca_ctx[meta_idx])))
|
||||||
{
|
{
|
||||||
|
|
|
@ -430,7 +430,10 @@ struct _NcaContext {
|
||||||
u8 storage_id; ///< NcmStorageId.
|
u8 storage_id; ///< NcmStorageId.
|
||||||
NcmContentStorage *ncm_storage; ///< Pointer to a NcmContentStorage instance. Used to read NCA data from eMMC/SD.
|
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.
|
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];
|
char content_id_str[0x21];
|
||||||
u8 hash[SHA256_HASH_SIZE]; ///< Manually calculated (if needed).
|
u8 hash[SHA256_HASH_SIZE]; ///< Manually calculated (if needed).
|
||||||
char hash_str[0x41];
|
char hash_str[0x41];
|
||||||
|
@ -440,7 +443,6 @@ struct _NcaContext {
|
||||||
char content_size_str[0x10]; ///< Placed here for convenience.
|
char content_size_str[0x10]; ///< Placed here for convenience.
|
||||||
u8 key_generation; ///< NcaKeyGeneration. Retrieved from the decrypted header.
|
u8 key_generation; ///< NcaKeyGeneration. Retrieved from the decrypted header.
|
||||||
u8 id_offset; ///< Retrieved from NcmContentInfo.
|
u8 id_offset; ///< Retrieved from NcmContentInfo.
|
||||||
u32 title_version;
|
|
||||||
bool rights_id_available;
|
bool rights_id_available;
|
||||||
bool titlekey_retrieved;
|
bool titlekey_retrieved;
|
||||||
bool valid_main_signature;
|
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 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 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()).
|
/// 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().
|
/// 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.
|
/// Input offset must be relative to the start of the NCA content file.
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
"remove_console_data": true
|
"remove_console_data": true
|
||||||
},
|
},
|
||||||
"nca_fs": {
|
"nca_fs": {
|
||||||
"write_section_image": false,
|
"write_raw_section": false,
|
||||||
"use_layeredfs_dir": false
|
"use_layeredfs_dir": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,8 +100,8 @@ bool bfttfInitialize(void)
|
||||||
/* Initialize NCA context. */
|
/* Initialize NCA context. */
|
||||||
/* NCA contexts don't need to be freed beforehand. */
|
/* NCA contexts don't need to be freed beforehand. */
|
||||||
/* Don't allow invalid NCA signatures. */
|
/* Don't allow invalid NCA signatures. */
|
||||||
bool nca_ctx_init = (ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Data, 0), \
|
bool nca_ctx_init = (ncaInitializeContext(nca_ctx, NcmStorageId_BuiltInSystem, 0, &(title_info->meta_key), \
|
||||||
title_info->version.value, NULL) && nca_ctx->valid_main_signature);
|
titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Data, 0), NULL) && nca_ctx->valid_main_signature);
|
||||||
|
|
||||||
/* Free title info. */
|
/* Free title info. */
|
||||||
titleFreeTitleInfo(&title_info);
|
titleFreeTitleInfo(&title_info);
|
||||||
|
|
|
@ -285,18 +285,18 @@ end:
|
||||||
|
|
||||||
static bool configValidateJsonNcaFsObject(const struct json_object *obj)
|
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;
|
if (!jsonValidateObject(obj)) goto end;
|
||||||
|
|
||||||
json_object_object_foreach(obj, key, val)
|
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);
|
CONFIG_VALIDATE_FIELD(Boolean, use_layeredfs_dir);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = (write_section_image_found && use_layeredfs_dir_found);
|
ret = (write_raw_section_found && use_layeredfs_dir_found);
|
||||||
|
|
||||||
end:
|
end:
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -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;
|
NcmContentStorage *ncm_storage = NULL;
|
||||||
u8 valid_fs_section_cnt = 0;
|
u8 valid_fs_section_cnt = 0;
|
||||||
|
|
||||||
if (!out || (storage_id != NcmStorageId_GameCard && !(ncm_storage = titleGetNcmStorageByStorageId(storage_id))) || \
|
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)) || \
|
(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!");
|
LOG_MSG_ERROR("Invalid parameters!");
|
||||||
return false;
|
return false;
|
||||||
|
@ -195,6 +195,10 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
|
||||||
out->storage_id = storage_id;
|
out->storage_id = storage_id;
|
||||||
out->ncm_storage = (out->storage_id != NcmStorageId_GameCard ? ncm_storage : NULL);
|
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));
|
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);
|
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->content_type = content_info->content_type;
|
||||||
out->id_offset = content_info->id_offset;
|
out->id_offset = content_info->id_offset;
|
||||||
out->title_version = title_version;
|
|
||||||
|
|
||||||
ncmContentInfoSizeToU64(content_info, &(out->content_size));
|
ncmContentInfoSizeToU64(content_info, &(out->content_size));
|
||||||
utilsGenerateFormattedSizeString((double)out->content_size, out->content_size_str, sizeof(out->content_size_str));
|
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);
|
NcaBucketInfo *compression_bucket = &(fs_ctx->header.compression_info.bucket);
|
||||||
|
|
||||||
bool success = false;
|
bool skip_extra_checks = false, success = false;
|
||||||
|
|
||||||
/* Fill section context. */
|
/* Fill section context. */
|
||||||
fs_ctx->enabled = false;
|
fs_ctx->enabled = false;
|
||||||
|
@ -897,21 +900,20 @@ static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx)
|
||||||
goto end;
|
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. */
|
/* Update context. */
|
||||||
LOG_MSG_WARNING("Empty SparseInfo data detected for FS section #%u in \"%s\". Skipping FS section.", section_idx, nca_ctx->content_id_str);
|
fs_ctx->sparse_table_offset = (sparse_info->physical_offset + sparse_bucket->offset);
|
||||||
success = true;
|
fs_ctx->section_size = raw_storage_size;
|
||||||
goto end;
|
} 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. */
|
/* 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);
|
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;
|
goto end;
|
||||||
|
|
|
@ -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_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_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->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->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->indirect_storage || !patch_ctx->aes_ctr_ex_storage || (base_ctx->base_storage_type == NcaStorageBaseStorageType_Compressed && \
|
||||||
patch_ctx->base_storage_type != NcaStorageBaseStorageType_Compressed))
|
patch_ctx->base_storage_type != NcaStorageBaseStorageType_Compressed))
|
||||||
|
|
|
@ -107,7 +107,7 @@ bool bfsarInitialize(void)
|
||||||
|
|
||||||
/* Initialize NCA context. */
|
/* Initialize NCA context. */
|
||||||
/* Don't allow invalid NCA signatures. */
|
/* 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)
|
!nca_ctx->valid_main_signature)
|
||||||
{
|
{
|
||||||
LOG_MSG_ERROR("Failed to initialize qlaunch Program NCA context!");
|
LOG_MSG_ERROR("Failed to initialize qlaunch Program NCA context!");
|
||||||
|
|
|
@ -862,7 +862,7 @@ char *utilsGeneratePath(const char *prefix, const char *filename, const char *ex
|
||||||
|
|
||||||
bool use_prefix = (prefix && *prefix);
|
bool use_prefix = (prefix && *prefix);
|
||||||
size_t prefix_len = (use_prefix ? strlen(prefix) : 0);
|
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);
|
bool use_extension = (extension && *extension);
|
||||||
size_t extension_len = (use_extension ? strlen(extension) : 0);
|
size_t extension_len = (use_extension ? strlen(extension) : 0);
|
||||||
|
|
|
@ -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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2709,7 +2709,7 @@ static char *titleGetPatchVersionString(TitleInfo *title_info)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initialize NCA context. */
|
/* 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);
|
LOG_MSG_ERROR("Failed to initialize NCA context for Control NCA from %016lX!", title_info->meta_key.id);
|
||||||
goto end;
|
goto end;
|
||||||
|
|
Loading…
Reference in a new issue