1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-08 11:51:48 +00:00
nxdumptool/code_templates/nsp_dumper.c
Pablo Curiel a9b5f7211c Some changes. Read full commit message.
* title: implemented titleGetNcmStorageIdName().

* nxdt_log: implemented log verbosity levels (debug, info, warning, error, none) and helper macros for each level. The rest of the codebase still needs to be updated to take advantange of this change.

* nxdt_log: implemented auxiliary logging via nxlink, if available.

* nxdt_utils: system CPU/MEM overclocking is now only applied through utilsSetLongRunningProcessState(), as it should have been from the beginning.

* nxdt_utils: nxlink initialization is now carried out without redirecting stdout and/or stderr, entirely removing the need for utilsRestoreConsoleOutput(). utilsGetNxLinkFileDescriptor() is used to send data to the nxlink host via dprintf() in log functions.
2022-07-12 02:27:03 +02:00

1481 lines
51 KiB
C

/*
* main.c
*
* Copyright (c) 2020, 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 "gamecard.h"
#include "title.h"
#include "cnmt.h"
#include "program_info.h"
#include "nacp.h"
#include "legal_info.h"
#include "cert.h"
#include "usb.h"
#define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE
#define OUTPATH "/nsp/"
bool g_borealisInitialized = false;
static PadState g_padState = {0};
typedef struct
{
void *data;
size_t data_written;
size_t total_size;
bool error;
bool transfer_cancelled;
} ThreadSharedData;
static const char *dump_type_strings[] = {
"dump base application",
"dump update",
"dump dlc"
};
static const u32 dump_type_strings_count = MAX_ELEMENTS(dump_type_strings);
typedef struct {
char str[64];
u32 val;
} options_t;
static options_t options[] = {
{ "set download distribution type", 0 },
{ "remove console specific data", 1 },
{ "remove titlekey crypto (overrides previous option)", 0 },
{ "disable linked account requirement", 0 },
{ "enable screenshots", 0 },
{ "enable video capture", 0 },
{ "disable hdcp", 0 },
{ "append authoringtool data", 1 },
{ "output device", 0 }
};
static const u32 options_count = MAX_ELEMENTS(options);
static Mutex g_conMutex = 0;
static UsbHsFsDevice *ums_devices = NULL;
static u32 ums_device_count = 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, ...)
{
mutexLock(&g_conMutex);
va_list v;
va_start(v, text);
vfprintf(stdout, text, v);
va_end(v);
mutexUnlock(&g_conMutex);
}
static void consoleRefresh(void)
{
mutexLock(&g_conMutex);
consoleUpdate(NULL);
mutexUnlock(&g_conMutex);
}
static void dump_thread_func(void *arg)
{
ThreadSharedData *shared_data = (ThreadSharedData*)arg;
TitleInfo *title_info = NULL;
bool set_download_type = (options[0].val == 1);
bool remove_console_data = (options[1].val == 1);
bool remove_titlekey_crypto = (options[2].val == 1);
bool patch_sua = (options[3].val == 1);
bool patch_screenshot = (options[4].val == 1);
bool patch_video_capture = (options[5].val == 1);
bool patch_hdcp = (options[6].val == 1);
bool append_authoringtool_data = (options[7].val == 1);
u32 output_device = options[8].val;
bool success = false;
UsbHsFsDevice *ums_device = (output_device < 2 ? NULL : &(ums_devices[output_device - 2]));
u64 free_space = 0;
u8 *buf = NULL;
char *dump_name = NULL, *path = NULL;
FILE *fd = NULL;
NcaContext *nca_ctx = NULL;
NcaContext *meta_nca_ctx = NULL;
ContentMetaContext cnmt_ctx = {0};
ProgramInfoContext *program_info_ctx = NULL;
u32 program_idx = 0, program_count = 0;
NacpContext *nacp_ctx = NULL;
u32 control_idx = 0, control_count = 0;
LegalInfoContext *legal_info_ctx = NULL;
u32 legal_info_idx = 0, legal_info_count = 0;
Ticket tik = {0};
TikCommonBlock *tik_common_block = NULL;
u8 *raw_cert_chain = NULL;
u64 raw_cert_chain_size = 0;
PartitionFileSystemFileContext pfs_file_ctx = {0};
pfsInitializeFileContext(&pfs_file_ctx);
char entry_name[64] = {0};
u64 nsp_header_size = 0, nsp_size = 0, nsp_offset = 0;
char *tmp_name = NULL;
Sha256Context sha256_ctx = {0};
u8 sha256_hash[SHA256_HASH_SIZE] = {0};
if (!shared_data || !(title_info = (TitleInfo*)shared_data->data) || !title_info->content_count || !title_info->content_infos) goto end;
if (ums_device && ums_device->write_protect)
{
consolePrint("device \"%s\" has write protection enabled!\n", ums_device->name);
goto end;
}
/* Allocate memory for the dump process. */
if (!(buf = usbAllocatePageAlignedBuffer(BLOCK_SIZE)))
{
consolePrint("buf alloc failed\n");
goto end;
}
/* Generate output path. */
if (!(dump_name = titleGenerateFileName(title_info, TitleNamingConvention_Full, output_device == 0 ? TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly : TitleFileNameIllegalCharReplaceType_IllegalFsChars)))
{
consolePrint("title generate file name failed\n");
goto end;
}
if (output_device != 1) sprintf(entry_name, "%s" OUTPATH, ums_device ? ums_device->name : DEVOPTAB_SDMC_DEVICE);
if (!(path = utilsGeneratePath(entry_name, dump_name, ".nsp")))
{
consolePrint("generate path failed\n");
goto end;
}
// get device free space
if (output_device != 1 && !utilsGetFileSystemStatsByPath(path, NULL, &free_space))
{
consolePrint("failed to retrieve free space from selected device\n");
goto end;
}
if (!(nca_ctx = calloc(title_info->content_count, sizeof(NcaContext))))
{
consolePrint("nca ctx calloc failed\n");
goto end;
}
// determine if we should initialize programinfo ctx
if (append_authoringtool_data)
{
program_count = titleGetContentCountByType(title_info, NcmContentType_Program);
if (program_count && !(program_info_ctx = calloc(program_count, sizeof(ProgramInfoContext))))
{
consolePrint("program info ctx calloc failed\n");
goto end;
}
}
// determine if we should initialize nacp ctx
if (patch_sua || patch_screenshot || patch_video_capture || patch_hdcp || append_authoringtool_data)
{
control_count = titleGetContentCountByType(title_info, NcmContentType_Control);
if (control_count && !(nacp_ctx = calloc(control_count, sizeof(NacpContext))))
{
consolePrint("nacp ctx calloc failed\n");
goto end;
}
}
// determine if we should initialize legalinfo ctx
if (append_authoringtool_data)
{
legal_info_count = titleGetContentCountByType(title_info, NcmContentType_LegalInformation);
if (legal_info_count && !(legal_info_ctx = calloc(legal_info_count, sizeof(LegalInfoContext))))
{
consolePrint("legal info ctx calloc failed\n");
goto end;
}
}
// set meta nca as the last nca
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 ? GameCardHashFileSystemPartitionType_Secure : 0), \
titleGetContentInfoByTypeAndIdOffset(title_info, NcmContentType_Meta, 0), title_info->version.value, &tik))
{
consolePrint("Meta nca initialize ctx failed\n");
goto end;
}
consolePrint("Meta nca initialize ctx succeeded\n");
if (!cnmtInitializeContext(&cnmt_ctx, meta_nca_ctx))
{
consolePrint("cnmt initialize ctx failed\n");
goto end;
}
consolePrint("cnmt initialize ctx succeeded (%s)\n", meta_nca_ctx->content_id_str);
// initialize nca context
// initialize content type context
// generate nca patches (if needed)
// generate content type xml
for(u32 i = 0, j = 0; i < title_info->content_count; i++)
{
// skip meta nca since we already initialized it
NcmContentInfo *content_info = &(title_info->content_infos[i]);
if (content_info->content_type == NcmContentType_Meta) continue;
NcaContext *cur_nca_ctx = &(nca_ctx[j]);
if (!ncaInitializeContext(cur_nca_ctx, title_info->storage_id, (title_info->storage_id == NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0), content_info, title_info->version.value, &tik))
{
consolePrint("%s #%u initialize nca ctx failed\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset);
goto end;
}
consolePrint("%s #%u initialize nca ctx succeeded\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset);
// don't go any further with this nca if we can't access its fs data because it's pointless
// TODO: add preload warning
if (cur_nca_ctx->rights_id_available && !cur_nca_ctx->titlekey_retrieved)
{
j++;
continue;
}
// set download distribution type
// has no effect if this nca uses NcaDistributionType_Download
if (set_download_type) ncaSetDownloadDistributionType(cur_nca_ctx);
// remove titlekey crypto
// has no effect if this nca doesn't use titlekey crypto
if (remove_titlekey_crypto && !ncaRemoveTitleKeyCrypto(cur_nca_ctx))
{
consolePrint("nca remove titlekey crypto failed\n");
goto end;
}
if (!cur_nca_ctx->fs_ctx[0].has_sparse_layer)
{
switch(content_info->content_type)
{
case NcmContentType_Program:
{
// don't proceed if we didn't allocate programinfo ctx or if we're dealing with a sparse layer
if (!program_count || !program_info_ctx) break;
ProgramInfoContext *cur_program_info_ctx = &(program_info_ctx[program_idx]);
if (!programInfoInitializeContext(cur_program_info_ctx, cur_nca_ctx))
{
consolePrint("initialize program info ctx failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
}
if (!programInfoGenerateAuthoringToolXml(cur_program_info_ctx))
{
consolePrint("program info xml failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
}
program_idx++;
consolePrint("initialize program info ctx succeeded (%s)\n", cur_nca_ctx->content_id_str);
break;
}
case NcmContentType_Control:
{
// don't proceed if we didn't allocate nacp ctx
if (!control_count || !nacp_ctx) break;
NacpContext *cur_nacp_ctx = &(nacp_ctx[control_idx]);
if (!nacpInitializeContext(cur_nacp_ctx, cur_nca_ctx))
{
consolePrint("initialize nacp ctx failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
}
if (!nacpGenerateNcaPatch(cur_nacp_ctx, patch_sua, patch_screenshot, patch_video_capture, patch_hdcp))
{
consolePrint("nacp nca patch failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
}
if (append_authoringtool_data && !nacpGenerateAuthoringToolXml(cur_nacp_ctx, title_info->version.value, cnmtGetRequiredTitleVersion(&cnmt_ctx)))
{
consolePrint("nacp xml failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
}
control_idx++;
consolePrint("initialize nacp ctx succeeded (%s)\n", cur_nca_ctx->content_id_str);
break;
}
case NcmContentType_LegalInformation:
{
// don't proceed if we didn't allocate legalinfo ctx
if (!legal_info_count || !legal_info_ctx) break;
LegalInfoContext *cur_legal_info_ctx = &(legal_info_ctx[legal_info_idx]);
if (!legalInfoInitializeContext(cur_legal_info_ctx, cur_nca_ctx))
{
consolePrint("initialize legal info ctx failed (%s)\n", cur_nca_ctx->content_id_str);
goto end;
}
legal_info_idx++;
consolePrint("initialize legal info ctx succeeded (%s)\n", cur_nca_ctx->content_id_str);
break;
}
default:
break;
}
}
if (!ncaEncryptHeader(cur_nca_ctx))
{
consolePrint("%s #%u encrypt nca header failed\n", titleGetNcmContentTypeName(content_info->content_type), content_info->id_offset);
goto end;
}
j++;
}
// generate cnmt xml right away even though we don't yet have all the data we need
// This is because we need its size to calculate the full nsp size
if (append_authoringtool_data && !cnmtGenerateAuthoringToolXml(&cnmt_ctx, nca_ctx, title_info->content_count))
{
consolePrint("cnmt xml #1 failed\n");
goto end;
}
bool retrieve_tik_cert = (!remove_titlekey_crypto && tik.size > 0);
if (retrieve_tik_cert)
{
if (!(tik_common_block = tikGetCommonBlock(tik.data)))
{
consolePrint("tik common block failed");
goto end;
}
if (remove_console_data && tik_common_block->titlekey_type == TikTitleKeyType_Personalized)
{
if (!tikConvertPersonalizedTicketToCommonTicket(&tik, &raw_cert_chain, &raw_cert_chain_size))
{
consolePrint("tik convert failed\n");
goto end;
}
} else {
raw_cert_chain = (title_info->storage_id == NcmStorageId_GameCard ? certRetrieveRawCertificateChainFromGameCardByRightsId(&(tik_common_block->rights_id), &raw_cert_chain_size) : \
certGenerateRawCertificateChainBySignatureIssuer(tik_common_block->issuer, &raw_cert_chain_size));
if (!raw_cert_chain)
{
consolePrint("cert failed\n");
goto end;
}
}
}
// add nca info
for(u32 i = 0; i < title_info->content_count; i++)
{
NcaContext *cur_nca_ctx = &(nca_ctx[i]);
sprintf(entry_name, "%s.%s", cur_nca_ctx->content_id_str, cur_nca_ctx->content_type == NcmContentType_Meta ? "cnmt.nca" : "nca");
if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cur_nca_ctx->content_size, NULL))
{
consolePrint("pfs add entry failed: %s\n", entry_name);
goto end;
}
}
// add cnmt xml info
if (append_authoringtool_data)
{
sprintf(entry_name, "%s.cnmt.xml", meta_nca_ctx->content_id_str);
if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cnmt_ctx.authoring_tool_xml_size, &(meta_nca_ctx->content_type_ctx_data_idx)))
{
consolePrint("pfs add entry failed: %s\n", entry_name);
goto end;
}
}
// add content type ctx data info
u32 limit = append_authoringtool_data ? (title_info->content_count - 1) : 0;
for(u32 i = 0; i < limit; i++)
{
bool ret = false;
NcaContext *cur_nca_ctx = &(nca_ctx[i]);
if (!cur_nca_ctx->content_type_ctx) continue;
switch(cur_nca_ctx->content_type)
{
case NcmContentType_Program:
{
ProgramInfoContext *cur_program_info_ctx = (ProgramInfoContext*)cur_nca_ctx->content_type_ctx;
sprintf(entry_name, "%s.programinfo.xml", cur_nca_ctx->content_id_str);
ret = pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cur_program_info_ctx->authoring_tool_xml_size, &(cur_nca_ctx->content_type_ctx_data_idx));
break;
}
case NcmContentType_Control:
{
NacpContext *cur_nacp_ctx = (NacpContext*)cur_nca_ctx->content_type_ctx;
for(u8 j = 0; j < cur_nacp_ctx->icon_count; j++)
{
NacpIconContext *icon_ctx = &(cur_nacp_ctx->icon_ctx[j]);
sprintf(entry_name, "%s.nx.%s.jpg", cur_nca_ctx->content_id_str, nacpGetLanguageString(icon_ctx->language));
if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, icon_ctx->icon_size, j == 0 ? &(cur_nca_ctx->content_type_ctx_data_idx) : NULL))
{
consolePrint("pfs add entry failed: %s\n", entry_name);
goto end;
}
}
sprintf(entry_name, "%s.nacp.xml", cur_nca_ctx->content_id_str);
ret = pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cur_nacp_ctx->authoring_tool_xml_size, !cur_nacp_ctx->icon_count ? &(cur_nca_ctx->content_type_ctx_data_idx) : NULL);
break;
}
case NcmContentType_LegalInformation:
{
LegalInfoContext *cur_legal_info_ctx = (LegalInfoContext*)cur_nca_ctx->content_type_ctx;
sprintf(entry_name, "%s.legalinfo.xml", cur_nca_ctx->content_id_str);
ret = pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, cur_legal_info_ctx->authoring_tool_xml_size, &(cur_nca_ctx->content_type_ctx_data_idx));
break;
}
default:
break;
}
if (!ret)
{
consolePrint("pfs add entry failed: %s\n", entry_name);
goto end;
}
}
// add ticket and cert info
if (retrieve_tik_cert)
{
sprintf(entry_name, "%s.tik", tik.rights_id_str);
if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, tik.size, NULL))
{
consolePrint("pfs add entry failed: %s\n", entry_name);
goto end;
}
sprintf(entry_name, "%s.cert", tik.rights_id_str);
if (!pfsAddEntryInformationToFileContext(&pfs_file_ctx, entry_name, raw_cert_chain_size, NULL))
{
consolePrint("pfs add entry failed: %s\n", entry_name);
goto end;
}
}
// write buffer to memory buffer
if (!pfsWriteFileContextHeaderToMemoryBuffer(&pfs_file_ctx, buf, BLOCK_SIZE, &nsp_header_size))
{
consolePrint("pfs write header to mem #1 failed\n");
goto end;
}
nsp_size = (nsp_header_size + pfs_file_ctx.fs_size);
consolePrint("nsp header size: 0x%lX | nsp size: 0x%lX\n", nsp_header_size, nsp_size);
if (output_device != 1)
{
if (nsp_size >= free_space)
{
consolePrint("nsp size exceeds free space\n");
goto end;
}
if (ums_device && ums_device->fs_type < UsbHsFsDeviceFileSystemType_exFAT && nsp_size > FAT32_FILESIZE_LIMIT)
{
consolePrint("split dumps not supported for FAT12/16/32 volumes in UMS devices (yet)\n");
goto end;
}
utilsCreateDirectoryTree(path, false);
if (!ums_device && nsp_size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(path))
{
consolePrint("create concatenation file failed\n");
goto end;
}
if (!(fd = fopen(path, "wb")))
{
consolePrint("fopen failed\n");
goto end;
}
// write placeholder header
memset(buf, 0, nsp_header_size);
fwrite(buf, 1, nsp_header_size, fd);
} else {
if (!usbSendFileProperties(nsp_size, path, (u32)nsp_header_size))
{
consolePrint("usb send file properties (header) failed\n");
goto end;
}
}
consolePrint("dump process started, please wait. hold b to cancel.\n");
nsp_offset += nsp_header_size;
// set nsp size
shared_data->total_size = nsp_size;
// write ncas
for(u32 i = 0; i < title_info->content_count; i++)
{
NcaContext *cur_nca_ctx = &(nca_ctx[i]);
u64 blksize = BLOCK_SIZE;
memset(&sha256_ctx, 0, sizeof(Sha256Context));
sha256ContextCreate(&sha256_ctx);
if (cur_nca_ctx->content_type == NcmContentType_Meta && (!cnmtGenerateNcaPatch(&cnmt_ctx) || !ncaEncryptHeader(cur_nca_ctx)))
{
consolePrint("cnmt generate patch failed\n");
goto end;
}
bool dirty_header = ncaIsHeaderDirty(cur_nca_ctx);
if (output_device == 1)
{
tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, i);
if (!usbSendFilePropertiesCommon(cur_nca_ctx->content_size, tmp_name))
{
consolePrint("usb send file properties \"%s\" failed\n", tmp_name);
goto end;
}
}
for(u64 offset = 0; offset < cur_nca_ctx->content_size; offset += blksize, nsp_offset += blksize, shared_data->data_written += blksize)
{
if (shared_data->transfer_cancelled)
{
if (output_device == 1) usbCancelFileTransfer();
goto end;
}
if ((cur_nca_ctx->content_size - offset) < blksize) blksize = (cur_nca_ctx->content_size - offset);
// read nca chunk
if (!ncaReadContentFile(cur_nca_ctx, buf, blksize, offset))
{
consolePrint("nca read failed at 0x%lX for \"%s\"\n", offset, cur_nca_ctx->content_id_str);
goto end;
}
if (dirty_header)
{
// write re-encrypted headers
if (!cur_nca_ctx->header_written) ncaWriteEncryptedHeaderDataToMemoryBuffer(cur_nca_ctx, buf, blksize, offset);
if (cur_nca_ctx->content_type_ctx_patch)
{
// write content type context patch
switch(cur_nca_ctx->content_type)
{
case NcmContentType_Meta:
cnmtWriteNcaPatch(&cnmt_ctx, buf, blksize, offset);
break;
case NcmContentType_Control:
nacpWriteNcaPatch((NacpContext*)cur_nca_ctx->content_type_ctx, buf, blksize, offset);
break;
default:
break;
}
}
// update flag to avoid entering this code block if it's not needed anymore
dirty_header = (!cur_nca_ctx->header_written || cur_nca_ctx->content_type_ctx_patch);
}
// update hash calculation
sha256ContextUpdate(&sha256_ctx, buf, blksize);
// write nca chunk
if (output_device != 1)
{
fwrite(buf, 1, blksize, fd);
} else {
if (!usbSendFileData(buf, blksize))
{
consolePrint("send file data failed\n");
goto end;
}
}
}
// get hash
sha256ContextGetHash(&sha256_ctx, sha256_hash);
// update content id and hash
ncaUpdateContentIdAndHash(cur_nca_ctx, sha256_hash);
// update cnmt
if (!cnmtUpdateContentInfo(&cnmt_ctx, cur_nca_ctx))
{
consolePrint("cnmt update content info failed\n");
goto end;
}
// update pfs entry name
if (!pfsUpdateEntryNameFromFileContext(&pfs_file_ctx, i, cur_nca_ctx->content_id_str))
{
consolePrint("pfs update entry name failed for nca \"%s\"\n", cur_nca_ctx->content_id_str);
goto end;
}
}
if (append_authoringtool_data)
{
// regenerate cnmt xml
if (!cnmtGenerateAuthoringToolXml(&cnmt_ctx, nca_ctx, title_info->content_count))
{
consolePrint("cnmt xml #2 failed\n");
goto end;
}
// write cnmt xml
if (output_device != 1)
{
fwrite(cnmt_ctx.authoring_tool_xml, 1, cnmt_ctx.authoring_tool_xml_size, fd);
} else {
tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, meta_nca_ctx->content_type_ctx_data_idx);
if (!usbSendFilePropertiesCommon(cnmt_ctx.authoring_tool_xml_size, tmp_name) || !usbSendFileData(cnmt_ctx.authoring_tool_xml, cnmt_ctx.authoring_tool_xml_size))
{
consolePrint("send \"%s\" failed\n", tmp_name);
goto end;
}
}
nsp_offset += cnmt_ctx.authoring_tool_xml_size;
shared_data->data_written += cnmt_ctx.authoring_tool_xml_size;
// update cnmt xml pfs entry name
if (!pfsUpdateEntryNameFromFileContext(&pfs_file_ctx, meta_nca_ctx->content_type_ctx_data_idx, meta_nca_ctx->content_id_str))
{
consolePrint("pfs update entry name cnmt xml failed\n");
goto end;
}
}
// write content type ctx data
for(u32 i = 0; i < limit; i++)
{
NcaContext *cur_nca_ctx = &(nca_ctx[i]);
if (!cur_nca_ctx->content_type_ctx) continue;
char *authoring_tool_xml = NULL;
u64 authoring_tool_xml_size = 0;
u32 data_idx = cur_nca_ctx->content_type_ctx_data_idx;
switch(cur_nca_ctx->content_type)
{
case NcmContentType_Program:
{
ProgramInfoContext *cur_program_info_ctx = (ProgramInfoContext*)cur_nca_ctx->content_type_ctx;
authoring_tool_xml = cur_program_info_ctx->authoring_tool_xml;
authoring_tool_xml_size = cur_program_info_ctx->authoring_tool_xml_size;
break;
}
case NcmContentType_Control:
{
NacpContext *cur_nacp_ctx = (NacpContext*)cur_nca_ctx->content_type_ctx;
authoring_tool_xml = cur_nacp_ctx->authoring_tool_xml;
authoring_tool_xml_size = cur_nacp_ctx->authoring_tool_xml_size;
// loop through available icons
for(u8 j = 0; j < cur_nacp_ctx->icon_count; j++)
{
NacpIconContext *icon_ctx = &(cur_nacp_ctx->icon_ctx[j]);
// write icon
if (output_device != 1)
{
fwrite(icon_ctx->icon_data, 1, icon_ctx->icon_size, fd);
} else {
tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, data_idx);
if (!usbSendFilePropertiesCommon(icon_ctx->icon_size, tmp_name) || !usbSendFileData(icon_ctx->icon_data, icon_ctx->icon_size))
{
consolePrint("send \"%s\" failed\n", tmp_name);
goto end;
}
}
nsp_offset += icon_ctx->icon_size;
shared_data->data_written += icon_ctx->icon_size;
// update pfs entry name
if (!pfsUpdateEntryNameFromFileContext(&pfs_file_ctx, data_idx++, cur_nca_ctx->content_id_str))
{
consolePrint("pfs update entry name failed for icon \"%s\" (%u)\n", cur_nca_ctx->content_id_str, icon_ctx->language);
goto end;
}
}
break;
}
case NcmContentType_LegalInformation:
{
LegalInfoContext *cur_legal_info_ctx = (LegalInfoContext*)cur_nca_ctx->content_type_ctx;
authoring_tool_xml = cur_legal_info_ctx->authoring_tool_xml;
authoring_tool_xml_size = cur_legal_info_ctx->authoring_tool_xml_size;
break;
}
default:
break;
}
// write xml
if (output_device != 1)
{
fwrite(authoring_tool_xml, 1, authoring_tool_xml_size, fd);
} else {
tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, data_idx);
if (!usbSendFilePropertiesCommon(authoring_tool_xml_size, tmp_name) || !usbSendFileData(authoring_tool_xml, authoring_tool_xml_size))
{
consolePrint("send \"%s\" failed\n", tmp_name);
goto end;
}
}
nsp_offset += authoring_tool_xml_size;
shared_data->data_written += authoring_tool_xml_size;
// update pfs entry name
if (!pfsUpdateEntryNameFromFileContext(&pfs_file_ctx, data_idx, cur_nca_ctx->content_id_str))
{
consolePrint("pfs update entry name failed for xml \"%s\"\n", cur_nca_ctx->content_id_str);
goto end;
}
}
if (retrieve_tik_cert)
{
// write ticket
if (output_device != 1)
{
fwrite(tik.data, 1, tik.size, fd);
} else {
tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, pfs_file_ctx.header.entry_count - 2);
if (!usbSendFilePropertiesCommon(tik.size, tmp_name) || !usbSendFileData(tik.data, tik.size))
{
consolePrint("send \"%s\" failed\n", tmp_name);
goto end;
}
}
nsp_offset += tik.size;
shared_data->data_written += tik.size;
// write cert
if (output_device != 1)
{
fwrite(raw_cert_chain, 1, raw_cert_chain_size, fd);
} else {
tmp_name = pfsGetEntryNameByIndexFromFileContext(&pfs_file_ctx, pfs_file_ctx.header.entry_count - 1);
if (!usbSendFilePropertiesCommon(raw_cert_chain_size, tmp_name) || !usbSendFileData(raw_cert_chain, raw_cert_chain_size))
{
consolePrint("send \"%s\" failed\n", tmp_name);
goto end;
}
}
nsp_offset += raw_cert_chain_size;
shared_data->data_written += raw_cert_chain_size;
}
// write new pfs0 header
if (!pfsWriteFileContextHeaderToMemoryBuffer(&pfs_file_ctx, buf, BLOCK_SIZE, &nsp_header_size))
{
consolePrint("pfs write header to mem #2 failed\n");
goto end;
}
if (output_device != 1)
{
rewind(fd);
fwrite(buf, 1, nsp_header_size, fd);
} else {
if (!usbSendNspHeader(buf, (u32)nsp_header_size))
{
consolePrint("send nsp header failed\n");
goto end;
}
}
shared_data->data_written += nsp_header_size;
success = true;
end:
consoleRefresh();
if (!success && !shared_data->transfer_cancelled) shared_data->error = true;
if (fd)
{
fclose(fd);
if (!ums_device && !success) utilsRemoveConcatenationFile(path);
utilsCommitSdCardFileSystemChanges();
}
pfsFreeFileContext(&pfs_file_ctx);
if (raw_cert_chain) free(raw_cert_chain);
if (legal_info_ctx)
{
for(u32 i = 0; i < legal_info_count; i++) legalInfoFreeContext(&(legal_info_ctx[i]));
free(legal_info_ctx);
}
if (nacp_ctx)
{
for(u32 i = 0; i < control_count; i++) nacpFreeContext(&(nacp_ctx[i]));
free(nacp_ctx);
}
if (program_info_ctx)
{
for(u32 i = 0; i < program_count; i++) programInfoFreeContext(&(program_info_ctx[i]));
free(program_info_ctx);
}
cnmtFreeContext(&cnmt_ctx);
if (nca_ctx) free(nca_ctx);
if (path) free(path);
if (dump_name) free(dump_name);
if (buf) free(buf);
threadExit();
}
static void nspDump(TitleInfo *title_info)
{
if (!title_info) return;
TitleApplicationMetadata *app_metadata = title_info->app_metadata;
ThreadSharedData shared_data = {0};
Thread dump_thread = {0};
time_t start = 0, btn_cancel_start_tmr = 0, btn_cancel_end_tmr = 0;
bool btn_cancel_cur_state = false, btn_cancel_prev_state = false;
u8 usb_host_speed = UsbHostSpeed_None;
u64 prev_size = 0;
u8 prev_time = 0, percent = 0;
u32 output_device = options[8].val;
consoleClear();
consolePrint("%s info:\n\n", title_info->meta_key.type == NcmContentMetaType_Application ? "base application" : \
(title_info->meta_key.type == NcmContentMetaType_Patch ? "update" : "dlc"));
if (app_metadata)
{
consolePrint("name: %s\n", app_metadata->lang_entry.name);
consolePrint("publisher: %s\n", app_metadata->lang_entry.author);
}
consolePrint("source storage: %s\n", titleGetNcmStorageIdName(title_info->storage_id));
consolePrint("title id: %016lX\n", title_info->meta_key.id);
consolePrint("version: %u (%u.%u.%u-%u.%u)\n", title_info->version.value, title_info->version.system_version.major, title_info->version.system_version.minor, title_info->version.system_version.micro, title_info->version.system_version.major_relstep, \
title_info->version.system_version.minor_relstep);
consolePrint("content count: %u\n", title_info->content_count);
consolePrint("size: %s\n", title_info->size_str);
consolePrint("______________________________\n\n");
consolePrint("dump options:\n\n");
for(u32 i = 0; i < (options_count - 1); i++) consolePrint("%s: %s\n", options[i].str, options[i].val ? "yes" : "no");
consolePrint("%s: ", options[options_count - 1].str);
switch(options[options_count - 1].val)
{
case 0:
consolePrint("%s\n", DEVOPTAB_SDMC_DEVICE);
break;
case 1:
consolePrint("usb host (pc)\n");
break;
default:
consolePrint("ums device #%u\n", options[options_count - 1].val - 1);
break;
}
consolePrint("______________________________\n\n");
if (output_device == 1)
{
// make sure we have a valid usb session
consolePrint("waiting for usb connection... ");
start = time(NULL);
while(true)
{
time_t now = time(NULL);
if ((now - start) >= 10) break;
consolePrint("%lu ", now - start);
consoleRefresh();
if ((usb_host_speed = usbIsReady())) break;
utilsSleep(1);
}
consolePrint("\n");
if (!usb_host_speed)
{
consolePrint("usb connection failed\n");
return;
}
}
// create dump thread
shared_data.data = title_info;
utilsCreateThread(&dump_thread, dump_thread_func, &shared_data, 2);
while(!shared_data.total_size && !shared_data.error) svcSleepThread(10000000); // 10 ms
if (shared_data.error)
{
utilsJoinThread(&dump_thread);
return;
}
// start dump
start = time(NULL);
while(shared_data.data_written < shared_data.total_size)
{
if (shared_data.error) break;
struct tm ts = {0};
time_t now = time(NULL);
localtime_r(&now, &ts);
size_t size = shared_data.data_written;
utilsScanPads();
btn_cancel_cur_state = (utilsGetButtonsHeld() & HidNpadButton_B);
if (btn_cancel_cur_state && btn_cancel_cur_state != btn_cancel_prev_state)
{
btn_cancel_start_tmr = now;
} else
if (btn_cancel_cur_state && btn_cancel_cur_state == btn_cancel_prev_state)
{
btn_cancel_end_tmr = now;
if ((btn_cancel_end_tmr - btn_cancel_start_tmr) >= 3)
{
shared_data.transfer_cancelled = true;
break;
}
} else {
btn_cancel_start_tmr = btn_cancel_end_tmr = 0;
}
btn_cancel_prev_state = btn_cancel_cur_state;
if (prev_time == ts.tm_sec || prev_size == size) continue;
percent = (u8)((size * 100) / shared_data.total_size);
prev_time = ts.tm_sec;
prev_size = size;
consolePrint("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, shared_data.total_size, percent, (now - start));
consoleRefresh();
}
start = (time(NULL) - start);
consolePrint("\nwaiting for thread to join\n");
utilsJoinThread(&dump_thread);
consolePrint("dump_thread done: %lu\n", time(NULL));
if (shared_data.error)
{
consolePrint("i/o error\n");
return;
}
if (shared_data.transfer_cancelled)
{
consolePrint("process cancelled\n");
return;
}
consolePrint("process completed in %lu seconds\n", start);
}
int main(int argc, char *argv[])
{
int ret = 0;
if (!utilsInitializeResources(argc, (const char**)argv))
{
ret = -1;
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;
TitleUserApplicationData user_app_data = {0};
TitleInfo *title_info = NULL;
u32 menu = 0, selected_idx = 0, scroll = 0, page_size = 30;
u32 title_idx = 0, title_scroll = 0;
u32 type_idx = 0, type_scroll = 0;
u32 list_count = 0, list_idx = 0;
u64 device_total_fs_size = 0, device_free_fs_size = 0;
char device_total_fs_size_str[36] = {0}, device_free_fs_size_str[32] = {0}, device_info[0x300] = {0};
bool device_retrieved_size = false, device_retrieved_info = false;
bool applet_status = true;
app_metadata = titleGetApplicationMetadataEntries(false, &app_count);
if (!app_metadata || !app_count)
{
consolePrint("app metadata failed\n");
goto out2;
}
consolePrint("app metadata succeeded\n");
consoleRefresh();
ums_devices = umsGetDevices(&ums_device_count);
utilsSleep(1);
while((applet_status = appletMainLoop()))
{
consoleClear();
consolePrint("press b to %s.\n", menu == 0 ? "exit" : "go back");
if (ums_device_count) consolePrint("press x to safely remove all ums devices.\n");
consolePrint("______________________________\n\n");
if (menu == 0)
{
consolePrint("title: %u / %u\n", selected_idx + 1, app_count);
consolePrint("selected title: %016lX - %s\n", app_metadata[selected_idx]->title_id, app_metadata[selected_idx]->lang_entry.name);
} else {
consolePrint("title info:\n\n");
consolePrint("name: %s\n", app_metadata[title_idx]->lang_entry.name);
consolePrint("publisher: %s\n", app_metadata[title_idx]->lang_entry.author);
consolePrint("title id: %016lX\n", app_metadata[title_idx]->title_id);
if (menu == 2)
{
consolePrint("______________________________\n\n");
if (title_info->previous || title_info->next)
{
consolePrint("press zl/l and/or zr/r to change the selected title\n");
consolePrint("title: %u / %u\n", list_idx, list_count);
consolePrint("______________________________\n\n");
}
consolePrint("selected %s info:\n\n", title_info->meta_key.type == NcmContentMetaType_Application ? "base application" : \
(title_info->meta_key.type == NcmContentMetaType_Patch ? "update" : "dlc"));
consolePrint("source storage: %s\n", titleGetNcmStorageIdName(title_info->storage_id));
if (title_info->meta_key.type != NcmContentMetaType_Application) consolePrint("title id: %016lX\n", title_info->meta_key.id);
consolePrint("version: %u (%u.%u.%u-%u.%u)\n", title_info->version.value, title_info->version.system_version.major, title_info->version.system_version.minor, \
title_info->version.system_version.micro, title_info->version.system_version.major_relstep, title_info->version.system_version.minor_relstep);
consolePrint("content count: %u\n", title_info->content_count);
consolePrint("size: %s\n", title_info->size_str);
}
}
consolePrint("______________________________\n\n");
u32 max_val = (menu == 0 ? app_count : (menu == 1 ? dump_type_strings_count : (1 + options_count)));
for(u32 i = scroll; i < max_val; i++)
{
if (i >= (scroll + page_size)) break;
consolePrint("%s", i == selected_idx ? " -> " : " ");
if (menu == 0)
{
consolePrint("%016lX - %s\n", app_metadata[i]->title_id, app_metadata[i]->lang_entry.name);
} else
if (menu == 1)
{
consolePrint("%s\n", dump_type_strings[i]);
} else
if (menu == 2)
{
if (i == 0)
{
consolePrint("start nsp dump\n");
} else
if (i < options_count)
{
consolePrint("%s: < %s >\n", options[i - 1].str, options[i - 1].val ? "yes" : "no");
} else {
consolePrint("%s: ", options[i - 1].str);
u32 output_device = options[i - 1].val;
if (output_device != 1)
{
if (!device_retrieved_size)
{
sprintf(device_total_fs_size_str, "%s/", output_device == 0 ? DEVOPTAB_SDMC_DEVICE : ums_devices[output_device - 2].name);
utilsGetFileSystemStatsByPath(device_total_fs_size_str, &device_total_fs_size, &device_free_fs_size);
utilsGenerateFormattedSizeString(device_total_fs_size, device_total_fs_size_str, sizeof(device_total_fs_size_str));
utilsGenerateFormattedSizeString(device_free_fs_size, device_free_fs_size_str, sizeof(device_free_fs_size_str));
device_retrieved_size = true;
}
if (output_device == 0)
{
consolePrint("< sdmc: (%s / %s) >\n", device_free_fs_size_str, device_total_fs_size_str);
} else {
UsbHsFsDevice *ums_device = &(ums_devices[output_device - 2]);
if (!device_retrieved_info)
{
if (ums_device->product_name[0])
{
sprintf(device_info, "%s, LUN %u, FS #%u, %s", ums_device->product_name, ums_device->lun, ums_device->fs_idx, LIBUSBHSFS_FS_TYPE_STR(ums_device->fs_type));
} else {
sprintf(device_info, "LUN %u, FS #%u, %s", ums_device->lun, ums_device->fs_idx, LIBUSBHSFS_FS_TYPE_STR(ums_device->fs_type));
}
device_retrieved_info = true;
}
consolePrint("< %s (%s) (%s / %s) >", ums_device->name, device_info, device_free_fs_size_str, device_total_fs_size_str);
}
} else {
consolePrint("< usb host (pc) >");
device_retrieved_size = device_retrieved_info = false;
}
}
}
}
consolePrint("\n");
consoleRefresh();
bool data_update = false;
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 (titleIsGameCardInfoUpdated())
{
free(app_metadata);
app_metadata = titleGetApplicationMetadataEntries(false, &app_count);
if (!app_metadata)
{
consolePrint("\napp metadata failed\n");
goto out2;
}
menu = selected_idx = scroll = 0;
title_idx = title_scroll = 0;
type_idx = type_scroll = 0;
list_count = list_idx = 0;
data_update = true;
break;
}
if (umsIsDeviceInfoUpdated())
{
free(ums_devices);
ums_devices = umsGetDevices(&ums_device_count);
options[options_count - 1].val = 0;
device_retrieved_size = device_retrieved_info = false;
data_update = true;
break;
}
}
if (!applet_status) break;
if (data_update) continue;
if (btn_down & HidNpadButton_A)
{
bool error = false;
if (menu == 0)
{
title_idx = selected_idx;
title_scroll = scroll;
} else
if (menu == 1)
{
type_idx = selected_idx;
type_scroll = scroll;
}
menu++;
if (menu == 3 && selected_idx != 0)
{
menu--;
continue;
}
if (menu == 1)
{
if (!titleGetUserApplicationData(app_metadata[title_idx]->title_id, &user_app_data))
{
consolePrint("\nget user application data failed!\n");
error = true;
}
} else
if (menu == 2)
{
if ((type_idx == 0 && !user_app_data.app_info) || (type_idx == 1 && !user_app_data.patch_info) || (type_idx == 2 && !user_app_data.aoc_info))
{
consolePrint("\nthe selected title doesn't have available %s data\n", type_idx == 0 ? "base application" : (type_idx == 1 ? "update" : "dlc"));
error = true;
} else {
title_info = (type_idx == 0 ? user_app_data.app_info : (type_idx == 1 ? user_app_data.patch_info : user_app_data.aoc_info));
list_count = titleGetCountFromInfoBlock(title_info);
list_idx = 1;
}
} else
if (menu == 3)
{
utilsSetLongRunningProcessState(true);
nspDump(title_info);
utilsSetLongRunningProcessState(false);
}
if (error || menu >= 3)
{
consolePrint("press any button to continue\n");
consoleRefresh();
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 : type_idx);
scroll = (menu == 0 ? title_scroll : type_scroll);
if (menu == 0) titleFreeUserApplicationData(&user_app_data);
}
} else
if ((btn_down & (HidNpadButton_Left | HidNpadButton_Right)) && menu == 2 && selected_idx != 0)
{
if (selected_idx < options_count)
{
options[selected_idx - 1].val ^= 1;
} else {
bool left = (btn_down & HidNpadButton_Left);
u32 *output_device = &(options[selected_idx - 1].val), orig_output_device = *output_device;
if (left)
{
(*output_device)--;
if (*output_device == UINT32_MAX) *output_device = (ums_device_count + 1);
} else {
(*output_device)++;
if (*output_device > (ums_device_count + 1)) *output_device = 0;
}
if (*output_device != orig_output_device) device_retrieved_size = device_retrieved_info = false;
}
} else
if ((btn_down & (HidNpadButton_L | HidNpadButton_ZL)) && menu == 2 && title_info->previous)
{
title_info = title_info->previous;
list_idx--;
} else
if ((btn_down & (HidNpadButton_R | HidNpadButton_ZR)) && menu == 2 && title_info->next)
{
title_info = title_info->next;
list_idx++;
} else
if ((btn_down & HidNpadButton_X) && ums_device_count)
{
for(u32 i = 0; i < ums_device_count; i++) usbHsFsUnmountDevice(&(ums_devices[i]), false);
options[options_count - 1].val = 0;
free(ums_devices);
ums_devices = NULL;
ums_device_count = 0;
}
if (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp)) svcSleepThread(50000000); // 50 ms
}
if (!applet_status) menu = UINT32_MAX;
out2:
consoleRefresh();
if (menu != UINT32_MAX)
{
consolePrint("press any button to exit\n");
utilsWaitForButtonPress(0);
}
if (ums_devices) free(ums_devices);
if (app_metadata) free(app_metadata);
out:
utilsCloseResources();
consoleExit(NULL);
return ret;
}