From ace4732fdafa7ef4b50f1d547b9523b7aca766e9 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Thu, 13 Aug 2020 22:31:02 -0400 Subject: [PATCH] Functions to generate gamecard/title filenames + fix CRC32 calculation. Updated the threaded gamecard dumper to reflect these changes. --- code_templates/threaded_usb_gc_dumper.c | 80 +++++++++-- source/crc32_fast.c | 12 +- source/main.c | 80 +++++++++-- source/title.c | 176 ++++++++++++++++++++++-- source/title.h | 25 ++++ 5 files changed, 336 insertions(+), 37 deletions(-) diff --git a/code_templates/threaded_usb_gc_dumper.c b/code_templates/threaded_usb_gc_dumper.c index 16d8ac3..0ad3afe 100644 --- a/code_templates/threaded_usb_gc_dumper.c +++ b/code_templates/threaded_usb_gc_dumper.c @@ -21,6 +21,8 @@ #include "utils.h" #include "gamecard.h" #include "usb.h" +#include "title.h" +#include "crc32_fast.h" #define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE @@ -58,6 +60,7 @@ typedef struct bool read_error; bool write_error; bool transfer_cancelled; + u32 xci_crc, full_xci_crc; } ThreadSharedData; /* Function prototypes. */ @@ -73,13 +76,14 @@ static bool sendGameCardImageViaUsb(void); static void changeKeyAreaOption(u32 idx); static void changeCertificateOption(u32 idx); static void changeTrimOption(u32 idx); +static void changeCrcOption(u32 idx); static int read_thread_func(void *arg); static int write_thread_func(void *arg); /* Global variables. */ -static bool g_appendKeyArea = false, g_keepCertificate = false, g_trimDump = false; +static bool g_appendKeyArea = false, g_keepCertificate = false, g_trimDump = false, g_calcCrc = false; static const char *g_xciOptions[] = { "no", "yes", NULL }; @@ -120,6 +124,16 @@ static MenuElement *g_xciMenuElements[] = { .options = g_xciOptions } }, + &(MenuElement){ + .str = "calculate crc32", + .child_menu = NULL, + .task_func = NULL, + .element_options = &(MenuElementOption){ + .selected = 0, + .options_func = &changeCrcOption, + .options = g_xciOptions + } + }, NULL }; @@ -388,16 +402,22 @@ static bool sendGameCardKeyAreaViaUsb(void) GameCardKeyArea gc_key_area = {0}; bool success = false; + u32 crc = 0; + char *filename = titleGenerateGameCardFileName(TitleFileNameConvention_Full, TitleFileNameIllegalCharReplaceType_IllegalFsChars); - if (!dumpGameCardKeyArea(&gc_key_area)) goto end; + if (!dumpGameCardKeyArea(&gc_key_area) || !filename) goto end; + + crc32FastCalculate(&gc_key_area, sizeof(GameCardKeyArea), &crc); + snprintf(path, MAX_ELEMENTS(path), "%s (Key Area) (%08X).bin", filename, crc); - sprintf(path, "card_key_area_%016lX.bin", gc_key_area.initial_data.key_source.package_id); if (!sendFileData(path, &gc_key_area, sizeof(GameCardKeyArea))) goto end; - consolePrint("successfully sent key area as \"%s\"\n", path); + printf("successfully sent key area as \"%s\"\n", path); success = true; end: + if (filename) free(filename); + utilsChangeHomeButtonBlockStatus(false); consolePrint("press any button to continue"); @@ -413,10 +433,11 @@ static bool sendGameCardCertificateViaUsb(void) utilsChangeHomeButtonBlockStatus(true); FsGameCardCertificate gc_cert = {0}; - char device_id_str[0x21] = {0}; bool success = false; + u32 crc = 0; + char *filename = titleGenerateGameCardFileName(TitleFileNameConvention_Full, TitleFileNameIllegalCharReplaceType_IllegalFsChars); - if (!gamecardGetCertificate(&gc_cert)) + if (!gamecardGetCertificate(&gc_cert) || !filename) { consolePrint("failed to get gamecard certificate\n"); goto end; @@ -424,14 +445,17 @@ static bool sendGameCardCertificateViaUsb(void) consolePrint("get gamecard certificate ok\n"); - utilsGenerateHexStringFromData(device_id_str, 0x21, gc_cert.device_id, 0x10); - sprintf(path, "card_certificate_%s.bin", device_id_str); + crc32FastCalculate(&gc_cert, sizeof(FsGameCardCertificate), &crc); + snprintf(path, MAX_ELEMENTS(path), "%s (Certificate) (%08X).bin", filename, crc); + if (!sendFileData(path, &gc_cert, sizeof(FsGameCardCertificate))) goto end; - consolePrint("successfully sent certificate as \"%s\"\n", path); + printf("successfully sent certificate as \"%s\"\n", path); success = true; end: + if (filename) free(filename); + utilsChangeHomeButtonBlockStatus(false); consolePrint("press any button to continue"); @@ -447,15 +471,25 @@ static bool sendGameCardImageViaUsb(void) utilsChangeHomeButtonBlockStatus(true); u64 gc_size = 0; + u32 key_area_crc = 0; GameCardKeyArea gc_key_area = {0}; ThreadSharedData shared_data = {0}; thrd_t read_thread, write_thread; + char *filename = NULL; + bool success = false; consolePrint("gamecard image dump\nappend key area: %s | keep certificate: %s | trim dump: %s\n\n", g_appendKeyArea ? "yes" : "no", g_keepCertificate ? "yes" : "no", g_trimDump ? "yes" : "no"); + filename = titleGenerateGameCardFileName(TitleFileNameConvention_Full, TitleFileNameIllegalCharReplaceType_IllegalFsChars); + if (!filename) + { + consolePrint("failed to generate gamecard filename!\n"); + goto end; + } + shared_data.data = usbAllocatePageAlignedBuffer(BLOCK_SIZE); if (!shared_data.data) { @@ -477,10 +511,12 @@ static bool sendGameCardImageViaUsb(void) { gc_size += sizeof(GameCardKeyArea); if (!dumpGameCardKeyArea(&gc_key_area)) goto end; + if (g_calcCrc) crc32FastCalculate(&gc_key_area, sizeof(GameCardKeyArea), &key_area_crc); + shared_data.full_xci_crc = key_area_crc; consolePrint("gamecard size (with key area): 0x%lX\n", gc_size); } - sprintf(path, "gamecard (%s) (%s) (%s).xci", g_appendKeyArea ? "keyarea" : "keyarealess", g_keepCertificate ? "cert" : "certless", g_trimDump ? "trimmed" : "untrimmed"); + snprintf(path, MAX_ELEMENTS(path), "%s (%s) (%s) (%s).xci", filename, g_appendKeyArea ? "keyarea" : "keyarealess", g_keepCertificate ? "cert" : "certless", g_trimDump ? "trimmed" : "untrimmed"); if (!usbSendFileProperties(gc_size, path)) { consolePrint("failed to send file properties for \"%s\"!\n", path); @@ -571,12 +607,22 @@ static bool sendGameCardImageViaUsb(void) goto end; } - consolePrint("process completed in %lu seconds\n", start); + printf("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"); + } + end: if (shared_data.data) free(shared_data.data); + if (filename) free(filename); + utilsChangeHomeButtonBlockStatus(false); consolePrint("press any button to continue"); @@ -600,6 +646,11 @@ static void changeTrimOption(u32 idx) g_trimDump = (idx > 0); } +static void changeCrcOption(u32 idx) +{ + g_calcCrc = (idx > 0); +} + static int read_thread_func(void *arg) { ThreadSharedData *shared_data = (ThreadSharedData*)arg; @@ -638,6 +689,13 @@ static int read_thread_func(void *arg) /* Remove certificate */ if (!g_keepCertificate && offset == 0) memset(buf + GAMECARD_CERTIFICATE_OFFSET, 0xFF, sizeof(FsGameCardCertificate)); + /* Update checksum */ + if (g_calcCrc) + { + crc32FastCalculate(buf, blksize, &(shared_data->xci_crc)); + if (g_appendKeyArea) crc32FastCalculate(buf, blksize, &(shared_data->full_xci_crc)); + } + /* Wait until the previous data chunk has been written */ mutexLock(&g_fileMutex); diff --git a/source/crc32_fast.c b/source/crc32_fast.c index 7bf6ba4..201e29a 100644 --- a/source/crc32_fast.c +++ b/source/crc32_fast.c @@ -35,10 +35,10 @@ static void crc32FastInitializeTables(u32 *table, u32 *wtable) for(u32 k = 0; k < 4; ++k) { - for(u32 w = 0, i = 0; i < 0x100; ++i) + for(u32 w, i = 0; i < 0x100; ++i) { - for(u32 j = 0; j < 4; ++j) w = (table[(u8)(j == k ? (w ^ i) : w)] ^ w >> 8); - wtable[i + (k << 8)] = (w ^ (k ? wtable[0] : 0)); + for(u32 j = w = 0; j < 4; ++j) w = (table[(u8)(j == k ? (w ^ i) : w)] ^ w >> 8); + wtable[(k << 8) + i] = (w ^ (k ? wtable[0] : 0)); } } } @@ -47,16 +47,16 @@ void crc32FastCalculate(const void *data, u64 n_bytes, u32 *crc) { if (!data || !n_bytes || !crc) return; - static u32 table[0x100] = {0}, wtable[0x100 * 4] = {0}; + static u32 table[0x100] = {0}, wtable[0x400] = {0}; u64 n_accum = (n_bytes / 4); if (!*table) crc32FastInitializeTables(table, wtable); for(u64 i = 0; i < n_accum; ++i) { - u32 a = (*crc ^ ((u32*)data)[i]); + u32 a = (*crc ^ ((const u32*)data)[i]); for(u32 j = *crc = 0; j < 4; ++j) *crc ^= wtable[(j << 8) + (u8)(a >> 8 * j)]; } - for(u64 i = (n_accum * 4); i < n_bytes; ++i) *crc = (table[(u8)*crc ^ ((u8*)data)[i]] ^ *crc >> 8); + for(u64 i = (n_accum * 4); i < n_bytes; ++i) *crc = (table[(u8)*crc ^ ((const u8*)data)[i]] ^ *crc >> 8); } diff --git a/source/main.c b/source/main.c index 16d8ac3..0ad3afe 100644 --- a/source/main.c +++ b/source/main.c @@ -21,6 +21,8 @@ #include "utils.h" #include "gamecard.h" #include "usb.h" +#include "title.h" +#include "crc32_fast.h" #define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE @@ -58,6 +60,7 @@ typedef struct bool read_error; bool write_error; bool transfer_cancelled; + u32 xci_crc, full_xci_crc; } ThreadSharedData; /* Function prototypes. */ @@ -73,13 +76,14 @@ static bool sendGameCardImageViaUsb(void); static void changeKeyAreaOption(u32 idx); static void changeCertificateOption(u32 idx); static void changeTrimOption(u32 idx); +static void changeCrcOption(u32 idx); static int read_thread_func(void *arg); static int write_thread_func(void *arg); /* Global variables. */ -static bool g_appendKeyArea = false, g_keepCertificate = false, g_trimDump = false; +static bool g_appendKeyArea = false, g_keepCertificate = false, g_trimDump = false, g_calcCrc = false; static const char *g_xciOptions[] = { "no", "yes", NULL }; @@ -120,6 +124,16 @@ static MenuElement *g_xciMenuElements[] = { .options = g_xciOptions } }, + &(MenuElement){ + .str = "calculate crc32", + .child_menu = NULL, + .task_func = NULL, + .element_options = &(MenuElementOption){ + .selected = 0, + .options_func = &changeCrcOption, + .options = g_xciOptions + } + }, NULL }; @@ -388,16 +402,22 @@ static bool sendGameCardKeyAreaViaUsb(void) GameCardKeyArea gc_key_area = {0}; bool success = false; + u32 crc = 0; + char *filename = titleGenerateGameCardFileName(TitleFileNameConvention_Full, TitleFileNameIllegalCharReplaceType_IllegalFsChars); - if (!dumpGameCardKeyArea(&gc_key_area)) goto end; + if (!dumpGameCardKeyArea(&gc_key_area) || !filename) goto end; + + crc32FastCalculate(&gc_key_area, sizeof(GameCardKeyArea), &crc); + snprintf(path, MAX_ELEMENTS(path), "%s (Key Area) (%08X).bin", filename, crc); - sprintf(path, "card_key_area_%016lX.bin", gc_key_area.initial_data.key_source.package_id); if (!sendFileData(path, &gc_key_area, sizeof(GameCardKeyArea))) goto end; - consolePrint("successfully sent key area as \"%s\"\n", path); + printf("successfully sent key area as \"%s\"\n", path); success = true; end: + if (filename) free(filename); + utilsChangeHomeButtonBlockStatus(false); consolePrint("press any button to continue"); @@ -413,10 +433,11 @@ static bool sendGameCardCertificateViaUsb(void) utilsChangeHomeButtonBlockStatus(true); FsGameCardCertificate gc_cert = {0}; - char device_id_str[0x21] = {0}; bool success = false; + u32 crc = 0; + char *filename = titleGenerateGameCardFileName(TitleFileNameConvention_Full, TitleFileNameIllegalCharReplaceType_IllegalFsChars); - if (!gamecardGetCertificate(&gc_cert)) + if (!gamecardGetCertificate(&gc_cert) || !filename) { consolePrint("failed to get gamecard certificate\n"); goto end; @@ -424,14 +445,17 @@ static bool sendGameCardCertificateViaUsb(void) consolePrint("get gamecard certificate ok\n"); - utilsGenerateHexStringFromData(device_id_str, 0x21, gc_cert.device_id, 0x10); - sprintf(path, "card_certificate_%s.bin", device_id_str); + crc32FastCalculate(&gc_cert, sizeof(FsGameCardCertificate), &crc); + snprintf(path, MAX_ELEMENTS(path), "%s (Certificate) (%08X).bin", filename, crc); + if (!sendFileData(path, &gc_cert, sizeof(FsGameCardCertificate))) goto end; - consolePrint("successfully sent certificate as \"%s\"\n", path); + printf("successfully sent certificate as \"%s\"\n", path); success = true; end: + if (filename) free(filename); + utilsChangeHomeButtonBlockStatus(false); consolePrint("press any button to continue"); @@ -447,15 +471,25 @@ static bool sendGameCardImageViaUsb(void) utilsChangeHomeButtonBlockStatus(true); u64 gc_size = 0; + u32 key_area_crc = 0; GameCardKeyArea gc_key_area = {0}; ThreadSharedData shared_data = {0}; thrd_t read_thread, write_thread; + char *filename = NULL; + bool success = false; consolePrint("gamecard image dump\nappend key area: %s | keep certificate: %s | trim dump: %s\n\n", g_appendKeyArea ? "yes" : "no", g_keepCertificate ? "yes" : "no", g_trimDump ? "yes" : "no"); + filename = titleGenerateGameCardFileName(TitleFileNameConvention_Full, TitleFileNameIllegalCharReplaceType_IllegalFsChars); + if (!filename) + { + consolePrint("failed to generate gamecard filename!\n"); + goto end; + } + shared_data.data = usbAllocatePageAlignedBuffer(BLOCK_SIZE); if (!shared_data.data) { @@ -477,10 +511,12 @@ static bool sendGameCardImageViaUsb(void) { gc_size += sizeof(GameCardKeyArea); if (!dumpGameCardKeyArea(&gc_key_area)) goto end; + if (g_calcCrc) crc32FastCalculate(&gc_key_area, sizeof(GameCardKeyArea), &key_area_crc); + shared_data.full_xci_crc = key_area_crc; consolePrint("gamecard size (with key area): 0x%lX\n", gc_size); } - sprintf(path, "gamecard (%s) (%s) (%s).xci", g_appendKeyArea ? "keyarea" : "keyarealess", g_keepCertificate ? "cert" : "certless", g_trimDump ? "trimmed" : "untrimmed"); + snprintf(path, MAX_ELEMENTS(path), "%s (%s) (%s) (%s).xci", filename, g_appendKeyArea ? "keyarea" : "keyarealess", g_keepCertificate ? "cert" : "certless", g_trimDump ? "trimmed" : "untrimmed"); if (!usbSendFileProperties(gc_size, path)) { consolePrint("failed to send file properties for \"%s\"!\n", path); @@ -571,12 +607,22 @@ static bool sendGameCardImageViaUsb(void) goto end; } - consolePrint("process completed in %lu seconds\n", start); + printf("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"); + } + end: if (shared_data.data) free(shared_data.data); + if (filename) free(filename); + utilsChangeHomeButtonBlockStatus(false); consolePrint("press any button to continue"); @@ -600,6 +646,11 @@ static void changeTrimOption(u32 idx) g_trimDump = (idx > 0); } +static void changeCrcOption(u32 idx) +{ + g_calcCrc = (idx > 0); +} + static int read_thread_func(void *arg) { ThreadSharedData *shared_data = (ThreadSharedData*)arg; @@ -638,6 +689,13 @@ static int read_thread_func(void *arg) /* Remove certificate */ if (!g_keepCertificate && offset == 0) memset(buf + GAMECARD_CERTIFICATE_OFFSET, 0xFF, sizeof(FsGameCardCertificate)); + /* Update checksum */ + if (g_calcCrc) + { + crc32FastCalculate(buf, blksize, &(shared_data->xci_crc)); + if (g_appendKeyArea) crc32FastCalculate(buf, blksize, &(shared_data->full_xci_crc)); + } + /* Wait until the previous data chunk has been written */ mutexLock(&g_fileMutex); diff --git a/source/title.c b/source/title.c index ddc66d0..74e6bab 100644 --- a/source/title.c +++ b/source/title.c @@ -61,6 +61,19 @@ static const char *g_titleNcmContentTypeNames[] = { [NcmContentType_DeltaFragment + 1] = "Unknown" }; +static const char *g_titleNcmContentMetaTypeNames[] = { + [NcmContentMetaType_Unknown] = "Unknown", + [NcmContentMetaType_SystemProgram] = "SystemProgram", + [NcmContentMetaType_SystemData] = "SystemData", + [NcmContentMetaType_SystemUpdate] = "SystemUpdate", + [NcmContentMetaType_BootImagePackage] = "BootImagePackage", + [NcmContentMetaType_BootImagePackageSafe] = "BootImagePackageSafe", + [NcmContentMetaType_Application - 0x7A] = "Application", + [NcmContentMetaType_Patch - 0x7A] = "Patch", + [NcmContentMetaType_AddOnContent - 0x7A] = "AddOnContent", + [NcmContentMetaType_Delta - 0x7A] = "Delta" +}; + /* Info retrieved from https://switchbrew.org/wiki/Title_list. */ /* Titles bundled with the kernel are excluded. */ static const SystemTitleName g_systemTitles[] = { @@ -620,7 +633,7 @@ bool titleGetUserApplicationData(u64 app_id, TitleUserApplicationData *out) bool success = false; - if (!titleIsUserApplicationContentAvailable(app_id) || !out) + if (!out) { LOGFILE("Invalid parameters!"); goto end; @@ -711,12 +724,155 @@ bool titleIsGameCardInfoUpdated(void) return ret; } +char *titleGenerateFileName(const TitleInfo *title_info, u8 name_convention, u8 illegal_char_replace_type) +{ + mutexLock(&g_titleMutex); + + char *filename = NULL; + char title_name[0x400] = {0}; + TitleApplicationMetadata *app_metadata = NULL; + + if (!title_info || name_convention > TitleFileNameConvention_IdAndVersionOnly || \ + (name_convention == TitleFileNameConvention_Full && illegal_char_replace_type > TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly)) + { + LOGFILE("Invalid parameters!"); + goto end; + } + + /* Retrieve application metadata. */ + /* System titles and user applications: just retrieve the app_metadata pointer from the input TitleInfo. */ + /* Patches and add-on contents: retrieve the app_metadata pointer from the parent TitleInfo if it's available. */ + app_metadata = (title_info->meta_key.type <= NcmContentMetaType_Application ? title_info->app_metadata : (title_info->parent ? title_info->parent->app_metadata : NULL)); + + /* Generate filename for this title. */ + if (name_convention == TitleFileNameConvention_Full) + { + if (app_metadata && strlen(app_metadata->lang_entry.name)) + { + sprintf(title_name, "%s ", app_metadata->lang_entry.name); + if (illegal_char_replace_type) utilsReplaceIllegalCharacters(title_name, illegal_char_replace_type == TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly); + } + + sprintf(title_name + strlen(title_name), "[%016lX][v%u][%s]", title_info->meta_key.id, title_info->meta_key.version, titleGetNcmContentMetaTypeName(title_info->meta_key.type)); + } else + if (name_convention == TitleFileNameConvention_IdAndVersionOnly) + { + sprintf(title_name, "%016lX_v%u_%s", title_info->meta_key.id, title_info->meta_key.version, titleGetNcmContentMetaTypeName(title_info->meta_key.type)); + } + + /* Duplicate generated filename. */ + filename = strdup(title_name); + if (!filename) LOGFILE("Failed to duplicate generated filename!"); + +end: + mutexUnlock(&g_titleMutex); + + return filename; +} + +char *titleGenerateGameCardFileName(u8 name_convention, u8 illegal_char_replace_type) +{ + mutexLock(&g_titleMutex); + + size_t cur_filename_len = 0; + char *filename = NULL, *tmp_filename = NULL; + char app_name[0x400] = {0}; + + if (!g_titleGameCardAvailable || !g_titleInfo || !g_titleInfoCount || !g_titleInfoGameCardCount || g_titleInfoGameCardCount > g_titleInfoCount || \ + g_titleInfoGameCardStartIndex != (g_titleInfoCount - g_titleInfoGameCardCount) || name_convention > TitleFileNameConvention_IdAndVersionOnly || \ + (name_convention == TitleFileNameConvention_Full && illegal_char_replace_type > TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly)) + { + LOGFILE("Invalid parameters!"); + goto end; + } + + for(u32 i = g_titleInfoGameCardStartIndex; i < g_titleInfoCount; i++) + { + TitleInfo *app_info = &(g_titleInfo[i]); + u32 app_version = app_info->meta_key.version; + + if (app_info->meta_key.type != NcmContentMetaType_Application) continue; + + /* Check if the inserted gamecard holds any bundled patches for the current user application. */ + /* If so, we'll use the highest patch version available as part of the filename. */ + for(u32 j = g_titleInfoGameCardStartIndex; j < g_titleInfoCount; j++) + { + if (j == i) continue; + + TitleInfo *patch_info = &(g_titleInfo[j]); + if (patch_info->meta_key.type != NcmContentMetaType_Patch || !titleCheckIfPatchIdBelongsToApplicationId(app_info->meta_key.id, patch_info->meta_key.id) || \ + patch_info->meta_key.version <= app_version) continue; + + app_version = patch_info->meta_key.version; + } + + /* Generate current user application name. */ + *app_name = '\0'; + + if (name_convention == TitleFileNameConvention_Full) + { + if (cur_filename_len) strcat(app_name, " + "); + + if (app_info->app_metadata && strlen(app_info->app_metadata->lang_entry.name)) + { + sprintf(app_name + strlen(app_name), "%s ", app_info->app_metadata->lang_entry.name); + if (illegal_char_replace_type) utilsReplaceIllegalCharacters(app_name, illegal_char_replace_type == TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly); + } + + sprintf(app_name + strlen(app_name), "[%016lX][v%u]", app_info->meta_key.id, app_version); + } else + if (name_convention == TitleFileNameConvention_IdAndVersionOnly) + { + if (cur_filename_len) strcat(app_name, "+"); + sprintf(app_name + strlen(app_name), "%016lX_v%u", app_info->meta_key.id, app_version); + } + + /* Reallocate output buffer. */ + size_t app_name_len = strlen(app_name); + + tmp_filename = realloc(filename, (cur_filename_len + app_name_len + 1) * sizeof(char)); + if (!tmp_filename) + { + LOGFILE("Failed to reallocate filename buffer!"); + if (filename) free(filename); + filename = NULL; + goto end; + } + + filename = tmp_filename; + tmp_filename = NULL; + + /* Concatenate current user application name. */ + filename[cur_filename_len] = '\0'; + strcat(filename, app_name); + cur_filename_len += app_name_len; + } + + if (!filename) LOGFILE("Error: the inserted gamecard doesn't hold any user applications!"); + +end: + mutexUnlock(&g_titleMutex); + + /* Fallback string if any errors occur. */ + /* This function is guaranteed to fail with Kiosk / Quest gamecards, so that's why this is needed. */ + if (!filename) filename = strdup("gamecard"); + + return filename; +} + const char *titleGetNcmContentTypeName(u8 content_type) { u8 idx = (content_type > NcmContentType_DeltaFragment ? (NcmContentType_DeltaFragment + 1) : content_type); return g_titleNcmContentTypeNames[idx]; } +const char *titleGetNcmContentMetaTypeName(u8 content_meta_type) +{ + u8 idx = (content_meta_type <= NcmContentMetaType_BootImagePackageSafe ? content_meta_type : \ + ((content_meta_type < NcmContentMetaType_Application || content_meta_type > NcmContentMetaType_Delta) ? 0 : (content_meta_type - 0x7A))); + return g_titleNcmContentMetaTypeNames[idx]; +} + NX_INLINE void titleFreeApplicationMetadata(void) { if (g_appMetadata) @@ -1543,7 +1699,7 @@ static void titleRemoveGameCardTitleInfoEntries(void) titleFreeTitleInfo(); } else { /* Update parent, previous and next title info pointers from user application, patch and add-on content entries. */ - for(u32 i = 0; i < (g_titleInfoCount - g_titleInfoGameCardCount); i++) + for(u32 i = 0; i < g_titleInfoGameCardStartIndex; i++) { TitleInfo *cur_title_info = &(g_titleInfo[i]); @@ -1556,14 +1712,14 @@ static void titleRemoveGameCardTitleInfoEntries(void) } /* Free content infos from gamecard title info entries. */ - for(u32 i = (g_titleInfoCount - g_titleInfoGameCardCount); i < g_titleInfoCount; i++) + for(u32 i = g_titleInfoGameCardStartIndex; i < g_titleInfoCount; i++) { TitleInfo *cur_title_info = &(g_titleInfo[i]); if (cur_title_info->content_infos) free(cur_title_info->content_infos); } /* Reallocate title info buffer. */ - TitleInfo *tmp_title_info = realloc(g_titleInfo, (g_titleInfoCount - g_titleInfoGameCardCount) * sizeof(TitleInfo)); + TitleInfo *tmp_title_info = realloc(g_titleInfo, g_titleInfoGameCardStartIndex * sizeof(TitleInfo)); if (tmp_title_info) { g_titleInfo = tmp_title_info; @@ -1571,7 +1727,7 @@ static void titleRemoveGameCardTitleInfoEntries(void) } /* Update counters. */ - g_titleInfoCount = (g_titleInfoCount - g_titleInfoGameCardCount); + g_titleInfoCount = g_titleInfoGameCardStartIndex; g_titleInfoGameCardStartIndex = g_titleInfoGameCardCount = 0; } } @@ -1582,9 +1738,11 @@ static bool titleIsUserApplicationContentAvailable(u64 app_id) for(u32 i = 0; i < g_titleInfoCount; i++) { - if ((g_titleInfo[i].meta_key.type == NcmContentMetaType_Application && g_titleInfo[i].meta_key.id == app_id) || \ - (g_titleInfo[i].meta_key.type == NcmContentMetaType_Patch && titleCheckIfPatchIdBelongsToApplicationId(app_id, g_titleInfo[i].meta_key.id)) || \ - (g_titleInfo[i].meta_key.type == NcmContentMetaType_AddOnContent && titleCheckIfAddOnContentIdBelongsToApplicationId(app_id, g_titleInfo[i].meta_key.id))) return true; + TitleInfo *cur_title_info = &(g_titleInfo[i]); + + if ((cur_title_info->meta_key.type == NcmContentMetaType_Application && cur_title_info->meta_key.id == app_id) || \ + (cur_title_info->meta_key.type == NcmContentMetaType_Patch && titleCheckIfPatchIdBelongsToApplicationId(app_id, cur_title_info->meta_key.id)) || \ + (cur_title_info->meta_key.type == NcmContentMetaType_AddOnContent && titleCheckIfAddOnContentIdBelongsToApplicationId(app_id, cur_title_info->meta_key.id))) return true; } return false; @@ -1605,7 +1763,7 @@ static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id, /* Speed up gamecard lookups. */ u32 start_idx = (storage_id == NcmStorageId_GameCard ? g_titleInfoGameCardStartIndex : 0); - u32 max_val = ((storage_id == NcmStorageId_GameCard || storage_id == NcmStorageId_Any) ? g_titleInfoCount : (g_titleInfoCount - g_titleInfoGameCardCount)); + u32 max_val = ((storage_id == NcmStorageId_GameCard || storage_id == NcmStorageId_Any) ? g_titleInfoCount : g_titleInfoGameCardStartIndex); for(u32 i = start_idx; i < max_val; i++) { diff --git a/source/title.h b/source/title.h index f2ca40b..d85ab54 100644 --- a/source/title.h +++ b/source/title.h @@ -70,6 +70,19 @@ typedef struct { TitleInfo *aoc_info; ///< Pointer to a TitleInfo element holding info for the first detected add-on content entry matching the provided application ID. } TitleUserApplicationData; +typedef enum { + TitleFileNameConvention_Full = 0, ///< Individual titles: "[{Name}] [{TitleId}][v{TitleVersion}][{TitleType}]". + ///< Gamecards: "[{Name1}] [{TitleId1}][v{TitleVersion1}] + ... + [{NameN}] [{TitleIdN}][v{TitleVersionN}]". + TitleFileNameConvention_IdAndVersionOnly = 1 ///< Individual titles: "{TitleId}_v{TitleVersion}_{TitleType}". + ///< Gamecards: "{TitleId1}_v{TitleVersion1}_{TitleType1} + ... + {TitleIdN}_v{TitleVersionN}_{TitleTypeN}". +} TitleFileNameConvention; + +typedef enum { + TitleFileNameIllegalCharReplaceType_None = 0, + TitleFileNameIllegalCharReplaceType_IllegalFsChars = 1, + TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly = 2 +} TitleFileNameIllegalCharReplaceType; + /// Initializes the title interface. bool titleInitialize(void); @@ -108,9 +121,21 @@ TitleInfo **titleGetInfoFromOrphanTitles(u32 *out_count); /// If titleGetApplicationMetadataEntries() has been previously called, its returned buffer should be freed and a new titleGetApplicationMetadataEntries() call should be issued. bool titleIsGameCardInfoUpdated(void); +/// Returns a pointer to a dynamically allocated buffer that holds a filename string suitable for output title dumps. +/// Returns NULL if an error occurs. +char *titleGenerateFileName(const TitleInfo *title_info, u8 name_convention, u8 illegal_char_replace_type); + +/// Returns a pointer to a dynamically allocated buffer that holds a filename string suitable for output gamecard dumps. +/// A valid gamecard must be inserted, and title info must have been loaded from it accordingly. +/// Returns NULL if an error occurs. +char *titleGenerateGameCardFileName(u8 name_convention, u8 illegal_char_replace_type); + /// Returns a pointer to a string holding the name of the provided ncm content type. const char *titleGetNcmContentTypeName(u8 content_type); +/// Returns a pointer to a string holding the name of the provided ncm content meta type. +const char *titleGetNcmContentMetaTypeName(u8 content_meta_type); + /// Miscellaneous functions. NX_INLINE void titleConvertNcmContentSizeToU64(const u8 *size, u64 *out)