mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-12-23 00:52:10 +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:
parent
0f1055c84e
commit
3aae84a025
6 changed files with 357 additions and 204 deletions
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* 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).
|
||||
*
|
||||
|
@ -37,7 +37,7 @@ typedef void (*MenuElementOptionFunction)(u32 idx);
|
|||
typedef struct {
|
||||
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.
|
||||
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;
|
||||
|
||||
typedef bool (*MenuElementFunction)(void);
|
||||
|
@ -70,10 +70,21 @@ typedef struct
|
|||
|
||||
/* 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 consoleRefresh(void);
|
||||
|
||||
static u32 menuGetElementCount(const Menu *menu);
|
||||
|
||||
void freeStorageList(void);
|
||||
void updateStorageList(void);
|
||||
|
||||
NX_INLINE bool useUsbHost(void);
|
||||
|
||||
static bool waitForGameCard(void);
|
||||
static bool waitForUsb(void);
|
||||
|
||||
|
@ -89,7 +100,6 @@ static bool saveGameCardIdSet(void);
|
|||
static bool saveGameCardImage(void);
|
||||
static bool saveConsoleLafwBlob(void);
|
||||
|
||||
static void changeStorageOption(u32 idx);
|
||||
static void changeKeyAreaOption(u32 idx);
|
||||
static void changeCertificateOption(u32 idx);
|
||||
static void changeTrimOption(u32 idx);
|
||||
|
@ -100,10 +110,9 @@ static void write_thread_func(void *arg);
|
|||
|
||||
/* 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 const char *g_xciOptions[] = { "no", "yes", NULL };
|
||||
static char *g_xciOptions[] = { "no", "yes", NULL };
|
||||
|
||||
static MenuElement *g_xciMenuElements[] = {
|
||||
&(MenuElement){
|
||||
|
@ -162,6 +171,12 @@ static Menu g_xciMenu = {
|
|||
.elements = g_xciMenuElements
|
||||
};
|
||||
|
||||
static MenuElementOption g_storageMenuElementOption = {
|
||||
.selected = 0,
|
||||
.options_func = NULL,
|
||||
.options = NULL
|
||||
};
|
||||
|
||||
static MenuElement *g_rootMenuElements[] = {
|
||||
&(MenuElement){
|
||||
.str = "dump gamecard xci",
|
||||
|
@ -203,11 +218,7 @@ static MenuElement *g_rootMenuElements[] = {
|
|||
.str = "output storage",
|
||||
.child_menu = NULL,
|
||||
.task_func = NULL,
|
||||
.element_options = &(MenuElementOption){
|
||||
.selected = 0,
|
||||
.options_func = &changeStorageOption,
|
||||
.options = g_storageOptions
|
||||
}
|
||||
.element_options = &g_storageMenuElementOption
|
||||
},
|
||||
NULL
|
||||
};
|
||||
|
@ -219,40 +230,16 @@ static Menu g_rootMenu = {
|
|||
.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 char path[FS_MAX_PATH] = {0}, txt_info[FS_MAX_PATH] = {0};
|
||||
|
||||
static bool g_appletStatus = true;
|
||||
|
||||
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 UsbHsFsDevice *g_umsDevices = NULL;
|
||||
static u32 g_umsDeviceCount = 0;
|
||||
static char **g_storageOptions = NULL;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
@ -275,12 +262,13 @@ int main(int argc, char *argv[])
|
|||
|
||||
consoleInit(NULL);
|
||||
|
||||
chdir(DEVOPTAB_SDMC_DEVICE GAMECARD_PATH);
|
||||
updateStorageList();
|
||||
|
||||
while(appletMainLoop())
|
||||
{
|
||||
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);
|
||||
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];
|
||||
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)
|
||||
{
|
||||
printf(": ");
|
||||
if (cur_options->selected > 0) printf("< ");
|
||||
printf("%s", cur_options->options[cur_options->selected]);
|
||||
if (cur_options->options[cur_options->selected + 1]) printf(" >");
|
||||
consolePrint(": ");
|
||||
if (cur_options->selected > 0) consolePrint("< ");
|
||||
consolePrint("%s", cur_options->options[cur_options->selected]);
|
||||
if (cur_options->options[cur_options->selected + 1]) consolePrint(" >");
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
consolePrint("\n");
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
consoleUpdate(NULL);
|
||||
consolePrint("\n");
|
||||
consoleRefresh();
|
||||
|
||||
bool data_update = false;
|
||||
u64 btn_down = 0, btn_held = 0;
|
||||
|
||||
while((g_appletStatus = appletMainLoop()))
|
||||
{
|
||||
utilsScanPads();
|
||||
btn_down = utilsGetButtonsDown();
|
||||
btn_held = utilsGetButtonsHeld();
|
||||
if (btn_down || btn_held) break;
|
||||
|
||||
if (umsIsDeviceInfoUpdated())
|
||||
{
|
||||
updateStorageList();
|
||||
data_update = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_appletStatus) break;
|
||||
|
||||
if (data_update) continue;
|
||||
|
||||
if (btn_down & HidNpadButton_A)
|
||||
{
|
||||
Menu *child_menu = (Menu*)selected_element->child_menu;
|
||||
|
@ -340,14 +339,20 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
|
||||
/* Wait for USB session. */
|
||||
if (g_useUsbHost && !waitForUsb()) break;
|
||||
if (useUsbHost() && !waitForUsb()) break;
|
||||
|
||||
/* Generate dump text. */
|
||||
generateDumpTxt();
|
||||
|
||||
/* Run task. */
|
||||
utilsSetLongRunningProcessState(true);
|
||||
if (selected_element->task_func()) saveDumpTxt();
|
||||
|
||||
if (selected_element->task_func())
|
||||
{
|
||||
saveDumpTxt();
|
||||
if (!useUsbHost()) updateStorageList(); // update free space
|
||||
}
|
||||
|
||||
utilsSetLongRunningProcessState(false);
|
||||
|
||||
/* Display prompt. */
|
||||
|
@ -411,12 +416,20 @@ int main(int argc, char *argv[])
|
|||
|
||||
cur_menu = cur_menu->parent;
|
||||
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
|
||||
}
|
||||
|
||||
out:
|
||||
freeStorageList();
|
||||
|
||||
utilsCloseResources();
|
||||
|
||||
consoleExit(NULL);
|
||||
|
@ -424,13 +437,52 @@ out:
|
|||
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, ...)
|
||||
{
|
||||
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);
|
||||
fflush(stdout);
|
||||
consoleUpdate(NULL);
|
||||
mutexUnlock(&g_conMutex);
|
||||
}
|
||||
|
||||
static u32 menuGetElementCount(const Menu *menu)
|
||||
|
@ -442,10 +494,105 @@ static u32 menuGetElementCount(const Menu *menu)
|
|||
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)
|
||||
{
|
||||
consoleClear();
|
||||
consolePrint("waiting for gamecard...\n");
|
||||
consoleRefresh();
|
||||
|
||||
u8 status = GameCardStatus_NotInserted;
|
||||
|
||||
|
@ -487,6 +634,7 @@ static bool waitForUsb(void)
|
|||
if (usbIsReady()) return true;
|
||||
|
||||
consolePrint("waiting for usb session...\n");
|
||||
consoleRefresh();
|
||||
|
||||
while((g_appletStatus = appletMainLoop()))
|
||||
{
|
||||
|
@ -526,7 +674,7 @@ static bool saveFileData(const char *path, void *data, size_t data_size)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (g_useUsbHost)
|
||||
if (useUsbHost())
|
||||
{
|
||||
if (!usbSendFilePropertiesCommon(data_size, path))
|
||||
{
|
||||
|
@ -572,20 +720,36 @@ static bool saveDumpTxt(void)
|
|||
|
||||
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");
|
||||
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);
|
||||
|
||||
if (output)
|
||||
{
|
||||
snprintf(path, MAX_ELEMENTS(path), "%s", output);
|
||||
utilsCreateDirectoryTree(path, false);
|
||||
} else {
|
||||
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;
|
||||
|
||||
printf("successfully saved specific data as \"%s\"\n", filename);
|
||||
consolePrint("successfully saved specific data as \"%s\"\n", filename);
|
||||
success = true;
|
||||
|
||||
end:
|
||||
|
@ -660,7 +824,7 @@ static bool saveGameCardCertificate(void)
|
|||
|
||||
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;
|
||||
|
||||
end:
|
||||
|
@ -686,7 +850,7 @@ static bool saveGameCardInitialData(void)
|
|||
|
||||
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;
|
||||
|
||||
end:
|
||||
|
@ -712,7 +876,7 @@ static bool saveGameCardIdSet(void)
|
|||
|
||||
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;
|
||||
|
||||
end:
|
||||
|
@ -723,7 +887,7 @@ end:
|
|||
|
||||
static bool saveGameCardImage(void)
|
||||
{
|
||||
u64 gc_size = 0;
|
||||
u64 gc_size = 0, free_space = 0;
|
||||
|
||||
u32 key_area_crc = 0;
|
||||
GameCardKeyArea gc_key_area = {0};
|
||||
|
@ -776,7 +940,7 @@ static bool saveGameCardImage(void)
|
|||
filename = generateOutputFileName(path);
|
||||
if (!filename) goto end;
|
||||
|
||||
if (g_useUsbHost)
|
||||
if (useUsbHost())
|
||||
{
|
||||
if (!usbSendFilePropertiesCommon(gc_size, filename))
|
||||
{
|
||||
|
@ -790,12 +954,33 @@ static bool saveGameCardImage(void)
|
|||
goto end;
|
||||
}
|
||||
} else {
|
||||
if (gc_size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(filename))
|
||||
if (!utilsGetFileSystemStatsByPath(path, NULL, &free_space))
|
||||
{
|
||||
consolePrint("failed to create concatenation file for \"%s\"!\n", filename);
|
||||
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))
|
||||
{
|
||||
consolePrint("failed to create concatenation file for \"%s\"!\n", filename);
|
||||
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");
|
||||
if (!shared_data.fp)
|
||||
{
|
||||
|
@ -822,6 +1007,7 @@ static bool saveGameCardImage(void)
|
|||
bool btn_cancel_cur_state = false, btn_cancel_prev_state = false;
|
||||
|
||||
consolePrint("hold b to cancel\n\n");
|
||||
consoleRefresh();
|
||||
|
||||
start = time(NULL);
|
||||
|
||||
|
@ -865,8 +1051,8 @@ static bool saveGameCardImage(void)
|
|||
prev_time = ts.tm_sec;
|
||||
prev_size = size;
|
||||
|
||||
printf("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, shared_data.total_size, percent, (now - start));
|
||||
consoleUpdate(NULL);
|
||||
consolePrint("%lu / %lu (%u%%) | Time elapsed: %lu\n", size, shared_data.total_size, percent, (now - start));
|
||||
consoleRefresh();
|
||||
}
|
||||
|
||||
start = (time(NULL) - start);
|
||||
|
@ -889,25 +1075,27 @@ static bool saveGameCardImage(void)
|
|||
goto end;
|
||||
}
|
||||
|
||||
printf("process completed in %lu seconds\n", start);
|
||||
consolePrint("process completed in %lu seconds\n", start);
|
||||
success = true;
|
||||
|
||||
if (g_calcCrc)
|
||||
{
|
||||
if (g_appendKeyArea) printf("key area crc: %08X | ", key_area_crc);
|
||||
printf("xci crc: %08X", shared_data.xci_crc);
|
||||
if (g_appendKeyArea) printf(" | xci crc (with key area): %08X", shared_data.full_xci_crc);
|
||||
printf("\n");
|
||||
if (g_appendKeyArea) consolePrint("key area crc: %08X | ", key_area_crc);
|
||||
consolePrint("xci crc: %08X", shared_data.xci_crc);
|
||||
if (g_appendKeyArea) consolePrint(" | xci crc (with key area): %08X", shared_data.full_xci_crc);
|
||||
consolePrint("\n");
|
||||
}
|
||||
|
||||
end:
|
||||
consoleRefresh();
|
||||
|
||||
if (shared_data.fp)
|
||||
{
|
||||
fclose(shared_data.fp);
|
||||
shared_data.fp = NULL;
|
||||
}
|
||||
|
||||
if (!success && !g_useUsbHost) utilsRemoveConcatenationFile(filename);
|
||||
if (!success && !useUsbHost()) utilsRemoveConcatenationFile(filename);
|
||||
|
||||
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;
|
||||
|
||||
printf("successfully saved lafw blob as \"%s\"\n", path);
|
||||
consolePrint("successfully saved lafw blob as \"%s\"\n", path);
|
||||
success = true;
|
||||
|
||||
end:
|
||||
return success;
|
||||
}
|
||||
|
||||
static void changeStorageOption(u32 idx)
|
||||
{
|
||||
g_useUsbHost = (idx > 0);
|
||||
}
|
||||
|
||||
static void changeKeyAreaOption(u32 idx)
|
||||
{
|
||||
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->transfer_cancelled && g_useUsbHost) usbCancelFileTransfer();
|
||||
if (shared_data->transfer_cancelled && useUsbHost()) usbCancelFileTransfer();
|
||||
mutexUnlock(&g_fileMutex);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Write current file data chunk */
|
||||
if (g_useUsbHost)
|
||||
if (useUsbHost())
|
||||
{
|
||||
shared_data->write_error = !usbSendFileData(shared_data->data, shared_data->data_size);
|
||||
} else {
|
||||
|
|
|
@ -48,7 +48,8 @@ typedef struct
|
|||
static const char *dump_type_strings[] = {
|
||||
"dump base application",
|
||||
"dump update",
|
||||
"dump dlc"
|
||||
"dump dlc",
|
||||
"dump dlc update"
|
||||
};
|
||||
|
||||
static const u32 dump_type_strings_count = MAX_ELEMENTS(dump_type_strings);
|
||||
|
@ -953,7 +954,8 @@ static void nspDump(TitleInfo *title_info)
|
|||
consoleClear();
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -1142,8 +1144,6 @@ int main(int argc, char *argv[])
|
|||
|
||||
ums_devices = umsGetDevices(&ums_device_count);
|
||||
|
||||
utilsSleep(1);
|
||||
|
||||
while((applet_status = appletMainLoop()))
|
||||
{
|
||||
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" : \
|
||||
(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));
|
||||
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, \
|
||||
|
@ -1341,12 +1342,12 @@ int main(int argc, char *argv[])
|
|||
} 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))
|
||||
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;
|
||||
} 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_idx = 1;
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ typedef enum {
|
|||
NcaKeyGeneration_Since1210NUP = 12, ///< 12.1.0.
|
||||
NcaKeyGeneration_Since1300NUP = 13, ///< 13.0.0 - 13.2.1.
|
||||
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_Max = 32
|
||||
} NcaKeyGeneration;
|
||||
|
|
|
@ -46,10 +46,10 @@ typedef struct {
|
|||
} TitleApplicationMetadata;
|
||||
|
||||
/// Generated using ncm calls.
|
||||
/// User applications: the parent pointer is always unused. 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.
|
||||
/// Add-on contents: the parent pointer always references the first corresponding user application. 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.
|
||||
/// User applications: the previous/next pointers reference other user applications with the same ID.
|
||||
/// Patches: the previous/next pointers reference other patches with the same ID.
|
||||
/// Add-on contents: the previous/next pointers reference sibling add-on contents.
|
||||
/// 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 {
|
||||
u8 storage_id; ///< NcmStorageId.
|
||||
NcmContentMetaKey meta_key; ///< Used with ncm calls.
|
||||
|
@ -59,15 +59,16 @@ typedef struct _TitleInfo {
|
|||
u64 size; ///< Total title size.
|
||||
char size_str[32]; ///< Total title size string.
|
||||
TitleApplicationMetadata *app_metadata; ///< User application metadata.
|
||||
struct _TitleInfo *parent, *previous, *next; ///< Linked lists.
|
||||
struct _TitleInfo *previous, *next; ///< Linked lists.
|
||||
} TitleInfo;
|
||||
|
||||
/// 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 {
|
||||
TitleInfo *app_info; ///< Pointer to a TitleInfo element holding info 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 *aoc_info; ///< Pointer to a TitleInfo element holding info for the first detected add-on content 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 for the first detected patch 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;
|
||||
|
||||
typedef enum {
|
||||
|
@ -250,6 +251,13 @@ NX_INLINE bool titleCheckIfDataPatchIdBelongsToApplicationId(u64 app_id, u64 dat
|
|||
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)
|
||||
{
|
||||
if (!info || !info->content_count || !info->content_infos || content_type > NcmContentType_DeltaFragment) return 0;
|
||||
|
|
|
@ -192,7 +192,7 @@ bool cnmtInitializeContext(ContentMetaContext *out, NcaContext *nca_ctx)
|
|||
case NcmContentMetaType_DataPatch:
|
||||
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);
|
||||
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;
|
||||
default:
|
||||
invalid_ext_header_size = (out->packaged_header->extended_header_size != 0);
|
||||
|
|
|
@ -238,14 +238,14 @@ static const TitleSystemEntry g_systemTitles[] = {
|
|||
{ 0x0100000000001007, "playerSelect" },
|
||||
{ 0x0100000000001008, "swkbd" },
|
||||
{ 0x0100000000001009, "miiEdit" },
|
||||
{ 0x010000000000100A, "web" },
|
||||
{ 0x010000000000100B, "shop" },
|
||||
{ 0x010000000000100A, "LibAppletWeb" },
|
||||
{ 0x010000000000100B, "LibAppletShop" },
|
||||
{ 0x010000000000100C, "overlayDisp" },
|
||||
{ 0x010000000000100D, "photoViewer" },
|
||||
{ 0x010000000000100E, "set" },
|
||||
{ 0x010000000000100F, "offlineWeb" },
|
||||
{ 0x0100000000001010, "loginShare" },
|
||||
{ 0x0100000000001011, "wifiWebAuth" },
|
||||
{ 0x010000000000100F, "LibAppletOff" },
|
||||
{ 0x0100000000001010, "LibAppletLns" },
|
||||
{ 0x0100000000001011, "LibAppletAuth" },
|
||||
{ 0x0100000000001012, "starter" },
|
||||
{ 0x0100000000001013, "myPage" },
|
||||
{ 0x0100000000001014, "PlayReport" },
|
||||
|
@ -524,7 +524,7 @@ static bool titleRefreshGameCardTitleInfo(void);
|
|||
static bool titleIsUserApplicationContentAvailable(u64 app_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 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -770,15 +770,12 @@ void titleFreeTitleInfo(TitleInfo **info)
|
|||
/* Free content infos. */
|
||||
if (ptr->content_infos) free(ptr->content_infos);
|
||||
|
||||
/* Free parent linked list. */
|
||||
titleFreeTitleInfo(&(ptr->parent));
|
||||
|
||||
/* Free previous sibling(s). */
|
||||
tmp1 = ptr->previous;
|
||||
while(tmp1)
|
||||
{
|
||||
tmp2 = tmp1->previous;
|
||||
tmp1->parent = tmp1->previous = tmp1->next = NULL;
|
||||
tmp1->previous = tmp1->next = NULL;
|
||||
titleFreeTitleInfo(&tmp1);
|
||||
tmp1 = tmp2;
|
||||
}
|
||||
|
@ -788,7 +785,7 @@ void titleFreeTitleInfo(TitleInfo **info)
|
|||
while(tmp1)
|
||||
{
|
||||
tmp2 = tmp1->next;
|
||||
tmp1->parent = tmp1->previous = tmp1->next = NULL;
|
||||
tmp1->previous = tmp1->next = NULL;
|
||||
titleFreeTitleInfo(&tmp1);
|
||||
tmp1 = tmp2;
|
||||
}
|
||||
|
@ -809,36 +806,30 @@ bool titleGetUserApplicationData(u64 app_id, TitleUserApplicationData *out)
|
|||
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. */
|
||||
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. */
|
||||
app_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, app_id);
|
||||
if (app_info)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
TITLE_ALLOCATE_USER_APP_DATA(app, "user application", break);
|
||||
|
||||
/* Get info for the first patch title. */
|
||||
patch_info = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, titleGetPatchIdByApplicationId(app_id));
|
||||
if (patch_info)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
TITLE_ALLOCATE_USER_APP_DATA(patch, "patch", 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++)
|
||||
{
|
||||
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++)
|
||||
{
|
||||
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;
|
||||
break;
|
||||
} else
|
||||
if (title_info->meta_key.type == NcmContentMetaType_DataPatch && titleCheckIfDataPatchIdBelongsToApplicationId(app_id, title_info->meta_key.id))
|
||||
{
|
||||
aoc_patch_info = title_info;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (aoc_info) 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 (aoc_info)
|
||||
{
|
||||
out->aoc_info = titleDuplicateTitleInfo(aoc_info, out->app_info, NULL, NULL);
|
||||
if (!out->aoc_info)
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to duplicate add-on content info for %016lX!", app_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (error) break;
|
||||
|
||||
#undef TITLE_ALLOCATE_USER_APP_DATA
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
|
@ -884,32 +880,17 @@ void titleFreeUserApplicationData(TitleUserApplicationData *user_app_data)
|
|||
{
|
||||
if (!user_app_data) return;
|
||||
|
||||
TitleInfo *tmp = NULL;
|
||||
|
||||
/* Free user application 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. */
|
||||
titleFreeTitleInfo(&(user_app_data->patch_info));
|
||||
|
||||
/* Free add-on content info. */
|
||||
titleFreeTitleInfo(&(user_app_data->aoc_info));
|
||||
|
||||
/* Free add-on content patch info. */
|
||||
titleFreeTitleInfo(&(user_app_data->aoc_patch_info));
|
||||
}
|
||||
|
||||
bool titleAreOrphanTitlesAvailable(void)
|
||||
|
@ -942,7 +923,7 @@ TitleInfo **titleGetOrphanTitles(u32 *out_count)
|
|||
/* Duplicate orphan title info entries. */
|
||||
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])
|
||||
{
|
||||
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];
|
||||
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 (child_info->meta_key.type < NcmContentMetaType_Application || (child_info->meta_key.type > NcmContentMetaType_AddOnContent && \
|
||||
|
@ -2081,33 +2062,21 @@ static void titleUpdateTitleInfoLinkedLists(void)
|
|||
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. */
|
||||
/* Patch, AddOnContent: retrieve pointer to the first parent user application entry and set it as the parent title. */
|
||||
/* DataPatch: retrieve pointer to the first parent add-on content 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. */
|
||||
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) : \
|
||||
titleGetAddOnContentIdByDataPatchId(child_info->meta_key.id)));
|
||||
titleGetApplicationIdByDataPatchId(child_info->meta_key.id)));
|
||||
|
||||
child_info->parent = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, app_id);
|
||||
|
||||
/* Set pointer to application metadata. */
|
||||
if (child_info->parent && !child_info->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 {
|
||||
/* We may be dealing with a parent add-on content with a yet-to-be-assigned application metadata pointer. */
|
||||
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)
|
||||
TitleInfo *parent = _titleGetInfoFromStorageByTitleId(NcmStorageId_Any, app_id);
|
||||
if (parent)
|
||||
{
|
||||
/* Set pointer to application metadata. */
|
||||
child_info->app_metadata = parent->app_metadata;
|
||||
} else {
|
||||
/* Add orphan title info entry since we have no application metadata. */
|
||||
titleAddOrphanTitleInfoEntry(child_info);
|
||||
continue;
|
||||
}
|
||||
|
@ -2134,8 +2103,9 @@ static void titleUpdateTitleInfoLinkedLists(void)
|
|||
if (!prev_info) continue;
|
||||
|
||||
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_AddOnContent && titleCheckIfAddOnContentIdsAreSiblings(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_DataPatch && titleCheckIfDataPatchIdsAreSiblings(prev_info->meta_key.id, child_info->meta_key.id))))
|
||||
{
|
||||
prev_info->next = child_info;
|
||||
child_info->previous = prev_info;
|
||||
|
@ -2404,7 +2374,7 @@ static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id)
|
|||
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 || \
|
||||
(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;
|
||||
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. */
|
||||
title_info_dup = calloc(1, sizeof(TitleInfo));
|
||||
|
@ -2428,7 +2398,7 @@ static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *pare
|
|||
|
||||
/* Copy TitleInfo data. */
|
||||
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. */
|
||||
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. */
|
||||
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 (elem) { \
|
||||
title_info_dup->elem = elem; \
|
||||
} 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; \
|
||||
dup_##elem = true; \
|
||||
} \
|
||||
|
@ -2460,7 +2430,7 @@ static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *pare
|
|||
tmp1 = title_info_dup->elem; \
|
||||
while(tmp1) { \
|
||||
tmp2 = tmp1->elem; \
|
||||
tmp1->parent = tmp1->previous = tmp1->next = NULL; \
|
||||
tmp1->previous = tmp1->next = NULL; \
|
||||
titleFreeTitleInfo(&tmp1); \
|
||||
tmp1 = tmp2; \
|
||||
} \
|
||||
|
@ -2469,14 +2439,8 @@ static TitleInfo *titleDuplicateTitleInfo(TitleInfo *title_info, TitleInfo *pare
|
|||
/* 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. */
|
||||
/* 2) Pointers passed into this function take precedence before actual data duplication. */
|
||||
|
||||
/* 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(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);
|
||||
TITLE_DUPLICATE_LINKED_LIST(previous, NULL, title_info_dup);
|
||||
TITLE_DUPLICATE_LINKED_LIST(next, title_info_dup, NULL);
|
||||
|
||||
/* Update flag. */
|
||||
success = true;
|
||||
|
@ -2490,17 +2454,14 @@ end:
|
|||
|
||||
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). */
|
||||
/* 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. */
|
||||
/* To avoid issues, we'll just clear all linked list pointers. */
|
||||
TITLE_FREE_DUPLICATED_LINKED_LIST(previous);
|
||||
TITLE_FREE_DUPLICATED_LINKED_LIST(next);
|
||||
|
||||
/* Free allocated buffer and update return pointer. */
|
||||
free(title_info_dup);
|
||||
title_info_dup = NULL;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue