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:
parent
7606f7b40a
commit
ace4732fda
5 changed files with 336 additions and 37 deletions
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
176
source/title.c
176
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++)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue