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

Functions to generate gamecard/title filenames + fix CRC32 calculation.

Updated the threaded gamecard dumper to reflect these changes.
This commit is contained in:
Pablo Curiel 2020-08-13 22:31:02 -04:00
parent 7606f7b40a
commit ace4732fda
5 changed files with 336 additions and 37 deletions

View file

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

View file

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

View file

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

View file

@ -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++)
{

View file

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