1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-22 10:16: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
*
* 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 {

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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;
}