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 "utils.h"
|
||||||
#include "gamecard.h"
|
#include "gamecard.h"
|
||||||
#include "usb.h"
|
#include "usb.h"
|
||||||
|
#include "title.h"
|
||||||
|
#include "crc32_fast.h"
|
||||||
|
|
||||||
#define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE
|
#define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE
|
||||||
|
|
||||||
|
@ -58,6 +60,7 @@ typedef struct
|
||||||
bool read_error;
|
bool read_error;
|
||||||
bool write_error;
|
bool write_error;
|
||||||
bool transfer_cancelled;
|
bool transfer_cancelled;
|
||||||
|
u32 xci_crc, full_xci_crc;
|
||||||
} ThreadSharedData;
|
} ThreadSharedData;
|
||||||
|
|
||||||
/* Function prototypes. */
|
/* Function prototypes. */
|
||||||
|
@ -73,13 +76,14 @@ static bool sendGameCardImageViaUsb(void);
|
||||||
static void changeKeyAreaOption(u32 idx);
|
static void changeKeyAreaOption(u32 idx);
|
||||||
static void changeCertificateOption(u32 idx);
|
static void changeCertificateOption(u32 idx);
|
||||||
static void changeTrimOption(u32 idx);
|
static void changeTrimOption(u32 idx);
|
||||||
|
static void changeCrcOption(u32 idx);
|
||||||
|
|
||||||
static int read_thread_func(void *arg);
|
static int read_thread_func(void *arg);
|
||||||
static int write_thread_func(void *arg);
|
static int write_thread_func(void *arg);
|
||||||
|
|
||||||
/* Global variables. */
|
/* 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 };
|
static const char *g_xciOptions[] = { "no", "yes", NULL };
|
||||||
|
|
||||||
|
@ -120,6 +124,16 @@ static MenuElement *g_xciMenuElements[] = {
|
||||||
.options = g_xciOptions
|
.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
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -388,16 +402,22 @@ static bool sendGameCardKeyAreaViaUsb(void)
|
||||||
|
|
||||||
GameCardKeyArea gc_key_area = {0};
|
GameCardKeyArea gc_key_area = {0};
|
||||||
bool success = false;
|
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;
|
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;
|
success = true;
|
||||||
|
|
||||||
end:
|
end:
|
||||||
|
if (filename) free(filename);
|
||||||
|
|
||||||
utilsChangeHomeButtonBlockStatus(false);
|
utilsChangeHomeButtonBlockStatus(false);
|
||||||
|
|
||||||
consolePrint("press any button to continue");
|
consolePrint("press any button to continue");
|
||||||
|
@ -413,10 +433,11 @@ static bool sendGameCardCertificateViaUsb(void)
|
||||||
utilsChangeHomeButtonBlockStatus(true);
|
utilsChangeHomeButtonBlockStatus(true);
|
||||||
|
|
||||||
FsGameCardCertificate gc_cert = {0};
|
FsGameCardCertificate gc_cert = {0};
|
||||||
char device_id_str[0x21] = {0};
|
|
||||||
bool success = false;
|
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");
|
consolePrint("failed to get gamecard certificate\n");
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -424,14 +445,17 @@ static bool sendGameCardCertificateViaUsb(void)
|
||||||
|
|
||||||
consolePrint("get gamecard certificate ok\n");
|
consolePrint("get gamecard certificate ok\n");
|
||||||
|
|
||||||
utilsGenerateHexStringFromData(device_id_str, 0x21, gc_cert.device_id, 0x10);
|
crc32FastCalculate(&gc_cert, sizeof(FsGameCardCertificate), &crc);
|
||||||
sprintf(path, "card_certificate_%s.bin", device_id_str);
|
snprintf(path, MAX_ELEMENTS(path), "%s (Certificate) (%08X).bin", filename, crc);
|
||||||
|
|
||||||
if (!sendFileData(path, &gc_cert, sizeof(FsGameCardCertificate))) goto end;
|
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;
|
success = true;
|
||||||
|
|
||||||
end:
|
end:
|
||||||
|
if (filename) free(filename);
|
||||||
|
|
||||||
utilsChangeHomeButtonBlockStatus(false);
|
utilsChangeHomeButtonBlockStatus(false);
|
||||||
|
|
||||||
consolePrint("press any button to continue");
|
consolePrint("press any button to continue");
|
||||||
|
@ -447,15 +471,25 @@ static bool sendGameCardImageViaUsb(void)
|
||||||
utilsChangeHomeButtonBlockStatus(true);
|
utilsChangeHomeButtonBlockStatus(true);
|
||||||
|
|
||||||
u64 gc_size = 0;
|
u64 gc_size = 0;
|
||||||
|
u32 key_area_crc = 0;
|
||||||
GameCardKeyArea gc_key_area = {0};
|
GameCardKeyArea gc_key_area = {0};
|
||||||
|
|
||||||
ThreadSharedData shared_data = {0};
|
ThreadSharedData shared_data = {0};
|
||||||
thrd_t read_thread, write_thread;
|
thrd_t read_thread, write_thread;
|
||||||
|
|
||||||
|
char *filename = NULL;
|
||||||
|
|
||||||
bool success = false;
|
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");
|
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);
|
shared_data.data = usbAllocatePageAlignedBuffer(BLOCK_SIZE);
|
||||||
if (!shared_data.data)
|
if (!shared_data.data)
|
||||||
{
|
{
|
||||||
|
@ -477,10 +511,12 @@ static bool sendGameCardImageViaUsb(void)
|
||||||
{
|
{
|
||||||
gc_size += sizeof(GameCardKeyArea);
|
gc_size += sizeof(GameCardKeyArea);
|
||||||
if (!dumpGameCardKeyArea(&gc_key_area)) goto end;
|
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);
|
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))
|
if (!usbSendFileProperties(gc_size, path))
|
||||||
{
|
{
|
||||||
consolePrint("failed to send file properties for \"%s\"!\n", path);
|
consolePrint("failed to send file properties for \"%s\"!\n", path);
|
||||||
|
@ -571,12 +607,22 @@ static bool sendGameCardImageViaUsb(void)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
consolePrint("process completed in %lu seconds\n", start);
|
printf("process completed in %lu seconds\n", start);
|
||||||
success = true;
|
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:
|
end:
|
||||||
if (shared_data.data) free(shared_data.data);
|
if (shared_data.data) free(shared_data.data);
|
||||||
|
|
||||||
|
if (filename) free(filename);
|
||||||
|
|
||||||
utilsChangeHomeButtonBlockStatus(false);
|
utilsChangeHomeButtonBlockStatus(false);
|
||||||
|
|
||||||
consolePrint("press any button to continue");
|
consolePrint("press any button to continue");
|
||||||
|
@ -600,6 +646,11 @@ static void changeTrimOption(u32 idx)
|
||||||
g_trimDump = (idx > 0);
|
g_trimDump = (idx > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void changeCrcOption(u32 idx)
|
||||||
|
{
|
||||||
|
g_calcCrc = (idx > 0);
|
||||||
|
}
|
||||||
|
|
||||||
static int read_thread_func(void *arg)
|
static int read_thread_func(void *arg)
|
||||||
{
|
{
|
||||||
ThreadSharedData *shared_data = (ThreadSharedData*)arg;
|
ThreadSharedData *shared_data = (ThreadSharedData*)arg;
|
||||||
|
@ -638,6 +689,13 @@ static int read_thread_func(void *arg)
|
||||||
/* Remove certificate */
|
/* Remove certificate */
|
||||||
if (!g_keepCertificate && offset == 0) memset(buf + GAMECARD_CERTIFICATE_OFFSET, 0xFF, sizeof(FsGameCardCertificate));
|
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 */
|
/* Wait until the previous data chunk has been written */
|
||||||
mutexLock(&g_fileMutex);
|
mutexLock(&g_fileMutex);
|
||||||
|
|
||||||
|
|
|
@ -35,10 +35,10 @@ static void crc32FastInitializeTables(u32 *table, u32 *wtable)
|
||||||
|
|
||||||
for(u32 k = 0; k < 4; ++k)
|
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);
|
for(u32 j = w = 0; j < 4; ++j) w = (table[(u8)(j == k ? (w ^ i) : w)] ^ w >> 8);
|
||||||
wtable[i + (k << 8)] = (w ^ (k ? wtable[0] : 0));
|
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;
|
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);
|
u64 n_accum = (n_bytes / 4);
|
||||||
|
|
||||||
if (!*table) crc32FastInitializeTables(table, wtable);
|
if (!*table) crc32FastInitializeTables(table, wtable);
|
||||||
|
|
||||||
for(u64 i = 0; i < n_accum; ++i)
|
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(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 "utils.h"
|
||||||
#include "gamecard.h"
|
#include "gamecard.h"
|
||||||
#include "usb.h"
|
#include "usb.h"
|
||||||
|
#include "title.h"
|
||||||
|
#include "crc32_fast.h"
|
||||||
|
|
||||||
#define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE
|
#define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE
|
||||||
|
|
||||||
|
@ -58,6 +60,7 @@ typedef struct
|
||||||
bool read_error;
|
bool read_error;
|
||||||
bool write_error;
|
bool write_error;
|
||||||
bool transfer_cancelled;
|
bool transfer_cancelled;
|
||||||
|
u32 xci_crc, full_xci_crc;
|
||||||
} ThreadSharedData;
|
} ThreadSharedData;
|
||||||
|
|
||||||
/* Function prototypes. */
|
/* Function prototypes. */
|
||||||
|
@ -73,13 +76,14 @@ static bool sendGameCardImageViaUsb(void);
|
||||||
static void changeKeyAreaOption(u32 idx);
|
static void changeKeyAreaOption(u32 idx);
|
||||||
static void changeCertificateOption(u32 idx);
|
static void changeCertificateOption(u32 idx);
|
||||||
static void changeTrimOption(u32 idx);
|
static void changeTrimOption(u32 idx);
|
||||||
|
static void changeCrcOption(u32 idx);
|
||||||
|
|
||||||
static int read_thread_func(void *arg);
|
static int read_thread_func(void *arg);
|
||||||
static int write_thread_func(void *arg);
|
static int write_thread_func(void *arg);
|
||||||
|
|
||||||
/* Global variables. */
|
/* 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 };
|
static const char *g_xciOptions[] = { "no", "yes", NULL };
|
||||||
|
|
||||||
|
@ -120,6 +124,16 @@ static MenuElement *g_xciMenuElements[] = {
|
||||||
.options = g_xciOptions
|
.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
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -388,16 +402,22 @@ static bool sendGameCardKeyAreaViaUsb(void)
|
||||||
|
|
||||||
GameCardKeyArea gc_key_area = {0};
|
GameCardKeyArea gc_key_area = {0};
|
||||||
bool success = false;
|
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;
|
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;
|
success = true;
|
||||||
|
|
||||||
end:
|
end:
|
||||||
|
if (filename) free(filename);
|
||||||
|
|
||||||
utilsChangeHomeButtonBlockStatus(false);
|
utilsChangeHomeButtonBlockStatus(false);
|
||||||
|
|
||||||
consolePrint("press any button to continue");
|
consolePrint("press any button to continue");
|
||||||
|
@ -413,10 +433,11 @@ static bool sendGameCardCertificateViaUsb(void)
|
||||||
utilsChangeHomeButtonBlockStatus(true);
|
utilsChangeHomeButtonBlockStatus(true);
|
||||||
|
|
||||||
FsGameCardCertificate gc_cert = {0};
|
FsGameCardCertificate gc_cert = {0};
|
||||||
char device_id_str[0x21] = {0};
|
|
||||||
bool success = false;
|
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");
|
consolePrint("failed to get gamecard certificate\n");
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -424,14 +445,17 @@ static bool sendGameCardCertificateViaUsb(void)
|
||||||
|
|
||||||
consolePrint("get gamecard certificate ok\n");
|
consolePrint("get gamecard certificate ok\n");
|
||||||
|
|
||||||
utilsGenerateHexStringFromData(device_id_str, 0x21, gc_cert.device_id, 0x10);
|
crc32FastCalculate(&gc_cert, sizeof(FsGameCardCertificate), &crc);
|
||||||
sprintf(path, "card_certificate_%s.bin", device_id_str);
|
snprintf(path, MAX_ELEMENTS(path), "%s (Certificate) (%08X).bin", filename, crc);
|
||||||
|
|
||||||
if (!sendFileData(path, &gc_cert, sizeof(FsGameCardCertificate))) goto end;
|
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;
|
success = true;
|
||||||
|
|
||||||
end:
|
end:
|
||||||
|
if (filename) free(filename);
|
||||||
|
|
||||||
utilsChangeHomeButtonBlockStatus(false);
|
utilsChangeHomeButtonBlockStatus(false);
|
||||||
|
|
||||||
consolePrint("press any button to continue");
|
consolePrint("press any button to continue");
|
||||||
|
@ -447,15 +471,25 @@ static bool sendGameCardImageViaUsb(void)
|
||||||
utilsChangeHomeButtonBlockStatus(true);
|
utilsChangeHomeButtonBlockStatus(true);
|
||||||
|
|
||||||
u64 gc_size = 0;
|
u64 gc_size = 0;
|
||||||
|
u32 key_area_crc = 0;
|
||||||
GameCardKeyArea gc_key_area = {0};
|
GameCardKeyArea gc_key_area = {0};
|
||||||
|
|
||||||
ThreadSharedData shared_data = {0};
|
ThreadSharedData shared_data = {0};
|
||||||
thrd_t read_thread, write_thread;
|
thrd_t read_thread, write_thread;
|
||||||
|
|
||||||
|
char *filename = NULL;
|
||||||
|
|
||||||
bool success = false;
|
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");
|
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);
|
shared_data.data = usbAllocatePageAlignedBuffer(BLOCK_SIZE);
|
||||||
if (!shared_data.data)
|
if (!shared_data.data)
|
||||||
{
|
{
|
||||||
|
@ -477,10 +511,12 @@ static bool sendGameCardImageViaUsb(void)
|
||||||
{
|
{
|
||||||
gc_size += sizeof(GameCardKeyArea);
|
gc_size += sizeof(GameCardKeyArea);
|
||||||
if (!dumpGameCardKeyArea(&gc_key_area)) goto end;
|
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);
|
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))
|
if (!usbSendFileProperties(gc_size, path))
|
||||||
{
|
{
|
||||||
consolePrint("failed to send file properties for \"%s\"!\n", path);
|
consolePrint("failed to send file properties for \"%s\"!\n", path);
|
||||||
|
@ -571,12 +607,22 @@ static bool sendGameCardImageViaUsb(void)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
consolePrint("process completed in %lu seconds\n", start);
|
printf("process completed in %lu seconds\n", start);
|
||||||
success = true;
|
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:
|
end:
|
||||||
if (shared_data.data) free(shared_data.data);
|
if (shared_data.data) free(shared_data.data);
|
||||||
|
|
||||||
|
if (filename) free(filename);
|
||||||
|
|
||||||
utilsChangeHomeButtonBlockStatus(false);
|
utilsChangeHomeButtonBlockStatus(false);
|
||||||
|
|
||||||
consolePrint("press any button to continue");
|
consolePrint("press any button to continue");
|
||||||
|
@ -600,6 +646,11 @@ static void changeTrimOption(u32 idx)
|
||||||
g_trimDump = (idx > 0);
|
g_trimDump = (idx > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void changeCrcOption(u32 idx)
|
||||||
|
{
|
||||||
|
g_calcCrc = (idx > 0);
|
||||||
|
}
|
||||||
|
|
||||||
static int read_thread_func(void *arg)
|
static int read_thread_func(void *arg)
|
||||||
{
|
{
|
||||||
ThreadSharedData *shared_data = (ThreadSharedData*)arg;
|
ThreadSharedData *shared_data = (ThreadSharedData*)arg;
|
||||||
|
@ -638,6 +689,13 @@ static int read_thread_func(void *arg)
|
||||||
/* Remove certificate */
|
/* Remove certificate */
|
||||||
if (!g_keepCertificate && offset == 0) memset(buf + GAMECARD_CERTIFICATE_OFFSET, 0xFF, sizeof(FsGameCardCertificate));
|
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 */
|
/* Wait until the previous data chunk has been written */
|
||||||
mutexLock(&g_fileMutex);
|
mutexLock(&g_fileMutex);
|
||||||
|
|
||||||
|
|
176
source/title.c
176
source/title.c
|
@ -61,6 +61,19 @@ static const char *g_titleNcmContentTypeNames[] = {
|
||||||
[NcmContentType_DeltaFragment + 1] = "Unknown"
|
[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. */
|
/* Info retrieved from https://switchbrew.org/wiki/Title_list. */
|
||||||
/* Titles bundled with the kernel are excluded. */
|
/* Titles bundled with the kernel are excluded. */
|
||||||
static const SystemTitleName g_systemTitles[] = {
|
static const SystemTitleName g_systemTitles[] = {
|
||||||
|
@ -620,7 +633,7 @@ bool titleGetUserApplicationData(u64 app_id, TitleUserApplicationData *out)
|
||||||
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
||||||
if (!titleIsUserApplicationContentAvailable(app_id) || !out)
|
if (!out)
|
||||||
{
|
{
|
||||||
LOGFILE("Invalid parameters!");
|
LOGFILE("Invalid parameters!");
|
||||||
goto end;
|
goto end;
|
||||||
|
@ -711,12 +724,155 @@ bool titleIsGameCardInfoUpdated(void)
|
||||||
return ret;
|
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)
|
const char *titleGetNcmContentTypeName(u8 content_type)
|
||||||
{
|
{
|
||||||
u8 idx = (content_type > NcmContentType_DeltaFragment ? (NcmContentType_DeltaFragment + 1) : content_type);
|
u8 idx = (content_type > NcmContentType_DeltaFragment ? (NcmContentType_DeltaFragment + 1) : content_type);
|
||||||
return g_titleNcmContentTypeNames[idx];
|
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)
|
NX_INLINE void titleFreeApplicationMetadata(void)
|
||||||
{
|
{
|
||||||
if (g_appMetadata)
|
if (g_appMetadata)
|
||||||
|
@ -1543,7 +1699,7 @@ static void titleRemoveGameCardTitleInfoEntries(void)
|
||||||
titleFreeTitleInfo();
|
titleFreeTitleInfo();
|
||||||
} else {
|
} else {
|
||||||
/* Update parent, previous and next title info pointers from user application, patch and add-on content entries. */
|
/* 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]);
|
TitleInfo *cur_title_info = &(g_titleInfo[i]);
|
||||||
|
|
||||||
|
@ -1556,14 +1712,14 @@ static void titleRemoveGameCardTitleInfoEntries(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Free content infos from gamecard title info entries. */
|
/* 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]);
|
TitleInfo *cur_title_info = &(g_titleInfo[i]);
|
||||||
if (cur_title_info->content_infos) free(cur_title_info->content_infos);
|
if (cur_title_info->content_infos) free(cur_title_info->content_infos);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reallocate title info buffer. */
|
/* 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)
|
if (tmp_title_info)
|
||||||
{
|
{
|
||||||
g_titleInfo = tmp_title_info;
|
g_titleInfo = tmp_title_info;
|
||||||
|
@ -1571,7 +1727,7 @@ static void titleRemoveGameCardTitleInfoEntries(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update counters. */
|
/* Update counters. */
|
||||||
g_titleInfoCount = (g_titleInfoCount - g_titleInfoGameCardCount);
|
g_titleInfoCount = g_titleInfoGameCardStartIndex;
|
||||||
g_titleInfoGameCardStartIndex = g_titleInfoGameCardCount = 0;
|
g_titleInfoGameCardStartIndex = g_titleInfoGameCardCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1582,9 +1738,11 @@ static bool titleIsUserApplicationContentAvailable(u64 app_id)
|
||||||
|
|
||||||
for(u32 i = 0; i < g_titleInfoCount; i++)
|
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) || \
|
TitleInfo *cur_title_info = &(g_titleInfo[i]);
|
||||||
(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;
|
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;
|
return false;
|
||||||
|
@ -1605,7 +1763,7 @@ static TitleInfo *_titleGetInfoFromStorageByTitleId(u8 storage_id, u64 title_id,
|
||||||
|
|
||||||
/* Speed up gamecard lookups. */
|
/* Speed up gamecard lookups. */
|
||||||
u32 start_idx = (storage_id == NcmStorageId_GameCard ? g_titleInfoGameCardStartIndex : 0);
|
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++)
|
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.
|
TitleInfo *aoc_info; ///< Pointer to a TitleInfo element holding info for the first detected add-on content entry matching the provided application ID.
|
||||||
} TitleUserApplicationData;
|
} 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.
|
/// Initializes the title interface.
|
||||||
bool titleInitialize(void);
|
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.
|
/// If titleGetApplicationMetadataEntries() has been previously called, its returned buffer should be freed and a new titleGetApplicationMetadataEntries() call should be issued.
|
||||||
bool titleIsGameCardInfoUpdated(void);
|
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.
|
/// Returns a pointer to a string holding the name of the provided ncm content type.
|
||||||
const char *titleGetNcmContentTypeName(u8 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.
|
/// Miscellaneous functions.
|
||||||
|
|
||||||
NX_INLINE void titleConvertNcmContentSizeToU64(const u8 *size, u64 *out)
|
NX_INLINE void titleConvertNcmContentSizeToU64(const u8 *size, u64 *out)
|
||||||
|
|
Loading…
Reference in a new issue