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

QoL changes.

* gc_dumper: add UMS device support.
* nsp_dumper: add DLC Update support.
* cnmt: add a reminder about the extended data size in NcmContentMetaType_DataPatch CNMTs.
* nca: update NcaKeyGeneration enum comment.
* title: update system titles array, severely overhaul the way linked lists work in Title* structs to properly support DLC updates.
This commit is contained in:
Pablo Curiel 2022-12-04 11:29:47 +01:00
parent 0f1055c84e
commit 3aae84a025
6 changed files with 357 additions and 204 deletions

View file

@ -1,7 +1,7 @@
/* /*
* main.c * main.c
* *
* Copyright (c) 2020, DarkMatterCore <pabloacurielz@gmail.com>. * Copyright (c) 2020-2022, DarkMatterCore <pabloacurielz@gmail.com>.
* *
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool). * This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
* *
@ -37,7 +37,7 @@ typedef void (*MenuElementOptionFunction)(u32 idx);
typedef struct { typedef struct {
u32 selected; ///< Used to keep track of the selected option. u32 selected; ///< Used to keep track of the selected option.
MenuElementOptionFunction options_func; ///< Pointer to a function to be called each time a new option is selected. Should be set to NULL if not used. MenuElementOptionFunction options_func; ///< Pointer to a function to be called each time a new option is selected. Should be set to NULL if not used.
const char **options; ///< Pointer to multiple char pointers with strings representing options. Last element must be set to NULL. char **options; ///< Pointer to multiple char pointers with strings representing options. Last element must be set to NULL.
} MenuElementOption; } MenuElementOption;
typedef bool (*MenuElementFunction)(void); typedef bool (*MenuElementFunction)(void);
@ -70,10 +70,21 @@ typedef struct
/* Function prototypes. */ /* Function prototypes. */
static void utilsScanPads(void);
static u64 utilsGetButtonsDown(void);
static u64 utilsGetButtonsHeld(void);
static void utilsWaitForButtonPress(u64 flag);
static void consolePrint(const char *text, ...); static void consolePrint(const char *text, ...);
static void consoleRefresh(void);
static u32 menuGetElementCount(const Menu *menu); static u32 menuGetElementCount(const Menu *menu);
void freeStorageList(void);
void updateStorageList(void);
NX_INLINE bool useUsbHost(void);
static bool waitForGameCard(void); static bool waitForGameCard(void);
static bool waitForUsb(void); static bool waitForUsb(void);
@ -89,7 +100,6 @@ static bool saveGameCardIdSet(void);
static bool saveGameCardImage(void); static bool saveGameCardImage(void);
static bool saveConsoleLafwBlob(void); static bool saveConsoleLafwBlob(void);
static void changeStorageOption(u32 idx);
static void changeKeyAreaOption(u32 idx); static void changeKeyAreaOption(u32 idx);
static void changeCertificateOption(u32 idx); static void changeCertificateOption(u32 idx);
static void changeTrimOption(u32 idx); static void changeTrimOption(u32 idx);
@ -100,10 +110,9 @@ static void write_thread_func(void *arg);
/* Global variables. */ /* Global variables. */
static bool g_useUsbHost = false, g_appendKeyArea = true, g_keepCertificate = false, g_trimDump = false, g_calcCrc = false; static bool g_appendKeyArea = true, g_keepCertificate = false, g_trimDump = false, g_calcCrc = false;
static const char *g_storageOptions[] = { "sd card", "usb host", NULL }; static char *g_xciOptions[] = { "no", "yes", NULL };
static const char *g_xciOptions[] = { "no", "yes", NULL };
static MenuElement *g_xciMenuElements[] = { static MenuElement *g_xciMenuElements[] = {
&(MenuElement){ &(MenuElement){
@ -162,6 +171,12 @@ static Menu g_xciMenu = {
.elements = g_xciMenuElements .elements = g_xciMenuElements
}; };
static MenuElementOption g_storageMenuElementOption = {
.selected = 0,
.options_func = NULL,
.options = NULL
};
static MenuElement *g_rootMenuElements[] = { static MenuElement *g_rootMenuElements[] = {
&(MenuElement){ &(MenuElement){
.str = "dump gamecard xci", .str = "dump gamecard xci",
@ -203,11 +218,7 @@ static MenuElement *g_rootMenuElements[] = {
.str = "output storage", .str = "output storage",
.child_menu = NULL, .child_menu = NULL,
.task_func = NULL, .task_func = NULL,
.element_options = &(MenuElementOption){ .element_options = &g_storageMenuElementOption
.selected = 0,
.options_func = &changeStorageOption,
.options = g_storageOptions
}
}, },
NULL NULL
}; };
@ -219,40 +230,16 @@ static Menu g_rootMenu = {
.elements = g_rootMenuElements .elements = g_rootMenuElements
}; };
static Mutex g_fileMutex = 0; static Mutex g_conMutex = 0, g_fileMutex = 0;
static CondVar g_readCondvar = 0, g_writeCondvar = 0; static CondVar g_readCondvar = 0, g_writeCondvar = 0;
static char path[FS_MAX_PATH] = {0}, txt_info[FS_MAX_PATH] = {0}; static char path[FS_MAX_PATH] = {0}, txt_info[FS_MAX_PATH] = {0};
static bool g_appletStatus = true; static bool g_appletStatus = true;
static void utilsScanPads(void) static UsbHsFsDevice *g_umsDevices = NULL;
{ static u32 g_umsDeviceCount = 0;
padUpdate(&g_padState); static char **g_storageOptions = NULL;
}
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;
}
}
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
@ -275,12 +262,13 @@ int main(int argc, char *argv[])
consoleInit(NULL); consoleInit(NULL);
chdir(DEVOPTAB_SDMC_DEVICE GAMECARD_PATH); updateStorageList();
while(appletMainLoop()) while(appletMainLoop())
{ {
consoleClear(); consoleClear();
printf("\npress b to %s.\n\n", cur_menu->parent ? "go back" : "exit"); consolePrint("\npress b to %s.\n", cur_menu->parent ? "go back" : "exit");
if (g_umsDeviceCount) consolePrint("press x to safely remove all ums devices.\n\n");
u32 limit = (cur_menu->scroll + page_size); u32 limit = (cur_menu->scroll + page_size);
MenuElement *selected_element = cur_menu->elements[cur_menu->selected]; MenuElement *selected_element = cur_menu->elements[cur_menu->selected];
@ -293,33 +281,44 @@ int main(int argc, char *argv[])
MenuElement *cur_element = cur_menu->elements[i]; MenuElement *cur_element = cur_menu->elements[i];
MenuElementOption *cur_options = cur_menu->elements[i]->element_options; MenuElementOption *cur_options = cur_menu->elements[i]->element_options;
printf("%s%s", i == cur_menu->selected ? " -> " : " ", cur_element->str); consolePrint("%s%s", i == cur_menu->selected ? " -> " : " ", cur_element->str);
if (cur_options) if (cur_options)
{ {
printf(": "); consolePrint(": ");
if (cur_options->selected > 0) printf("< "); if (cur_options->selected > 0) consolePrint("< ");
printf("%s", cur_options->options[cur_options->selected]); consolePrint("%s", cur_options->options[cur_options->selected]);
if (cur_options->options[cur_options->selected + 1]) printf(" >"); if (cur_options->options[cur_options->selected + 1]) consolePrint(" >");
} }
printf("\n"); consolePrint("\n");
} }
printf("\n"); consolePrint("\n");
consoleUpdate(NULL); consoleRefresh();
bool data_update = false;
u64 btn_down = 0, btn_held = 0; u64 btn_down = 0, btn_held = 0;
while((g_appletStatus = appletMainLoop())) while((g_appletStatus = appletMainLoop()))
{ {
utilsScanPads(); utilsScanPads();
btn_down = utilsGetButtonsDown(); btn_down = utilsGetButtonsDown();
btn_held = utilsGetButtonsHeld(); btn_held = utilsGetButtonsHeld();
if (btn_down || btn_held) break; if (btn_down || btn_held) break;
if (umsIsDeviceInfoUpdated())
{
updateStorageList();
data_update = true;
break;
}
} }
if (!g_appletStatus) break; if (!g_appletStatus) break;
if (data_update) continue;
if (btn_down & HidNpadButton_A) if (btn_down & HidNpadButton_A)
{ {
Menu *child_menu = (Menu*)selected_element->child_menu; Menu *child_menu = (Menu*)selected_element->child_menu;
@ -340,14 +339,20 @@ int main(int argc, char *argv[])
} }
/* Wait for USB session. */ /* Wait for USB session. */
if (g_useUsbHost && !waitForUsb()) break; if (useUsbHost() && !waitForUsb()) break;
/* Generate dump text. */ /* Generate dump text. */
generateDumpTxt(); generateDumpTxt();
/* Run task. */ /* Run task. */
utilsSetLongRunningProcessState(true); utilsSetLongRunningProcessState(true);
if (selected_element->task_func()) saveDumpTxt();
if (selected_element->task_func())
{
saveDumpTxt();
if (!useUsbHost()) updateStorageList(); // update free space
}
utilsSetLongRunningProcessState(false); utilsSetLongRunningProcessState(false);
/* Display prompt. */ /* Display prompt. */
@ -411,12 +416,20 @@ int main(int argc, char *argv[])
cur_menu = cur_menu->parent; cur_menu = cur_menu->parent;
element_count = menuGetElementCount(cur_menu); element_count = menuGetElementCount(cur_menu);
} else
if ((btn_down & HidNpadButton_X) && g_umsDeviceCount)
{
for(u32 i = 0; i < g_umsDeviceCount; i++) usbHsFsUnmountDevice(&(g_umsDevices[i]), false);
updateStorageList();
} }
if (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp)) svcSleepThread(50000000); // 50 ms if (btn_held & (HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp)) svcSleepThread(50000000); // 50 ms
} }
out: out:
freeStorageList();
utilsCloseResources(); utilsCloseResources();
consoleExit(NULL); consoleExit(NULL);
@ -424,13 +437,52 @@ out:
return ret; return ret;
} }
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);
consoleRefresh();
while(appletMainLoop())
{
utilsScanPads();
if (utilsGetButtonsDown() & flag) break;
}
}
static void consolePrint(const char *text, ...) static void consolePrint(const char *text, ...)
{ {
mutexLock(&g_conMutex);
va_list v; va_list v;
va_start(v, text); va_start(v, text);
vfprintf(stdout, text, v); vfprintf(stdout, text, v);
va_end(v); va_end(v);
mutexUnlock(&g_conMutex);
}
static void consoleRefresh(void)
{
mutexLock(&g_conMutex);
fflush(stdout);
consoleUpdate(NULL); consoleUpdate(NULL);
mutexUnlock(&g_conMutex);
} }
static u32 menuGetElementCount(const Menu *menu) static u32 menuGetElementCount(const Menu *menu)
@ -442,10 +494,105 @@ static u32 menuGetElementCount(const Menu *menu)
return cnt; return cnt;
} }
void freeStorageList(void)
{
u32 elem_count = (2 + g_umsDeviceCount); // sd card, usb host, ums devices
/* Free all previously allocated data. */
if (g_storageOptions)
{
for(u32 i = 0; i < elem_count && g_storageOptions[i]; i++)
{
free(g_storageOptions[i]);
g_storageOptions[i] = NULL;
}
free(g_storageOptions);
g_storageOptions = NULL;
}
if (g_umsDevices)
{
free(g_umsDevices);
g_umsDevices = NULL;
}
g_umsDeviceCount = 0;
}
void updateStorageList(void)
{
char **tmp = NULL;
u32 elem_count = 0, idx = 0;
/* Free all previously allocated data. */
freeStorageList();
/* Get UMS devices. */
g_umsDevices = umsGetDevices(&g_umsDeviceCount);
elem_count = (2 + g_umsDeviceCount); // sd card, usb host, ums devices
/* Reallocate buffer. */
tmp = realloc(g_storageOptions, (elem_count + 1) * sizeof(char*)); // NULL terminator
g_storageOptions = tmp;
tmp = NULL;
memset(g_storageOptions, 0, (elem_count + 1) * sizeof(char*)); // NULL terminator
/* Generate UMS device strings. */
for(u32 i = 0; i < elem_count; i++)
{
u64 total = 0, free = 0;
char total_str[36] = {0}, free_str[32] = {0};
g_storageOptions[idx] = calloc(sizeof(char), 0x300);
if (!g_storageOptions[idx]) continue;
if (i == 1)
{
sprintf(g_storageOptions[idx], "usb host (pc)");
} else {
sprintf(total_str, "%s/", i == 0 ? DEVOPTAB_SDMC_DEVICE : g_umsDevices[i - 2].name);
utilsGetFileSystemStatsByPath(total_str, &total, &free);
utilsGenerateFormattedSizeString(total, total_str, sizeof(total_str));
utilsGenerateFormattedSizeString(free, free_str, sizeof(free_str));
if (i == 0)
{
sprintf(g_storageOptions[idx], DEVOPTAB_SDMC_DEVICE " (%s / %s)", free_str, total_str);
} else {
UsbHsFsDevice *ums_device = &(g_umsDevices[i]);
if (ums_device->product_name[0])
{
sprintf(g_storageOptions[idx], "%s (%s, LUN %u, FS #%u, %s)", ums_device->name, ums_device->product_name, ums_device->lun, ums_device->fs_idx, LIBUSBHSFS_FS_TYPE_STR(ums_device->fs_type));
} else {
sprintf(g_storageOptions[idx], "%s (LUN %u, FS #%u, %s)", ums_device->name, ums_device->lun, ums_device->fs_idx, LIBUSBHSFS_FS_TYPE_STR(ums_device->fs_type));
}
sprintf(g_storageOptions[idx] + strlen(g_storageOptions[idx]), " (%s / %s)", free_str, total_str);
}
}
idx++;
}
/* Update storage menu element options. */
if (g_storageMenuElementOption.selected >= elem_count) g_storageMenuElementOption.selected = 0;
g_storageMenuElementOption.options = g_storageOptions;
}
NX_INLINE bool useUsbHost(void)
{
return (g_storageMenuElementOption.selected == 1);
}
static bool waitForGameCard(void) static bool waitForGameCard(void)
{ {
consoleClear(); consoleClear();
consolePrint("waiting for gamecard...\n"); consolePrint("waiting for gamecard...\n");
consoleRefresh();
u8 status = GameCardStatus_NotInserted; u8 status = GameCardStatus_NotInserted;
@ -487,6 +634,7 @@ static bool waitForUsb(void)
if (usbIsReady()) return true; if (usbIsReady()) return true;
consolePrint("waiting for usb session...\n"); consolePrint("waiting for usb session...\n");
consoleRefresh();
while((g_appletStatus = appletMainLoop())) while((g_appletStatus = appletMainLoop()))
{ {
@ -526,7 +674,7 @@ static bool saveFileData(const char *path, void *data, size_t data_size)
return false; return false;
} }
if (g_useUsbHost) if (useUsbHost())
{ {
if (!usbSendFilePropertiesCommon(data_size, path)) if (!usbSendFilePropertiesCommon(data_size, path))
{ {
@ -572,20 +720,36 @@ static bool saveDumpTxt(void)
static char *generateOutputFileName(const char *extension) static char *generateOutputFileName(const char *extension)
{ {
char *filename = NULL, *output = NULL; char *filename = NULL, *prefix = NULL, *output = NULL;
if (!extension || !*extension || !(filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, g_useUsbHost ? TitleFileNameIllegalCharReplaceType_IllegalFsChars : TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly))) if (!extension || !*extension || !(filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, g_storageMenuElementOption.selected > 0 ? TitleFileNameIllegalCharReplaceType_IllegalFsChars : TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly)))
{ {
consolePrint("failed to get gamecard filename!\n"); consolePrint("failed to get gamecard filename!\n");
return NULL; return NULL;
} }
output = utilsGeneratePath(NULL, filename, extension); if (!useUsbHost())
{
prefix = calloc(sizeof(char), 0x300);
if (!prefix)
{
consolePrint("failed to generate prefix!\n");
free(filename);
return NULL;
}
sprintf(prefix, "%s:/gamecard_data/", g_storageMenuElementOption.selected == 0 ? DEVOPTAB_SDMC_DEVICE : g_umsDevices[g_storageMenuElementOption.selected - 2].name);
}
output = utilsGeneratePath(prefix, filename, extension);
if (prefix) free(prefix);
free(filename); free(filename);
if (output) if (output)
{ {
snprintf(path, MAX_ELEMENTS(path), "%s", output); snprintf(path, MAX_ELEMENTS(path), "%s", output);
utilsCreateDirectoryTree(path, false);
} else { } else {
consolePrint("failed to generate output filename!\n"); consolePrint("failed to generate output filename!\n");
} }
@ -628,7 +792,7 @@ static bool saveGameCardSpecificData(void)
if (!saveFileData(filename, &(gc_security_information.specific_data), sizeof(GameCardSpecificData))) goto end; if (!saveFileData(filename, &(gc_security_information.specific_data), sizeof(GameCardSpecificData))) goto end;
printf("successfully saved specific data as \"%s\"\n", filename); consolePrint("successfully saved specific data as \"%s\"\n", filename);
success = true; success = true;
end: end:
@ -660,7 +824,7 @@ static bool saveGameCardCertificate(void)
if (!saveFileData(filename, &gc_cert, sizeof(FsGameCardCertificate))) goto end; if (!saveFileData(filename, &gc_cert, sizeof(FsGameCardCertificate))) goto end;
printf("successfully saved certificate as \"%s\"\n", filename); consolePrint("successfully saved certificate as \"%s\"\n", filename);
success = true; success = true;
end: end:
@ -686,7 +850,7 @@ static bool saveGameCardInitialData(void)
if (!saveFileData(filename, &(gc_security_information.initial_data), sizeof(GameCardInitialData))) goto end; if (!saveFileData(filename, &(gc_security_information.initial_data), sizeof(GameCardInitialData))) goto end;
printf("successfully saved initial data as \"%s\"\n", filename); consolePrint("successfully saved initial data as \"%s\"\n", filename);
success = true; success = true;
end: end:
@ -712,7 +876,7 @@ static bool saveGameCardIdSet(void)
if (!saveFileData(filename, &id_set, sizeof(FsGameCardIdSet))) goto end; if (!saveFileData(filename, &id_set, sizeof(FsGameCardIdSet))) goto end;
printf("successfully saved gamecard id set as \"%s\"\n", filename); consolePrint("successfully saved gamecard id set as \"%s\"\n", filename);
success = true; success = true;
end: end:
@ -723,7 +887,7 @@ end:
static bool saveGameCardImage(void) static bool saveGameCardImage(void)
{ {
u64 gc_size = 0; u64 gc_size = 0, free_space = 0;
u32 key_area_crc = 0; u32 key_area_crc = 0;
GameCardKeyArea gc_key_area = {0}; GameCardKeyArea gc_key_area = {0};
@ -776,7 +940,7 @@ static bool saveGameCardImage(void)
filename = generateOutputFileName(path); filename = generateOutputFileName(path);
if (!filename) goto end; if (!filename) goto end;
if (g_useUsbHost) if (useUsbHost())
{ {
if (!usbSendFilePropertiesCommon(gc_size, filename)) if (!usbSendFilePropertiesCommon(gc_size, filename))
{ {
@ -790,11 +954,32 @@ static bool saveGameCardImage(void)
goto end; goto end;
} }
} else { } else {
if (!utilsGetFileSystemStatsByPath(path, NULL, &free_space))
{
consolePrint("failed to retrieve free space from selected device\n");
goto end;
}
if (gc_size >= free_space)
{
consolePrint("dump size exceeds free space\n");
goto end;
}
if (g_storageMenuElementOption.selected == 0)
{
if (gc_size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(filename)) if (gc_size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(filename))
{ {
consolePrint("failed to create concatenation file for \"%s\"!\n", filename); consolePrint("failed to create concatenation file for \"%s\"!\n", filename);
goto end; goto end;
} }
} else {
if (g_umsDevices[g_storageMenuElementOption.selected - 2].fs_type < UsbHsFsDeviceFileSystemType_exFAT && gc_size > FAT32_FILESIZE_LIMIT)
{
consolePrint("split dumps not supported for FAT12/16/32 volumes in UMS devices (yet)\n");
goto end;
}
}
shared_data.fp = fopen(filename, "wb"); shared_data.fp = fopen(filename, "wb");
if (!shared_data.fp) if (!shared_data.fp)
@ -822,6 +1007,7 @@ static bool saveGameCardImage(void)
bool btn_cancel_cur_state = false, btn_cancel_prev_state = false; bool btn_cancel_cur_state = false, btn_cancel_prev_state = false;
consolePrint("hold b to cancel\n\n"); consolePrint("hold b to cancel\n\n");
consoleRefresh();
start = time(NULL); start = time(NULL);
@ -865,8 +1051,8 @@ static bool saveGameCardImage(void)
prev_time = ts.tm_sec; prev_time = ts.tm_sec;
prev_size = size; prev_size = size;
printf("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, shared_data.total_size, percent, (now - start)); consolePrint("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, shared_data.total_size, percent, (now - start));
consoleUpdate(NULL); consoleRefresh();
} }
start = (time(NULL) - start); start = (time(NULL) - start);
@ -889,25 +1075,27 @@ static bool saveGameCardImage(void)
goto end; goto end;
} }
printf("process completed in %lu seconds\n", start); consolePrint("process completed in %lu seconds\n", start);
success = true; success = true;
if (g_calcCrc) if (g_calcCrc)
{ {
if (g_appendKeyArea) printf("key area crc: %08X | ", key_area_crc); if (g_appendKeyArea) consolePrint("key area crc: %08X | ", key_area_crc);
printf("xci crc: %08X", shared_data.xci_crc); consolePrint("xci crc: %08X", shared_data.xci_crc);
if (g_appendKeyArea) printf(" | xci crc (with key area): %08X", shared_data.full_xci_crc); if (g_appendKeyArea) consolePrint(" | xci crc (with key area): %08X", shared_data.full_xci_crc);
printf("\n"); consolePrint("\n");
} }
end: end:
consoleRefresh();
if (shared_data.fp) if (shared_data.fp)
{ {
fclose(shared_data.fp); fclose(shared_data.fp);
shared_data.fp = NULL; shared_data.fp = NULL;
} }
if (!success && !g_useUsbHost) utilsRemoveConcatenationFile(filename); if (!success && !useUsbHost()) utilsRemoveConcatenationFile(filename);
if (shared_data.data) free(shared_data.data); if (shared_data.data) free(shared_data.data);
@ -942,18 +1130,13 @@ static bool saveConsoleLafwBlob(void)
if (!saveFileData(path, &lafw_blob, sizeof(LotusAsicFirmwareBlob))) goto end; if (!saveFileData(path, &lafw_blob, sizeof(LotusAsicFirmwareBlob))) goto end;
printf("successfully saved lafw blob as \"%s\"\n", path); consolePrint("successfully saved lafw blob as \"%s\"\n", path);
success = true; success = true;
end: end:
return success; return success;
} }
static void changeStorageOption(u32 idx)
{
g_useUsbHost = (idx > 0);
}
static void changeKeyAreaOption(u32 idx) static void changeKeyAreaOption(u32 idx)
{ {
g_appendKeyArea = (idx > 0); g_appendKeyArea = (idx > 0);
@ -1063,13 +1246,13 @@ static void write_thread_func(void *arg)
if (shared_data->read_error || shared_data->transfer_cancelled) if (shared_data->read_error || shared_data->transfer_cancelled)
{ {
if (shared_data->transfer_cancelled && g_useUsbHost) usbCancelFileTransfer(); if (shared_data->transfer_cancelled && useUsbHost()) usbCancelFileTransfer();
mutexUnlock(&g_fileMutex); mutexUnlock(&g_fileMutex);
break; break;
} }
/* Write current file data chunk */ /* Write current file data chunk */
if (g_useUsbHost) if (useUsbHost())
{ {
shared_data->write_error = !usbSendFileData(shared_data->data, shared_data->data_size); shared_data->write_error = !usbSendFileData(shared_data->data, shared_data->data_size);
} else { } else {

View file

@ -48,7 +48,8 @@ typedef struct
static const char *dump_type_strings[] = { static const char *dump_type_strings[] = {
"dump base application", "dump base application",
"dump update", "dump update",
"dump dlc" "dump dlc",
"dump dlc update"
}; };
static const u32 dump_type_strings_count = MAX_ELEMENTS(dump_type_strings); static const u32 dump_type_strings_count = MAX_ELEMENTS(dump_type_strings);
@ -953,7 +954,8 @@ static void nspDump(TitleInfo *title_info)
consoleClear(); consoleClear();
consolePrint("%s info:\n\n", title_info->meta_key.type == NcmContentMetaType_Application ? "base application" : \ consolePrint("%s info:\n\n", title_info->meta_key.type == NcmContentMetaType_Application ? "base application" : \
(title_info->meta_key.type == NcmContentMetaType_Patch ? "update" : "dlc")); (title_info->meta_key.type == NcmContentMetaType_Patch ? "update" : \
(title_info->meta_key.type == NcmContentMetaType_AddOnContent ? "dlc" : "dlc update")));
if (app_metadata) if (app_metadata)
{ {
@ -1142,8 +1144,6 @@ int main(int argc, char *argv[])
ums_devices = umsGetDevices(&ums_device_count); ums_devices = umsGetDevices(&ums_device_count);
utilsSleep(1);
while((applet_status = appletMainLoop())) while((applet_status = appletMainLoop()))
{ {
consoleClear(); consoleClear();
@ -1174,7 +1174,8 @@ int main(int argc, char *argv[])
} }
consolePrint("selected %s info:\n\n", title_info->meta_key.type == NcmContentMetaType_Application ? "base application" : \ consolePrint("selected %s info:\n\n", title_info->meta_key.type == NcmContentMetaType_Application ? "base application" : \
(title_info->meta_key.type == NcmContentMetaType_Patch ? "update" : "dlc")); (title_info->meta_key.type == NcmContentMetaType_Patch ? "update" : \
(title_info->meta_key.type == NcmContentMetaType_AddOnContent ? "dlc" : "dlc update")));
consolePrint("source storage: %s\n", titleGetNcmStorageIdName(title_info->storage_id)); 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); 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, \ 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, \
@ -1341,12 +1342,12 @@ int main(int argc, char *argv[])
} else } else
if (menu == 2) 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)) 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) || (type_idx == 3 && !user_app_data.aoc_patch_info))
{ {
consolePrint("\nthe selected title doesn't have available %s data\n", type_idx == 0 ? "base application" : (type_idx == 1 ? "update" : "dlc")); consolePrint("\nthe selected title doesn't have available %s data\n", type_idx == 0 ? "base application" : (type_idx == 1 ? "update" : (type_idx == 2 ? "dlc" : "dlc update")));
error = true; error = true;
} else { } else {
title_info = (type_idx == 0 ? user_app_data.app_info : (type_idx == 1 ? user_app_data.patch_info : user_app_data.aoc_info)); title_info = (type_idx == 0 ? user_app_data.app_info : (type_idx == 1 ? user_app_data.patch_info : (type_idx == 2 ? user_app_data.aoc_info : user_app_data.aoc_patch_info)));
list_count = titleGetCountFromInfoBlock(title_info); list_count = titleGetCountFromInfoBlock(title_info);
list_idx = 1; list_idx = 1;
} }

View file

@ -91,7 +91,7 @@ typedef enum {
NcaKeyGeneration_Since1210NUP = 12, ///< 12.1.0. NcaKeyGeneration_Since1210NUP = 12, ///< 12.1.0.
NcaKeyGeneration_Since1300NUP = 13, ///< 13.0.0 - 13.2.1. NcaKeyGeneration_Since1300NUP = 13, ///< 13.0.0 - 13.2.1.
NcaKeyGeneration_Since1400NUP = 14, ///< 14.0.0 - 14.1.2. NcaKeyGeneration_Since1400NUP = 14, ///< 14.0.0 - 14.1.2.
NcaKeyGeneration_Since1500NUP = 15, ///< 15.0.0. NcaKeyGeneration_Since1500NUP = 15, ///< 15.0.0 - 15.0.1.
NcaKeyGeneration_Current = NcaKeyGeneration_Since1500NUP, NcaKeyGeneration_Current = NcaKeyGeneration_Since1500NUP,
NcaKeyGeneration_Max = 32 NcaKeyGeneration_Max = 32
} NcaKeyGeneration; } NcaKeyGeneration;

View file

@ -46,10 +46,10 @@ typedef struct {
} TitleApplicationMetadata; } TitleApplicationMetadata;
/// Generated using ncm calls. /// Generated using ncm calls.
/// User applications: the parent pointer is always unused. The previous/next pointers reference other user applications with the same ID. /// User applications: the previous/next pointers reference other user applications with the same ID.
/// Patches: the parent pointer always references the first corresponding user application. The previous/next pointers reference other patches with the same ID. /// Patches: the previous/next pointers reference other patches with the same ID.
/// Add-on contents: the parent pointer always references the first corresponding user application. The previous/next pointers reference sibling add-on contents. /// Add-on contents: the previous/next pointers reference sibling add-on contents.
/// Add-on content patches: the parent pointer always references the first corresponding add-on content. The previous/next pointers reference other patches with the same ID. /// Add-on content patches: the previous/next pointers reference other patches with the same ID and/or other patches for sibling add-on contents.
typedef struct _TitleInfo { typedef struct _TitleInfo {
u8 storage_id; ///< NcmStorageId. u8 storage_id; ///< NcmStorageId.
NcmContentMetaKey meta_key; ///< Used with ncm calls. NcmContentMetaKey meta_key; ///< Used with ncm calls.
@ -59,15 +59,16 @@ typedef struct _TitleInfo {
u64 size; ///< Total title size. u64 size; ///< Total title size.
char size_str[32]; ///< Total title size string. char size_str[32]; ///< Total title size string.
TitleApplicationMetadata *app_metadata; ///< User application metadata. TitleApplicationMetadata *app_metadata; ///< User application metadata.
struct _TitleInfo *parent, *previous, *next; ///< Linked lists. struct _TitleInfo *previous, *next; ///< Linked lists.
} TitleInfo; } TitleInfo;
/// Used to deal with user applications stored in the eMMC, SD card and/or gamecard. /// Used to deal with user applications stored in the eMMC, SD card and/or gamecard.
/// The parent, previous and next pointers from the TitleInfo elements are used to traverse through multiple user applications, patches and/or add-on contents. /// The previous and next pointers from the TitleInfo elements are used to traverse through multiple user applications, patches, add-on contents and or add-on content patches.
typedef struct { typedef struct {
TitleInfo *app_info; ///< Pointer to a TitleInfo element holding info for the first detected user application entry matching the provided application ID. TitleInfo *app_info; ///< Pointer to a TitleInfo element for the first detected user application entry matching the provided application ID.
TitleInfo *patch_info; ///< Pointer to a TitleInfo element holding info for the first detected patch entry matching the provided application ID. TitleInfo *patch_info; ///< Pointer to a TitleInfo element for the first detected patch entry matching the provided application ID.
TitleInfo *aoc_info; ///< Pointer to a TitleInfo element holding info for the first detected add-on content entry matching the provided application ID. TitleInfo *aoc_info; ///< Pointer to a TitleInfo element for the first detected add-on content entry matching the provided application ID.
TitleInfo *aoc_patch_info; ///< Pointer to a TitleInfo element for the first detected add-on content patch entry matching the provided application ID.
} TitleUserApplicationData; } TitleUserApplicationData;
typedef enum { typedef enum {
@ -250,6 +251,13 @@ NX_INLINE bool titleCheckIfDataPatchIdBelongsToApplicationId(u64 app_id, u64 dat
return titleCheckIfAddOnContentIdBelongsToApplicationId(app_id, titleGetAddOnContentIdByDataPatchId(data_patch_id)); return titleCheckIfAddOnContentIdBelongsToApplicationId(app_id, titleGetAddOnContentIdByDataPatchId(data_patch_id));
} }
NX_INLINE bool titleCheckIfDataPatchIdsAreSiblings(u64 data_patch_id_1, u64 data_patch_id_2)
{
u64 app_id_1 = titleGetApplicationIdByDataPatchId(data_patch_id_1);
u64 app_id_2 = titleGetApplicationIdByDataPatchId(data_patch_id_2);
return (app_id_1 == app_id_2 && titleCheckIfDataPatchIdBelongsToApplicationId(app_id_1, data_patch_id_1) && titleCheckIfDataPatchIdBelongsToApplicationId(app_id_2, data_patch_id_2));
}
NX_INLINE u32 titleGetContentCountByType(TitleInfo *info, u8 content_type) NX_INLINE u32 titleGetContentCountByType(TitleInfo *info, u8 content_type)
{ {
if (!info || !info->content_count || !info->content_infos || content_type > NcmContentType_DeltaFragment) return 0; if (!info || !info->content_count || !info->content_infos || content_type > NcmContentType_DeltaFragment) return 0;

View file

@ -192,7 +192,7 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx)
case NcmContentMetaType_DataPatch: case NcmContentMetaType_DataPatch:
invalid_ext_header_size = (out->packaged_header->extended_header_size != (u16)sizeof(ContentMetaDataPatchMetaExtendedHeader)); invalid_ext_header_size = (out->packaged_header->extended_header_size != (u16)sizeof(ContentMetaDataPatchMetaExtendedHeader));
out->extended_data_size = (!invalid_ext_header_size ? ((ContentMetaDataPatchMetaExtendedHeader*)out->extended_header)->extended_data_size : 0); out->extended_data_size = (!invalid_ext_header_size ? ((ContentMetaDataPatchMetaExtendedHeader*)out->extended_header)->extended_data_size : 0);
invalid_ext_data_size = (out->extended_data_size <= sizeof(ContentMetaPatchMetaExtendedDataHeader)); invalid_ext_data_size = (out->extended_data_size <= sizeof(ContentMetaPatchMetaExtendedDataHeader)); // TODO: check if this is right.
break; break;
default: default:
invalid_ext_header_size = (out->packaged_header->extended_header_size != 0); invalid_ext_header_size = (out->packaged_header->extended_header_size != 0);

View file

@ -238,14 +238,14 @@ static const TitleSystemEntry g_systemTitles[] = {
{ 0x0100000000001007, "playerSelect" }, { 0x0100000000001007, "playerSelect" },
{ 0x0100000000001008, "swkbd" }, { 0x0100000000001008, "swkbd" },
{ 0x0100000000001009, "miiEdit" }, { 0x0100000000001009, "miiEdit" },
{ 0x010000000000100A, "web" }, { 0x010000000000100A, "LibAppletWeb" },
{ 0x010000000000100B, "shop" }, { 0x010000000000100B, "LibAppletShop" },
{ 0x010000000000100C, "overlayDisp" }, { 0x010000000000100C, "overlayDisp" },
{ 0x010000000000100D, "photoViewer" }, { 0x010000000000100D, "photoViewer" },
{ 0x010000000000100E, "set" }, { 0x010000000000100E, "set" },
{ 0x010000000000100F, "offlineWeb" }, { 0x010000000000100F, "LibAppletOff" },
{ 0x0100000000001010, "loginShare" }, { 0x0100000000001010, "LibAppletLns" },
{ 0x0100000000001011, "wifiWebAuth" }, { 0x0100000000001011, "LibAppletAuth" },
{ 0x0100000000001012, "starter" }, { 0x0100000000001012, "starter" },
{ 0x0100000000001013, "myPage" }, { 0x0100000000001013, "myPage" },
{ 0x0100000000001014, "PlayReport" }, { 0x0100000000001014, "PlayReport" },
@ -524,7 +524,7 @@ static bool titleRefreshGameCardTitleInfo(void);
static bool titleIsUserApplicationContentAvailable(u64 app_id); static bool titleIsUserApplicationContentAvailable(u64 app_id);
static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id); static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id);
static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *parent, TitleInfo *previous, TitleInfo *next); static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *previous, TitleInfo *next);
static int titleSystemTitleMetadataEntrySortFunction(const void *a, const void *b); static int titleSystemTitleMetadataEntrySortFunction(const void *a, const void *b);
static int titleUserApplicationMetadataEntrySortFunction(const void *a, const void *b); static int titleUserApplicationMetadataEntrySortFunction(const void *a, const void *b);
@ -754,7 +754,7 @@ TitleInfo *titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id)
TitleInfo *title_info = (g_titleInterfaceInit ? _titleGetInfoFromStorageByTitleId(storage_id, title_id) : NULL); TitleInfo *title_info = (g_titleInterfaceInit ? _titleGetInfoFromStorageByTitleId(storage_id, title_id) : NULL);
if (title_info) if (title_info)
{ {
ret = titleDuplicateTitleInfo(title_info, NULL, NULL, NULL); ret = titleDuplicateTitleInfo(title_info, NULL, NULL);
if (!ret) LOG_MSG_ERROR("Failed to duplicate title info for %016lX!", title_id); if (!ret) LOG_MSG_ERROR("Failed to duplicate title info for %016lX!", title_id);
} }
} }
@ -770,15 +770,12 @@ void titleFreeTitleInfo(TitleInfo **info)
/* Free content infos. */ /* Free content infos. */
if (ptr->content_infos) free(ptr->content_infos); if (ptr->content_infos) free(ptr->content_infos);
/* Free parent linked list. */
titleFreeTitleInfo(&(ptr->parent));
/* Free previous sibling(s). */ /* Free previous sibling(s). */
tmp1 = ptr->previous; tmp1 = ptr->previous;
while(tmp1) while(tmp1)
{ {
tmp2 = tmp1->previous; tmp2 = tmp1->previous;
tmp1->parent = tmp1->previous = tmp1->next = NULL; tmp1->previous = tmp1->next = NULL;
titleFreeTitleInfo(&tmp1); titleFreeTitleInfo(&tmp1);
tmp1 = tmp2; tmp1 = tmp2;
} }
@ -788,7 +785,7 @@ void titleFreeTitleInfo(TitleInfo **info)
while(tmp1) while(tmp1)
{ {
tmp2 = tmp1->next; tmp2 = tmp1->next;
tmp1->parent = tmp1->previous = tmp1->next = NULL; tmp1->previous = tmp1->next = NULL;
titleFreeTitleInfo(&tmp1); titleFreeTitleInfo(&tmp1);
tmp1 = tmp2; tmp1 = tmp2;
} }
@ -809,36 +806,30 @@ bool titleGetUserApplicationData(u64 app_id, TitleUserApplicationData *out)
break; break;
} }
TitleInfo *app_info = NULL, *patch_info = NULL, *aoc_info = NULL; bool error = false;
TitleInfo *app_info = NULL, *patch_info = NULL, *aoc_info = NULL, *aoc_patch_info = NULL;
/* Clear output. */ /* Clear output. */
titleFreeUserApplicationData(out); titleFreeUserApplicationData(out);
#define TITLE_ALLOCATE_USER_APP_DATA(elem, msg, decl) \
if (elem##_info && !out->elem##_info) { \
out->elem##_info = titleDuplicateTitleInfo(elem##_info, NULL, NULL); \
if (!out->elem##_info) { \
LOG_MSG_ERROR("Failed to duplicate %s info for %016lX!", msg, app_id); \
decl; \
} \
}
/* Get info for the first user application title. */ /* Get info for the first user application title. */
app_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, app_id); app_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, app_id);
if (app_info) TITLE_ALLOCATE_USER_APP_DATA(app, "user application", break);
{
out->app_info = titleDuplicateTitleInfo(app_info, NULL, NULL, NULL);
if (!out->app_info)
{
LOG_MSG_ERROR("Failed to duplicate user application info for %016lX!", app_id);
break;
}
}
/* Get info for the first patch title. */ /* Get info for the first patch title. */
patch_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, titleGetPatchIdByApplicationId(app_id)); patch_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, titleGetPatchIdByApplicationId(app_id));
if (patch_info) TITLE_ALLOCATE_USER_APP_DATA(patch, "patch", break);
{
out->patch_info = titleDuplicateTitleInfo(patch_info, out->app_info, NULL, NULL);
if (!out->patch_info)
{
LOG_MSG_ERROR("Failed to duplicate patch info for %016lX!", app_id);
break;
}
}
/* Get info for the first add-on content title. */ /* Get info for the first add-on content and add-on content patch titles. */
for(u8 i = NcmStorageId_GameCard; i <= NcmStorageId_SdCard; i++) for(u8 i = NcmStorageId_GameCard; i <= NcmStorageId_SdCard; i++)
{ {
if (i == NcmStorageId_BuiltInSystem) continue; if (i == NcmStorageId_BuiltInSystem) continue;
@ -849,28 +840,33 @@ bool titleGetUserApplicationData(u64 app_id, TitleUserApplicationData *out)
for(u32 j = 0; j < title_storage->title_count; j++) for(u32 j = 0; j < title_storage->title_count; j++)
{ {
TitleInfo *title_info = title_storage->titles[j]; TitleInfo *title_info = title_storage->titles[j];
if (title_info && title_info->meta_key.type == NcmContentMetaType_AddOnContent && titleCheckIfAddOnContentIdBelongsToApplicationId(app_id, title_info->meta_key.id)) if (!title_info) continue;
if (title_info->meta_key.type == NcmContentMetaType_AddOnContent && titleCheckIfAddOnContentIdBelongsToApplicationId(app_id, title_info->meta_key.id))
{ {
aoc_info = title_info; aoc_info = title_info;
break; break;
} } else
} if (title_info->meta_key.type == NcmContentMetaType_DataPatch && titleCheckIfDataPatchIdBelongsToApplicationId(app_id, title_info->meta_key.id))
if (aoc_info) break;
}
if (aoc_info)
{ {
out->aoc_info = titleDuplicateTitleInfo(aoc_info, out->app_info, NULL, NULL); aoc_patch_info = title_info;
if (!out->aoc_info)
{
LOG_MSG_ERROR("Failed to duplicate add-on content info for %016lX!", app_id);
break; break;
} }
} }
TITLE_ALLOCATE_USER_APP_DATA(aoc, "add-on content", error = true; break);
TITLE_ALLOCATE_USER_APP_DATA(aoc_patch, "add-on content patch", error = true; break);
if (out->aoc_info && out->aoc_patch_info) break;
}
if (error) break;
#undef TITLE_ALLOCATE_USER_APP_DATA
/* Check retrieved title info. */ /* Check retrieved title info. */
ret = (app_info || patch_info || aoc_info); ret = (app_info || patch_info || aoc_info || aoc_patch_info);
if (!ret) LOG_MSG_ERROR("Failed to retrieve user application data for ID \"%016lX\"!", app_id); if (!ret) LOG_MSG_ERROR("Failed to retrieve user application data for ID \"%016lX\"!", app_id);
} }
@ -884,32 +880,17 @@ void titleFreeUserApplicationData(TitleUserApplicationData *user_app_data)
{ {
if (!user_app_data) return; if (!user_app_data) return;
TitleInfo *tmp = NULL;
/* Free user application info. */ /* Free user application info. */
titleFreeTitleInfo(&(user_app_data->app_info)); titleFreeTitleInfo(&(user_app_data->app_info));
/* Make sure to clear all references to the parent linked list beforehand. */
/* Unlike titleDuplicateTitleInfo(), we don't need to traverse backwards because elements from TitleUserApplicationData always point to the first title info. */
tmp = user_app_data->patch_info;
while(tmp)
{
tmp->parent = NULL;
tmp = tmp->next;
}
tmp = user_app_data->aoc_info;
while(tmp)
{
tmp->parent = NULL;
tmp = tmp->next;
}
/* Free patch info. */ /* Free patch info. */
titleFreeTitleInfo(&(user_app_data->patch_info)); titleFreeTitleInfo(&(user_app_data->patch_info));
/* Free add-on content info. */ /* Free add-on content info. */
titleFreeTitleInfo(&(user_app_data->aoc_info)); titleFreeTitleInfo(&(user_app_data->aoc_info));
/* Free add-on content patch info. */
titleFreeTitleInfo(&(user_app_data->aoc_patch_info));
} }
bool titleAreOrphanTitlesAvailable(void) bool titleAreOrphanTitlesAvailable(void)
@ -942,7 +923,7 @@ TitleInfo **titleGetOrphanTitles(u32 *out_count)
/* Duplicate orphan title info entries. */ /* Duplicate orphan title info entries. */
for(u32 i = 0; i < g_orphanTitleInfoCount; i++) for(u32 i = 0; i < g_orphanTitleInfoCount; i++)
{ {
orphan_info[i] = titleDuplicateTitleInfo(g_orphanTitleInfo[i], NULL, NULL, NULL); orphan_info[i] = titleDuplicateTitleInfo(g_orphanTitleInfo[i], NULL, NULL);
if (!orphan_info[i]) if (!orphan_info[i])
{ {
LOG_MSG_ERROR("Failed to duplicate info for orphan title %016lX!", g_orphanTitleInfo[i]->meta_key.id); LOG_MSG_ERROR("Failed to duplicate info for orphan title %016lX!", g_orphanTitleInfo[i]->meta_key.id);
@ -2071,7 +2052,7 @@ static void titleUpdateTitleInfoLinkedLists(void)
TitleInfo *child_info = titles[j]; TitleInfo *child_info = titles[j];
if (!child_info) continue; if (!child_info) continue;
child_info->parent = child_info->previous = child_info->next = NULL; child_info->previous = child_info->next = NULL;
/* If we're dealing with a title that's not an user application, patch, add-on content or add-on content patch, flag it as orphan and proceed onto the next one. */ /* If we're dealing with a title that's not an user application, patch, add-on content or add-on content patch, flag it as orphan and proceed onto the next one. */
if (child_info->meta_key.type < NcmContentMetaType_Application || (child_info->meta_key.type > NcmContentMetaType_AddOnContent && \ if (child_info->meta_key.type < NcmContentMetaType_Application || (child_info->meta_key.type > NcmContentMetaType_AddOnContent && \
@ -2081,33 +2062,21 @@ static void titleUpdateTitleInfoLinkedLists(void)
continue; continue;
} }
if (child_info->meta_key.type != NcmContentMetaType_Application) if (child_info->meta_key.type != NcmContentMetaType_Application && !child_info->app_metadata)
{ {
/* We're dealing with a patch, an add-on content or an add-on content patch. */ /* We're dealing with a patch, an add-on content or an add-on content patch. */
/* Patch, AddOnContent: retrieve pointer to the first parent user application entry and set it as the parent title. */ /* We'll just retrieve a pointer to the first matching user application entry and use it to set a pointer to an application metadata entry. */
/* DataPatch: retrieve pointer to the first parent add-on content entry and set it as the parent title. */
u64 app_id = (child_info->meta_key.type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(child_info->meta_key.id) : \ u64 app_id = (child_info->meta_key.type == NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId(child_info->meta_key.id) : \
(child_info->meta_key.type == NcmContentMetaType_AddOnContent ? titleGetApplicationIdByAddOnContentId(child_info->meta_key.id) : \ (child_info->meta_key.type == NcmContentMetaType_AddOnContent ? titleGetApplicationIdByAddOnContentId(child_info->meta_key.id) : \
titleGetAddOnContentIdByDataPatchId(child_info->meta_key.id))); titleGetApplicationIdByDataPatchId(child_info->meta_key.id)));
child_info->parent = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, app_id);
TitleInfo *parent = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, app_id);
if (parent)
{
/* Set pointer to application metadata. */ /* Set pointer to application metadata. */
if (child_info->parent && !child_info->app_metadata) child_info->app_metadata = parent->app_metadata;
{
if (child_info->meta_key.type != NcmContentMetaType_DataPatch || child_info->parent->app_metadata)
{
child_info->app_metadata = child_info->parent->app_metadata;
} else { } else {
/* We may be dealing with a parent add-on content with a yet-to-be-assigned application metadata pointer. */ /* Add orphan title info entry since we have no application metadata. */
TitleInfo *tmp_title_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, titleGetApplicationIdByDataPatchId(child_info->meta_key.id));
if (tmp_title_info) child_info->parent->app_metadata = child_info->app_metadata = tmp_title_info->app_metadata;
}
}
/* Add orphan title info entry if we have no application metadata. */
if (!child_info->app_metadata)
{
titleAddOrphanTitleInfoEntry(child_info); titleAddOrphanTitleInfoEntry(child_info);
continue; continue;
} }
@ -2134,8 +2103,9 @@ static void titleUpdateTitleInfoLinkedLists(void)
if (!prev_info) continue; if (!prev_info) continue;
if (prev_info->meta_key.type == child_info->meta_key.type && \ if (prev_info->meta_key.type == child_info->meta_key.type && \
((child_info->meta_key.type != NcmContentMetaType_AddOnContent && prev_info->meta_key.id == child_info->meta_key.id) || \ (((child_info->meta_key.type == NcmContentMetaType_Application || child_info->meta_key.type == NcmContentMetaType_Patch) && prev_info->meta_key.id == child_info->meta_key.id) || \
(child_info->meta_key.type == NcmContentMetaType_AddOnContent && titleCheckIfAddOnContentIdsAreSiblings(prev_info->meta_key.id, child_info->meta_key.id)))) (child_info->meta_key.type == NcmContentMetaType_AddOnContent && titleCheckIfAddOnContentIdsAreSiblings(prev_info->meta_key.id, child_info->meta_key.id)) || \
(child_info->meta_key.type == NcmContentMetaType_DataPatch && titleCheckIfDataPatchIdsAreSiblings(prev_info->meta_key.id, child_info->meta_key.id))))
{ {
prev_info->next = child_info; prev_info->next = child_info;
child_info->previous = prev_info; child_info->previous = prev_info;
@ -2404,7 +2374,7 @@ static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id)
return out; return out;
} }
static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *parent, TitleInfo *previous, TitleInfo *next) static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *previous, TitleInfo *next)
{ {
if (!title_info || title_info->storage_id < NcmStorageId_GameCard || title_info->storage_id > NcmStorageId_SdCard || !title_info->meta_key.id || \ if (!title_info || title_info->storage_id < NcmStorageId_GameCard || title_info->storage_id > NcmStorageId_SdCard || !title_info->meta_key.id || \
(title_info->meta_key.type > NcmContentMetaType_BootImagePackageSafe && title_info->meta_key.type < NcmContentMetaType_Application) || \ (title_info->meta_key.type > NcmContentMetaType_BootImagePackageSafe && title_info->meta_key.type < NcmContentMetaType_Application) || \
@ -2416,7 +2386,7 @@ static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *pare
TitleInfo *title_info_dup = NULL, *tmp1 = NULL, *tmp2 = NULL; TitleInfo *title_info_dup = NULL, *tmp1 = NULL, *tmp2 = NULL;
NcmContentInfo *content_infos_dup = NULL; NcmContentInfo *content_infos_dup = NULL;
bool dup_parent = false, dup_previous = false, dup_next = false, success = false; bool dup_previous = false, dup_next = false, success = false;
/* Allocate memory for the new TitleInfo element. */ /* Allocate memory for the new TitleInfo element. */
title_info_dup = calloc(1, sizeof(TitleInfo)); title_info_dup = calloc(1, sizeof(TitleInfo));
@ -2428,7 +2398,7 @@ static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *pare
/* Copy TitleInfo data. */ /* Copy TitleInfo data. */
memcpy(title_info_dup, title_info, sizeof(TitleInfo)); memcpy(title_info_dup, title_info, sizeof(TitleInfo));
title_info_dup->parent = title_info_dup->previous = title_info_dup->next = NULL; title_info_dup->previous = title_info_dup->next = NULL;
/* Allocate memory for NcmContentInfo elements. */ /* Allocate memory for NcmContentInfo elements. */
content_infos_dup = calloc(title_info->content_count, sizeof(NcmContentInfo)); content_infos_dup = calloc(title_info->content_count, sizeof(NcmContentInfo));
@ -2444,12 +2414,12 @@ static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *pare
/* Update content infos pointer. */ /* Update content infos pointer. */
title_info_dup->content_infos = content_infos_dup; title_info_dup->content_infos = content_infos_dup;
#define TITLE_DUPLICATE_LINKED_LIST(elem, prnt, prv, nxt) \ #define TITLE_DUPLICATE_LINKED_LIST(elem, prv, nxt) \
if (title_info->elem) { \ if (title_info->elem) { \
if (elem) { \ if (elem) { \
title_info_dup->elem = elem; \ title_info_dup->elem = elem; \
} else { \ } else { \
title_info_dup->elem = titleDuplicateTitleInfo(title_info->elem, prnt, prv, nxt); \ title_info_dup->elem = titleDuplicateTitleInfo(title_info->elem, prv, nxt); \
if (!title_info_dup->elem) goto end; \ if (!title_info_dup->elem) goto end; \
dup_##elem = true; \ dup_##elem = true; \
} \ } \
@ -2460,7 +2430,7 @@ static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *pare
tmp1 = title_info_dup->elem; \ tmp1 = title_info_dup->elem; \
while(tmp1) { \ while(tmp1) { \
tmp2 = tmp1->elem; \ tmp2 = tmp1->elem; \
tmp1->parent = tmp1->previous = tmp1->next = NULL; \ tmp1->previous = tmp1->next = NULL; \
titleFreeTitleInfo(&tmp1); \ titleFreeTitleInfo(&tmp1); \
tmp1 = tmp2; \ tmp1 = tmp2; \
} \ } \
@ -2469,14 +2439,8 @@ static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *pare
/* Duplicate linked lists based on two different principles: */ /* Duplicate linked lists based on two different principles: */
/* 1) Linked list pointers will only be populated if their corresponding pointer is also populated in the TitleInfo element to duplicate. */ /* 1) Linked list pointers will only be populated if their corresponding pointer is also populated in the TitleInfo element to duplicate. */
/* 2) Pointers passed into this function take precedence before actual data duplication. */ /* 2) Pointers passed into this function take precedence before actual data duplication. */
TITLE_DUPLICATE_LINKED_LIST(previous, NULL, title_info_dup);
/* Duplicate parent linked list and update pointer to parent TitleInfo entry -- this will be used while duplicating siblings in the next linked lists. */ TITLE_DUPLICATE_LINKED_LIST(next, title_info_dup, NULL);
TITLE_DUPLICATE_LINKED_LIST(parent, NULL, NULL, NULL);
if (title_info->parent) parent = title_info_dup->parent;
/* Duplicate previous and next linked lists. */
TITLE_DUPLICATE_LINKED_LIST(previous, parent, NULL, title_info_dup);
TITLE_DUPLICATE_LINKED_LIST(next, parent, title_info_dup, NULL);
/* Update flag. */ /* Update flag. */
success = true; success = true;
@ -2490,17 +2454,14 @@ end:
if (title_info_dup) if (title_info_dup)
{ {
/* Free parent linked list (if duplicated). */
/* Parent TitleInfo entries are user applications with no reference to child titles, so it's safe to free them first with titleFreeTitleInfo(). */
if (dup_parent) titleFreeTitleInfo(&(title_info_dup->parent));
/* Free previous and next linked lists (if duplicated). */ /* Free previous and next linked lists (if duplicated). */
/* We need to take care of not freeing the parent linked list, either because we may have already freed it, or because it may have been passed as an argument. */ /* We need to take care of not freeing the linked lists right away, either because we may have already freed them, or because they may have been passed as arguments. */
/* Furthermore, both the next pointer from the previous sibling and the previous pointer from the next sibling reference our current duplicated entry. */ /* Furthermore, both the next pointer from the previous sibling and the previous pointer from the next sibling reference our current duplicated entry. */
/* To avoid issues, we'll just clear all linked list pointers. */ /* To avoid issues, we'll just clear all linked list pointers. */
TITLE_FREE_DUPLICATED_LINKED_LIST(previous); TITLE_FREE_DUPLICATED_LINKED_LIST(previous);
TITLE_FREE_DUPLICATED_LINKED_LIST(next); TITLE_FREE_DUPLICATED_LINKED_LIST(next);
/* Free allocated buffer and update return pointer. */
free(title_info_dup); free(title_info_dup);
title_info_dup = NULL; title_info_dup = NULL;
} }