1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-22 18:26:39 +00:00

system_update: add menu option.

Makes it possible to dump a system update. The SystemVersion file is retrieved to get additional information about the system update.
This commit is contained in:
Pablo Curiel 2024-10-27 12:23:46 +01:00
parent d2baf7ee7f
commit 160236c4de
7 changed files with 447 additions and 28 deletions

View file

@ -36,7 +36,7 @@ Currently planned changes for this branch include:
* Improved support for multigame gamecards and titles with more than one Program NCA (e.g. SM3DAS). :white_check_mark: * Improved support for multigame gamecards and titles with more than one Program NCA (e.g. SM3DAS). :white_check_mark:
* Control.nacp patching while dumping NSPs (lets you patch screenshot, video, user account and HDCP restrictions). :white_check_mark: * Control.nacp patching while dumping NSPs (lets you patch screenshot, video, user account and HDCP restrictions). :white_check_mark:
* Partition FS / Hash FS / RomFS browser using custom devoptab wrappers. :white_check_mark: * Partition FS / Hash FS / RomFS browser using custom devoptab wrappers. :white_check_mark:
* Full system update dumps. :x: * Full system update dumps with checksum and signature verification. :white_check_mark:
* Batch NSP dumps. :x: * Batch NSP dumps. :x:
* `FsFileSystem` + `FatFs` based eMMC browser using a custom devoptab wrapper (allows copying files protected by the FS sysmodule at runtime). :x: * `FsFileSystem` + `FatFs` based eMMC browser using a custom devoptab wrapper (allows copying files protected by the FS sysmodule at runtime). :x:
* New UI using a [customized borealis fork](https://github.com/DarkMatterCore/borealis/tree/nxdumptool-legacy). :warning: * New UI using a [customized borealis fork](https://github.com/DarkMatterCore/borealis/tree/nxdumptool-legacy). :warning:

View file

@ -29,6 +29,7 @@
#include <core/cert.h> #include <core/cert.h>
#include <core/usb.h> #include <core/usb.h>
#include <core/devoptab/nxdt_devoptab.h> #include <core/devoptab/nxdt_devoptab.h>
#include <core/system_update.h>
#define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE #define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE
#define WAIT_TIME_LIMIT 30 #define WAIT_TIME_LIMIT 30
@ -74,8 +75,8 @@ typedef enum {
MenuId_BrowseHFS = 4, MenuId_BrowseHFS = 4,
MenuId_UserTitles = 5, MenuId_UserTitles = 5,
MenuId_UserTitlesSubMenu = 6, MenuId_UserTitlesSubMenu = 6,
MenuId_NSPTitleTypes = 7, MenuId_NspTitleTypes = 7,
MenuId_NSP = 8, MenuId_Nsp = 8,
MenuId_TicketTitleTypes = 9, MenuId_TicketTitleTypes = 9,
MenuId_Ticket = 10, MenuId_Ticket = 10,
MenuId_NcaTitleTypes = 11, MenuId_NcaTitleTypes = 11,
@ -83,7 +84,8 @@ typedef enum {
MenuId_NcaFsSections = 13, MenuId_NcaFsSections = 13,
MenuId_NcaFsSectionsSubMenu = 14, MenuId_NcaFsSectionsSubMenu = 14,
MenuId_SystemTitles = 15, MenuId_SystemTitles = 15,
MenuId_Count = 16 MenuId_SystemUpdate = 16,
MenuId_Count = 17
} MenuId; } MenuId;
typedef struct typedef struct
@ -158,6 +160,11 @@ typedef struct {
const char *base_out_path; const char *base_out_path;
} FsBrowserHighlightedEntriesThreadData; } FsBrowserHighlightedEntriesThreadData;
typedef struct {
SharedThreadData shared_thread_data;
SystemUpdateDumpContext *sys_upd_dump_ctx;
} SystemUpdateThreadData;
/* Function prototypes. */ /* Function prototypes. */
static void utilsScanPads(void); static void utilsScanPads(void);
@ -224,6 +231,8 @@ static bool saveNintendoContentArchive(void *userdata);
static bool saveNintendoContentArchiveFsSection(void *userdata); static bool saveNintendoContentArchiveFsSection(void *userdata);
static bool browseNintendoContentArchiveFsSection(void *userdata); static bool browseNintendoContentArchiveFsSection(void *userdata);
static bool saveSystemUpdateDump(void *userdata);
static bool fsBrowser(const char *mount_name, const char *base_out_path); static bool fsBrowser(const char *mount_name, const char *base_out_path);
static bool fsBrowserGetDirEntries(const char *dir_path, FsBrowserEntry **out_entries, u32 *out_entry_count); static bool fsBrowserGetDirEntries(const char *dir_path, FsBrowserEntry **out_entries, u32 *out_entry_count);
static bool fsBrowserDumpFile(const char *dir_path, const FsBrowserEntry *entry, const char *base_out_path); static bool fsBrowserDumpFile(const char *dir_path, const FsBrowserEntry *entry, const char *base_out_path);
@ -254,6 +263,8 @@ static void fsBrowserFileReadThreadFunc(void *arg);
static void fsBrowserHighlightedEntriesReadThreadFunc(void *arg); static void fsBrowserHighlightedEntriesReadThreadFunc(void *arg);
static bool fsBrowserHighlightedEntriesReadThreadLoop(SharedThreadData *shared_thread_data, const char *dir_path, const FsBrowserEntry *entries, u32 entries_count, const char *base_out_path, void *buf1, void *buf2); static bool fsBrowserHighlightedEntriesReadThreadLoop(SharedThreadData *shared_thread_data, const char *dir_path, const FsBrowserEntry *entries, u32 entries_count, const char *base_out_path, void *buf1, void *buf2);
static void systemUpdateReadThreadFunc(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);
@ -723,7 +734,7 @@ static MenuElement *g_nspMenuElements[] = {
}; };
static Menu g_nspMenu = { static Menu g_nspMenu = {
.id = MenuId_NSP, .id = MenuId_Nsp,
.parent = NULL, .parent = NULL,
.selected = 0, .selected = 0,
.scroll = 0, .scroll = 0,
@ -900,7 +911,7 @@ static MenuElement *g_userTitlesSubMenuElements[] = {
&(MenuElement){ &(MenuElement){
.str = "nsp dump options", .str = "nsp dump options",
.child_menu = &(Menu){ .child_menu = &(Menu){
.id = MenuId_NSPTitleTypes, .id = MenuId_NspTitleTypes,
.parent = NULL, .parent = NULL,
.selected = 0, .selected = 0,
.scroll = 0, .scroll = 0,
@ -966,6 +977,26 @@ static Menu g_systemTitlesMenu = {
.elements = NULL .elements = NULL
}; };
static MenuElement *g_dumpSystemUpdateMenuElements[] = {
&(MenuElement){
.str = "start dump",
.child_menu = NULL,
.task_func = &saveSystemUpdateDump,
.element_options = NULL,
.userdata = NULL
},
&g_storageMenuElement,
NULL
};
static Menu g_dumpSystemUpdateMenu = {
.id = MenuId_SystemUpdate,
.parent = NULL,
.selected = 0,
.scroll = 0,
.elements = g_dumpSystemUpdateMenuElements
};
static MenuElement *g_rootMenuElements[] = { static MenuElement *g_rootMenuElements[] = {
&(MenuElement){ &(MenuElement){
.str = "gamecard menu", .str = "gamecard menu",
@ -994,6 +1025,13 @@ static MenuElement *g_rootMenuElements[] = {
.element_options = NULL, .element_options = NULL,
.userdata = NULL .userdata = NULL
}, },
&(MenuElement){
.str = "dump system update",
.child_menu = &g_dumpSystemUpdateMenu,
.task_func = NULL,
.element_options = NULL,
.userdata = NULL
},
&(MenuElement){ &(MenuElement){
.str = "reset settings", .str = "reset settings",
.child_menu = NULL, .child_menu = NULL,
@ -1066,7 +1104,7 @@ int main(int argc, char *argv[])
u32 child_id = selected_element->child_menu->id; u32 child_id = selected_element->child_menu->id;
g_titleTypesMenuElements[0]->child_menu = g_titleTypesMenuElements[1]->child_menu = \ g_titleTypesMenuElements[0]->child_menu = g_titleTypesMenuElements[1]->child_menu = \
g_titleTypesMenuElements[2]->child_menu = g_titleTypesMenuElements[3]->child_menu = (child_id == MenuId_NSPTitleTypes ? &g_nspMenu : \ g_titleTypesMenuElements[2]->child_menu = g_titleTypesMenuElements[3]->child_menu = (child_id == MenuId_NspTitleTypes ? &g_nspMenu : \
(child_id == MenuId_TicketTitleTypes ? &g_ticketMenu : \ (child_id == MenuId_TicketTitleTypes ? &g_ticketMenu : \
(child_id == MenuId_NcaTitleTypes ? &g_ncaMenu : NULL))); (child_id == MenuId_NcaTitleTypes ? &g_ncaMenu : NULL)));
} }
@ -1097,12 +1135,12 @@ int main(int argc, char *argv[])
consolePrint("title info:\n\n"); consolePrint("title info:\n\n");
consolePrint("name: %s\n", app_metadata->lang_entry.name); consolePrint("name: %s\n", app_metadata->lang_entry.name);
consolePrint("publisher: %s\n", app_metadata->lang_entry.author); consolePrint("publisher: %s\n", app_metadata->lang_entry.author);
if (cur_menu->id == MenuId_UserTitlesSubMenu || cur_menu->id == MenuId_NSPTitleTypes || cur_menu->id == MenuId_TicketTitleTypes || \ if (cur_menu->id == MenuId_UserTitlesSubMenu || cur_menu->id == MenuId_NspTitleTypes || cur_menu->id == MenuId_TicketTitleTypes || \
cur_menu->id == MenuId_NcaTitleTypes) consolePrint("title id: %016lX\n", app_metadata->title_id); cur_menu->id == MenuId_NcaTitleTypes) consolePrint("title id: %016lX\n", app_metadata->title_id);
consolePrint("______________________________\n\n"); consolePrint("______________________________\n\n");
} }
if (cur_menu->id == MenuId_NSP || cur_menu->id == MenuId_Ticket || cur_menu->id == MenuId_Nca || \ if (cur_menu->id == MenuId_Nsp || cur_menu->id == MenuId_Ticket || cur_menu->id == MenuId_Nca || \
cur_menu->id == MenuId_NcaFsSections || cur_menu->id == MenuId_NcaFsSectionsSubMenu) cur_menu->id == MenuId_NcaFsSections || cur_menu->id == MenuId_NcaFsSectionsSubMenu)
{ {
if (cur_menu->id != MenuId_NcaFsSections && cur_menu->id != MenuId_NcaFsSectionsSubMenu && (title_info->previous || title_info->next)) if (cur_menu->id != MenuId_NcaFsSections && cur_menu->id != MenuId_NcaFsSectionsSubMenu && (title_info->previous || title_info->next))
@ -1123,7 +1161,7 @@ int main(int argc, char *argv[])
consolePrint("size: %s\n", title_info->size_str); consolePrint("size: %s\n", title_info->size_str);
consolePrint("______________________________\n\n"); consolePrint("______________________________\n\n");
if (cur_menu->id == MenuId_NSP) g_nspMenuElements[0]->userdata = title_info; if (cur_menu->id == MenuId_Nsp) g_nspMenuElements[0]->userdata = title_info;
if (cur_menu->id == MenuId_Ticket) g_ticketMenuElements[0]->userdata = title_info; if (cur_menu->id == MenuId_Ticket) g_ticketMenuElements[0]->userdata = title_info;
@ -1245,7 +1283,7 @@ int main(int argc, char *argv[])
error = !titleGetUserApplicationData(app_metadata->title_id, &user_app_data); error = !titleGetUserApplicationData(app_metadata->title_id, &user_app_data);
if (error) consolePrint("\nfailed to get user application data for %016lX!\n", app_metadata->title_id); if (error) consolePrint("\nfailed to get user application data for %016lX!\n", app_metadata->title_id);
} else } else
if (child_menu->id == MenuId_NSP || child_menu->id == MenuId_Ticket || child_menu->id == MenuId_Nca) if (child_menu->id == MenuId_Nsp || child_menu->id == MenuId_Ticket || child_menu->id == MenuId_Nca)
{ {
u32 title_type = (cur_menu->id != MenuId_SystemTitles ? *((u32*)selected_element->userdata) : NcmContentMetaType_Unknown); u32 title_type = (cur_menu->id != MenuId_SystemTitles ? *((u32*)selected_element->userdata) : NcmContentMetaType_Unknown);
@ -1477,12 +1515,12 @@ int main(int argc, char *argv[])
g_titleTypesMenuElements[0]->child_menu = g_titleTypesMenuElements[1]->child_menu = \ g_titleTypesMenuElements[0]->child_menu = g_titleTypesMenuElements[1]->child_menu = \
g_titleTypesMenuElements[2]->child_menu = g_titleTypesMenuElements[3]->child_menu = NULL; g_titleTypesMenuElements[2]->child_menu = g_titleTypesMenuElements[3]->child_menu = NULL;
} else } else
if (cur_menu->id == MenuId_NSPTitleTypes || cur_menu->id == MenuId_TicketTitleTypes || cur_menu->id == MenuId_NcaTitleTypes) if (cur_menu->id == MenuId_NspTitleTypes || cur_menu->id == MenuId_TicketTitleTypes || cur_menu->id == MenuId_NcaTitleTypes)
{ {
title_info = NULL; title_info = NULL;
title_info_idx = title_info_count = 0; title_info_idx = title_info_count = 0;
} else } else
if (cur_menu->id == MenuId_NSP) if (cur_menu->id == MenuId_Nsp)
{ {
g_nspMenuElements[0]->userdata = NULL; g_nspMenuElements[0]->userdata = NULL;
} else } else
@ -1554,13 +1592,13 @@ int main(int argc, char *argv[])
consolePrint("press any button to go back"); consolePrint("press any button to go back");
utilsWaitForButtonPress(0); utilsWaitForButtonPress(0);
} else } else
if (((btn_down & (HidNpadButton_L)) || (btn_held & HidNpadButton_ZL)) && (cur_menu->id == MenuId_NSP || cur_menu->id == MenuId_Ticket || cur_menu->id == MenuId_Nca) && title_info->previous) if (((btn_down & (HidNpadButton_L)) || (btn_held & HidNpadButton_ZL)) && (cur_menu->id == MenuId_Nsp || cur_menu->id == MenuId_Ticket || cur_menu->id == MenuId_Nca) && title_info->previous)
{ {
title_info = title_info->previous; title_info = title_info->previous;
title_info_idx--; title_info_idx--;
switchNcaListTitle(&cur_menu, &element_count, title_info); switchNcaListTitle(&cur_menu, &element_count, title_info);
} else } else
if (((btn_down & (HidNpadButton_R)) || (btn_held & HidNpadButton_ZR)) && (cur_menu->id == MenuId_NSP || cur_menu->id == MenuId_Ticket || cur_menu->id == MenuId_Nca) && title_info->next) if (((btn_down & (HidNpadButton_R)) || (btn_held & HidNpadButton_ZR)) && (cur_menu->id == MenuId_Nsp || cur_menu->id == MenuId_Ticket || cur_menu->id == MenuId_Nca) && title_info->next)
{ {
title_info = title_info->next; title_info = title_info->next;
title_info_idx++; title_info_idx++;
@ -3595,6 +3633,41 @@ end:
return success; return success;
} }
static bool saveSystemUpdateDump(void *userdata)
{
NX_IGNORE_ARG(userdata);
SystemUpdateDumpContext sys_upd_dump_ctx = {0};
SystemUpdateThreadData sys_upd_thread_data = {0};
SharedThreadData *shared_thread_data = &(sys_upd_thread_data.shared_thread_data);
char size_str[16] = {0};
bool success = false;
if (!systemUpdateInitializeDumpContext(&sys_upd_dump_ctx))
{
consolePrint("system update dump ctx init failed!\n");
goto end;
}
sys_upd_thread_data.sys_upd_dump_ctx = &sys_upd_dump_ctx;
shared_thread_data->total_size = sys_upd_dump_ctx.total_size;
utilsGenerateFormattedSizeString((double)sys_upd_dump_ctx.total_size, size_str, sizeof(size_str));
consolePrint("sysupd description: %.*s\nsysupd size: 0x%lX (%s)\nsysupd content count: %u\n", (int)sizeof(sys_upd_dump_ctx.version_file.display_title), \
sys_upd_dump_ctx.version_file.display_title, sys_upd_dump_ctx.total_size, \
size_str, sys_upd_dump_ctx.content_count);
consoleRefresh();
success = spanDumpThreads(systemUpdateReadThreadFunc, genericWriteThreadFunc, &sys_upd_thread_data);
end:
systemUpdateFreeDumpContext(&sys_upd_dump_ctx);
return success;
}
static bool fsBrowser(const char *mount_name, const char *base_out_path) static bool fsBrowser(const char *mount_name, const char *base_out_path)
{ {
char dir_path[FS_MAX_PATH] = {0}; char dir_path[FS_MAX_PATH] = {0};
@ -5985,6 +6058,255 @@ end:
return !shared_thread_data->read_error; return !shared_thread_data->read_error;
} }
static void systemUpdateReadThreadFunc(void *arg)
{
void *buf1 = NULL, *buf2 = NULL;
SystemUpdateThreadData *sys_upd_thread_data = (SystemUpdateThreadData*)arg;
SharedThreadData *shared_thread_data = &(sys_upd_thread_data->shared_thread_data);
SystemUpdateDumpContext *sys_upd_dump_ctx = sys_upd_thread_data->sys_upd_dump_ctx;
char sys_upd_path[FS_MAX_PATH] = {0}, *filename = NULL;
size_t filename_len = 0;
u64 free_space = 0;
u32 dev_idx = g_storageMenuElementOption.selected;
u64 nca_filesize = 0;
char *nca_filename = NULL;
buf1 = usbAllocatePageAlignedBuffer(BLOCK_SIZE);
buf2 = usbAllocatePageAlignedBuffer(BLOCK_SIZE);
snprintf(sys_upd_path, MAX_ELEMENTS(sys_upd_path), "/%.*s (%s)", (int)sizeof(sys_upd_dump_ctx->version_file.display_title),
sys_upd_dump_ctx->version_file.display_title, utilsIsDevelopmentUnit() ? "Dev" : "Prod");
filename = generateOutputGameCardFileName("System Update", sys_upd_path, false);
filename_len = (filename ? strlen(filename) : 0);
if (!shared_thread_data->total_size || !buf1 || !buf2 || !filename)
{
shared_thread_data->read_error = true;
goto end;
}
utilsReplaceIllegalCharacters(strrchr(filename, '/') + 1, dev_idx == 0);
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;
}
if (!shared_thread_data->read_error && shared_thread_data->total_size >= free_space)
{
consolePrint("dump size exceeds free space\n");
shared_thread_data->read_error = true;
}
} else {
if (!usbStartExtractedFsDump(shared_thread_data->total_size, filename))
{
consolePrint("failed to send extracted fs info to host\n");
shared_thread_data->read_error = true;
}
}
if (shared_thread_data->read_error)
{
condvarWakeAll(&g_writeCondvar);
goto end;
}
/* Loop through all file entries. */
while(sys_upd_dump_ctx->content_idx < sys_upd_dump_ctx->content_count)
{
if (nca_filename)
{
free(nca_filename);
nca_filename = NULL;
}
/* 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;
if (dev_idx == 0) utilsCommitSdCardFileSystemChanges();
}
}
/* Retrieve system update content file information. */
shared_thread_data->read_error = (!systemUpdateGetCurrentContentFileSizeFromDumpContext(sys_upd_dump_ctx, &nca_filesize) || !nca_filesize || \
!(nca_filename = systemUpdateGetCurrentContentFileNameFromDumpContext(sys_upd_dump_ctx)));
if (shared_thread_data->read_error)
{
condvarWakeAll(&g_writeCondvar);
break;
}
/* Generate output path. */
snprintf(sys_upd_path, MAX_ELEMENTS(sys_upd_path), "%s/%s", filename, nca_filename);
utilsReplaceIllegalCharacters(sys_upd_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(nca_filesize, sys_upd_path);
} else {
/* Create directory tree. */
utilsCreateDirectoryTree(sys_upd_path, false);
if (dev_idx == 0)
{
/* Create ConcatenationFile if we're dealing with a big file + SD card as the output storage. */
if (nca_filesize > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(sys_upd_path))
{
consolePrint("failed to create concatenation file for \"%s\"!\n", sys_upd_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 && nca_filesize > 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(sys_upd_path, "wb")) == NULL);
if (!shared_thread_data->read_error)
{
/* Set file size. */
setvbuf(shared_thread_data->fp, NULL, _IONBF, 0);
ftruncate(fileno(shared_thread_data->fp), (off_t)nca_filesize);
} else {
consolePrint("failed to open \"%s\" for writing!\n", sys_upd_path);
}
}
}
if (shared_thread_data->read_error)
{
condvarWakeAll(&g_writeCondvar);
break;
}
for(u64 offset = 0, blksize = BLOCK_SIZE; offset < nca_filesize; offset += blksize)
{
if (blksize > (nca_filesize - offset)) blksize = (nca_filesize - 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 = !systemUpdateReadCurrentContentFileFromDumpContext(sys_upd_dump_ctx, buf1, blksize);
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);
if (dev_idx == 1) usbEndExtractedFsDump();
shared_thread_data->read_error = !systemUpdateIsDumpContextFinished(sys_upd_dump_ctx);
if (!shared_thread_data->read_error)
{
consolePrint("successfully saved system update data to \"%s\"\n", filename);
} else {
consolePrint("unexpected sys upd dump ctx error\n");
}
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 (nca_filename) free(nca_filename);
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

View file

@ -92,6 +92,23 @@ typedef struct {
NXDT_ASSERT(Version, 0x4); NXDT_ASSERT(Version, 0x4);
/// Used by the SystemVersion title file (0100000000000809).
typedef struct {
u8 major;
u8 minor;
u8 micro;
u8 reserved_1;
u8 rev_major;
u8 rev_minor;
u8 reserved_2[0x2];
char platform[0x20]; ///< e.g. "NX".
char hash[0x40]; ///< e.g. "52971eebbba7ab9e6e23d73753aa63e0c3794b16".
char display_version[0x18]; ///< e.g. "19.0.0".
char display_title[0x80]; ///< e.g. "NintendoSDK Firmware for NX 19.0.0-4.0".
} SystemVersionFile;
NXDT_ASSERT(SystemVersionFile, 0x100);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -24,6 +24,7 @@
#ifndef __SYSTEM_UPDATE_H__ #ifndef __SYSTEM_UPDATE_H__
#define __SYSTEM_UPDATE_H__ #define __SYSTEM_UPDATE_H__
#include "hos_version_structs.h"
#include "nca.h" #include "nca.h"
#include "title.h" #include "title.h"
@ -32,13 +33,14 @@ extern "C" {
#endif #endif
typedef struct { typedef struct {
u64 cur_size; ///< Current dump size. u64 cur_size; ///< Current dump size.
u64 total_size; ///< Total dump size. u64 total_size; ///< Total dump size.
u32 content_idx; ///< Current content index. u32 content_idx; ///< Current content index.
u32 content_count; ///< Total content count. u32 content_count; ///< Total content count.
u64 cur_content_offset; ///< Current content offset. u64 cur_content_offset; ///< Current content offset.
Sha256Context sha256_ctx; ///< SHA-256 hash context. Used to verify dumped NCAs. Sha256Context sha256_ctx; ///< SHA-256 hash context. Used to verify dumped NCAs.
NcaContext **nca_ctxs; ///< NCA context pointer array for all system update contents. Used to read content data. NcaContext **nca_ctxs; ///< NCA context pointer array for all system update contents. Used to read content data.
SystemVersionFile version_file; ///< File data from the SystemVersion title.
} SystemUpdateDumpContext; } SystemUpdateDumpContext;
/// Initializes the system update interface. /// Initializes the system update interface.

View file

@ -65,7 +65,7 @@ typedef enum {
TikPropertyMask_SharedTitle = BIT(1), ///< Determines if the title holds shared contents only. Most likely unused -- a remnant from previous ticket formats. TikPropertyMask_SharedTitle = BIT(1), ///< Determines if the title holds shared contents only. Most likely unused -- a remnant from previous ticket formats.
TikPropertyMask_AllContents = BIT(2), ///< Determines if the content index mask shall be bypassed. Most likely unused -- a remnant from previous ticket formats. TikPropertyMask_AllContents = BIT(2), ///< Determines if the content index mask shall be bypassed. Most likely unused -- a remnant from previous ticket formats.
TikPropertyMask_DeviceLinkIndepedent = BIT(3), ///< Determines if the console should *not* connect to the Internet to verify if the title's being used by the primary console. TikPropertyMask_DeviceLinkIndepedent = BIT(3), ///< Determines if the console should *not* connect to the Internet to verify if the title's being used by the primary console.
TikPropertyMask_Volatile = BIT(4), ///< Determines if the ticket copy inside ticket.bin should be encrypted or not. TikPropertyMask_Volatile = BIT(4), ///< Determines if the ticket copy inside ticket.bin is available after reboot. Can be encrypted.
TikPropertyMask_ELicenseRequired = BIT(5), ///< Determines if the console should connect to the Internet to perform license verification. TikPropertyMask_ELicenseRequired = BIT(5), ///< Determines if the console should connect to the Internet to perform license verification.
TikPropertyMask_Count = 6 ///< Total values supported by this enum. TikPropertyMask_Count = 6 ///< Total values supported by this enum.
} TikPropertyMask; } TikPropertyMask;

View file

@ -68,6 +68,7 @@
#define BOOT_SYSMODULE_TID (u64)0x0100000000000005 #define BOOT_SYSMODULE_TID (u64)0x0100000000000005
#define SPL_SYSMODULE_TID (u64)0x0100000000000028 #define SPL_SYSMODULE_TID (u64)0x0100000000000028
#define ES_SYSMODULE_TID (u64)0x0100000000000033 #define ES_SYSMODULE_TID (u64)0x0100000000000033
#define SYSTEM_VERSION_TID (u64)0x0100000000000809
#define SYSTEM_UPDATE_TID (u64)0x0100000000000816 #define SYSTEM_UPDATE_TID (u64)0x0100000000000816
#define QLAUNCH_TID (u64)0x0100000000001000 #define QLAUNCH_TID (u64)0x0100000000001000

View file

@ -22,6 +22,9 @@
#include <core/nxdt_utils.h> #include <core/nxdt_utils.h>
#include <core/system_update.h> #include <core/system_update.h>
#include <core/cnmt.h> #include <core/cnmt.h>
#include <core/romfs.h>
#define SYSTEM_VERSION_FILE_PATH "/file"
/* Global variables. */ /* Global variables. */
@ -41,6 +44,8 @@ static bool systemUpdateProcessContentRecords(SystemUpdateDumpContext *ctx, Titl
static int systemUpdateNcaContextSortFunction(const void *a, const void *b); static int systemUpdateNcaContextSortFunction(const void *a, const void *b);
static bool systemUpdateGetSystemVersionFileData(SystemUpdateDumpContext *ctx);
NX_INLINE NcaContext *systemUpdateGetCurrentNcaContextFromDumpContext(SystemUpdateDumpContext *ctx); NX_INLINE NcaContext *systemUpdateGetCurrentNcaContextFromDumpContext(SystemUpdateDumpContext *ctx);
bool systemUpdateInitialize(void) bool systemUpdateInitialize(void)
@ -175,6 +180,7 @@ bool systemUpdateReadCurrentContentFileFromDumpContext(SystemUpdateDumpContext *
return false; return false;
} }
u8 nca_hash[SHA256_HASH_SIZE] = {0};
bool success = false; bool success = false;
/* Read NCA data. */ /* Read NCA data. */
@ -199,9 +205,9 @@ bool systemUpdateReadCurrentContentFileFromDumpContext(SystemUpdateDumpContext *
if (ctx->cur_content_offset >= nca_ctx->content_size) if (ctx->cur_content_offset >= nca_ctx->content_size)
{ {
/* Verify SHA-256 hash for this content. */ /* Verify SHA-256 hash for this content. */
sha256ContextGetHash(&(ctx->sha256_ctx), nca_ctx->hash); sha256ContextGetHash(&(ctx->sha256_ctx), nca_hash);
if (memcmp(nca_ctx->hash, nca_ctx->content_id.c, sizeof(nca_ctx->content_id.c)) != 0) if (memcmp(nca_hash, nca_ctx->content_id.c, sizeof(nca_ctx->content_id.c)) != 0)
{ {
LOG_MSG_ERROR("SHA-256 checksum mismatch for %s NCA \"%s\"! (title %016lX).", titleGetNcmContentTypeName(nca_ctx->content_type), \ LOG_MSG_ERROR("SHA-256 checksum mismatch for %s NCA \"%s\"! (title %016lX).", titleGetNcmContentTypeName(nca_ctx->content_type), \
nca_ctx->content_id_str, nca_ctx->title_id); nca_ctx->content_id_str, nca_ctx->title_id);
@ -248,15 +254,18 @@ static bool _systemUpdateInitializeDumpContext(SystemUpdateDumpContext *ctx)
/* Manually add SystemUpdate content records. */ /* Manually add SystemUpdate content records. */
/* The SystemUpdate CNMT doesn't reference itself. */ /* The SystemUpdate CNMT doesn't reference itself. */
success = systemUpdateProcessContentRecords(ctx, g_systemUpdateTitleInfo); if (!systemUpdateProcessContentRecords(ctx, g_systemUpdateTitleInfo))
if (!success)
{ {
LOG_MSG_ERROR("Failed to process SystemUpdate content records!"); LOG_MSG_ERROR("Failed to process SystemUpdate content records!");
goto end; goto end;
} }
/* Sort NCA contexts. */ /* Sort NCA contexts. */
qsort(ctx->nca_ctxs, ctx->content_count, sizeof(NcaContext*), &systemUpdateNcaContextSortFunction); if (ctx->content_count > 1) qsort(ctx->nca_ctxs, ctx->content_count, sizeof(NcaContext*), &systemUpdateNcaContextSortFunction);
/* Retrieve system version file data. */
success = systemUpdateGetSystemVersionFileData(ctx);
if (!success) LOG_MSG_ERROR("Failed to retrieve SystemVersion file data!");
end: end:
/* Free output context, if needed. */ /* Free output context, if needed. */
@ -414,6 +423,74 @@ static int systemUpdateNcaContextSortFunction(const void *a, const void *b)
return 0; return 0;
} }
static bool systemUpdateGetSystemVersionFileData(SystemUpdateDumpContext *ctx)
{
if (!systemUpdateIsValidDumpContext(ctx))
{
LOG_MSG_ERROR("Invalid parameters!");
return false;
}
NcaContext *nca_ctx = NULL;
RomFileSystemContext romfs_ctx = {0};
RomFileSystemFileEntry *romfs_file_entry = NULL;
bool success = false;
/* Loop through our NCA contexts until we find the Data NCA for the SystemVersion title. */
for(u32 i = 0; i < ctx->content_count; i++)
{
nca_ctx = ctx->nca_ctxs[i];
if (nca_ctx && nca_ctx->title_id == SYSTEM_VERSION_TID && nca_ctx->content_type == NcmContentType_Data) break;
nca_ctx = NULL;
}
if (!nca_ctx)
{
LOG_MSG_ERROR("Unable to find Data NCA for SystemVersion title!");
goto end;
}
LOG_MSG_DEBUG("Found Data NCA \"%s\" for SystemVersion title.", nca_ctx->content_id_str);
/* Initialize RomFS context. */
if (!romfsInitializeContext(&romfs_ctx, &(nca_ctx->fs_ctx[0]), NULL))
{
LOG_MSG_ERROR("Failed to initialize RomFS context for SystemVersion Data NCA!");
goto end;
}
/* Get RomFS file entry. */
if (!(romfs_file_entry = romfsGetFileEntryByPath(&romfs_ctx, SYSTEM_VERSION_FILE_PATH)))
{
LOG_MSG_ERROR("Failed to retrieve RomFS file entry for SystemVersion Data NCA!");
goto end;
}
/* Validate file size. */
if (romfs_file_entry->size != sizeof(ctx->version_file))
{
LOG_MSG_ERROR("Invalid RomFS file entry size in SystemVersion Data NCA! Got 0x%lX, expected 0x%lX.", romfs_file_entry->size, sizeof(ctx->version_file));
goto end;
}
/* Read SystemVersion file data. */
success = romfsReadFileEntryData(&romfs_ctx, romfs_file_entry, &(ctx->version_file), sizeof(ctx->version_file), 0);
if (!success)
{
LOG_MSG_ERROR("Failed to read SystemVersion file data!");
goto end;
}
LOG_DATA_DEBUG(&(ctx->version_file), sizeof(ctx->version_file), "SystemVersion file data:");
end:
romfsFreeContext(&romfs_ctx);
return success;
}
NX_INLINE NcaContext *systemUpdateGetCurrentNcaContextFromDumpContext(SystemUpdateDumpContext *ctx) NX_INLINE NcaContext *systemUpdateGetCurrentNcaContextFromDumpContext(SystemUpdateDumpContext *ctx)
{ {
return ((systemUpdateIsValidDumpContext(ctx) && !systemUpdateIsDumpContextFinished(ctx)) ? ctx->nca_ctxs[ctx->content_idx] : NULL); return ((systemUpdateIsValidDumpContext(ctx) && !systemUpdateIsDumpContextFinished(ctx)) ? ctx->nca_ctxs[ctx->content_idx] : NULL);