mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-23 02:36:41 +00:00
gamecard: cache LAFW blob during interface initialization.
Other changes include: * Codestyle fixes. * Remove references to GameCardKeyArea in the usb_gc_dumper PoC. * Remove option to append key area to output XCI dumps in usb_gc_dumper PoC.
This commit is contained in:
parent
e79b03afeb
commit
8168a5ac84
3 changed files with 235 additions and 294 deletions
|
@ -64,7 +64,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;
|
u32 xci_crc;
|
||||||
} ThreadSharedData;
|
} ThreadSharedData;
|
||||||
|
|
||||||
/* Function prototypes. */
|
/* Function prototypes. */
|
||||||
|
@ -73,13 +73,12 @@ static void consolePrint(const char *text, ...);
|
||||||
|
|
||||||
static u32 menuGetElementCount(const Menu *menu);
|
static u32 menuGetElementCount(const Menu *menu);
|
||||||
|
|
||||||
static bool sendGameCardKeyAreaViaUsb(void);
|
|
||||||
static bool sendGameCardSpecificDataViaUsb(void);
|
static bool sendGameCardSpecificDataViaUsb(void);
|
||||||
static bool sendGameCardCertificateViaUsb(void);
|
static bool sendGameCardCertificateViaUsb(void);
|
||||||
|
static bool sendGameCardInitialDataViaUsb(void);
|
||||||
static bool sendGameCardImageViaUsb(void);
|
static bool sendGameCardImageViaUsb(void);
|
||||||
static bool sendConsoleLafwViaUsb(void);
|
static bool sendConsoleLafwBlobViaUsb(void);
|
||||||
|
|
||||||
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 void changeCrcOption(u32 idx);
|
||||||
|
@ -89,7 +88,7 @@ static void write_thread_func(void *arg);
|
||||||
|
|
||||||
/* Global variables. */
|
/* Global variables. */
|
||||||
|
|
||||||
static bool g_appendKeyArea = false, g_keepCertificate = false, g_trimDump = false, g_calcCrc = false;
|
static bool 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 };
|
||||||
|
|
||||||
|
@ -100,16 +99,6 @@ static MenuElement *g_xciMenuElements[] = {
|
||||||
.task_func = &sendGameCardImageViaUsb,
|
.task_func = &sendGameCardImageViaUsb,
|
||||||
.element_options = NULL
|
.element_options = NULL
|
||||||
},
|
},
|
||||||
&(MenuElement){
|
|
||||||
.str = "append key area",
|
|
||||||
.child_menu = NULL,
|
|
||||||
.task_func = NULL,
|
|
||||||
.element_options = &(MenuElementOption){
|
|
||||||
.selected = 0,
|
|
||||||
.options_func = &changeKeyAreaOption,
|
|
||||||
.options = g_xciOptions
|
|
||||||
}
|
|
||||||
},
|
|
||||||
&(MenuElement){
|
&(MenuElement){
|
||||||
.str = "keep certificate",
|
.str = "keep certificate",
|
||||||
.child_menu = NULL,
|
.child_menu = NULL,
|
||||||
|
@ -151,12 +140,6 @@ static Menu g_xciMenu = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static MenuElement *g_rootMenuElements[] = {
|
static MenuElement *g_rootMenuElements[] = {
|
||||||
&(MenuElement){
|
|
||||||
.str = "dump gamecard initial data",
|
|
||||||
.child_menu = NULL,
|
|
||||||
.task_func = &sendGameCardKeyAreaViaUsb,
|
|
||||||
.element_options = NULL
|
|
||||||
},
|
|
||||||
&(MenuElement){
|
&(MenuElement){
|
||||||
.str = "dump gamecard specific data",
|
.str = "dump gamecard specific data",
|
||||||
.child_menu = NULL,
|
.child_menu = NULL,
|
||||||
|
@ -169,6 +152,12 @@ static MenuElement *g_rootMenuElements[] = {
|
||||||
.task_func = &sendGameCardCertificateViaUsb,
|
.task_func = &sendGameCardCertificateViaUsb,
|
||||||
.element_options = NULL
|
.element_options = NULL
|
||||||
},
|
},
|
||||||
|
&(MenuElement){
|
||||||
|
.str = "dump gamecard initial data",
|
||||||
|
.child_menu = NULL,
|
||||||
|
.task_func = &sendGameCardInitialDataViaUsb,
|
||||||
|
.element_options = NULL
|
||||||
|
},
|
||||||
&(MenuElement){
|
&(MenuElement){
|
||||||
.str = "dump gamecard xci",
|
.str = "dump gamecard xci",
|
||||||
.child_menu = &g_xciMenu,
|
.child_menu = &g_xciMenu,
|
||||||
|
@ -176,9 +165,9 @@ static MenuElement *g_rootMenuElements[] = {
|
||||||
.element_options = NULL
|
.element_options = NULL
|
||||||
},
|
},
|
||||||
&(MenuElement){
|
&(MenuElement){
|
||||||
.str = "dump console LAFW",
|
.str = "dump console lafw blob",
|
||||||
.child_menu = NULL,
|
.child_menu = NULL,
|
||||||
.task_func = &sendConsoleLafwViaUsb,
|
.task_func = &sendConsoleLafwBlobViaUsb,
|
||||||
.element_options = NULL
|
.element_options = NULL
|
||||||
},
|
},
|
||||||
NULL
|
NULL
|
||||||
|
@ -450,24 +439,6 @@ static bool sendFileData(const char *path, void *data, size_t data_size)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool dumpGameCardKeyArea(GameCardKeyArea *out)
|
|
||||||
{
|
|
||||||
if (!out)
|
|
||||||
{
|
|
||||||
consolePrint("invalid parameters to dump key area!\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!gamecardGetKeyArea(out))
|
|
||||||
{
|
|
||||||
consolePrint("failed to get gamecard key area\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
consolePrint("get gamecard key area ok\n");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool dumpGameCardSecurityInformation(GameCardSecurityInformation *out)
|
static bool dumpGameCardSecurityInformation(GameCardSecurityInformation *out)
|
||||||
{
|
{
|
||||||
if (!out)
|
if (!out)
|
||||||
|
@ -486,41 +457,6 @@ static bool dumpGameCardSecurityInformation(GameCardSecurityInformation *out)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool sendGameCardKeyAreaViaUsb(void)
|
|
||||||
{
|
|
||||||
if (!waitForGameCardAndUsb()) return false;
|
|
||||||
|
|
||||||
utilsSetLongRunningProcessState(true);
|
|
||||||
|
|
||||||
GameCardKeyArea gc_key_area = {0};
|
|
||||||
bool success = false;
|
|
||||||
u32 crc = 0;
|
|
||||||
char *filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, TitleFileNameIllegalCharReplaceType_IllegalFsChars);
|
|
||||||
|
|
||||||
if (!dumpGameCardKeyArea(&gc_key_area) || !filename) goto end;
|
|
||||||
|
|
||||||
crc = crc32Calculate(&(gc_key_area.initial_data), sizeof(GameCardInitialData));
|
|
||||||
snprintf(path, MAX_ELEMENTS(path), "%s (Initial Data) (%08X).bin", filename, crc);
|
|
||||||
|
|
||||||
if (!sendFileData(path, &(gc_key_area.initial_data), sizeof(GameCardInitialData))) goto end;
|
|
||||||
|
|
||||||
printf("successfully sent key area as \"%s\"\n", path);
|
|
||||||
success = true;
|
|
||||||
|
|
||||||
end:
|
|
||||||
if (filename) free(filename);
|
|
||||||
|
|
||||||
utilsSetLongRunningProcessState(false);
|
|
||||||
|
|
||||||
FsGameCardIdSet id_set = {0};
|
|
||||||
if (gamecardGetIdSet(&id_set)) LOG_DATA(&id_set, sizeof(FsGameCardIdSet), "Gamecard ID set:");
|
|
||||||
|
|
||||||
consolePrint("press any button to continue");
|
|
||||||
utilsWaitForButtonPress(0);
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool sendGameCardSpecificDataViaUsb(void)
|
static bool sendGameCardSpecificDataViaUsb(void)
|
||||||
{
|
{
|
||||||
if (!waitForGameCardAndUsb()) return false;
|
if (!waitForGameCardAndUsb()) return false;
|
||||||
|
@ -591,44 +527,30 @@ end:
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool sendConsoleLafwViaUsb(void)
|
static bool sendGameCardInitialDataViaUsb(void)
|
||||||
{
|
{
|
||||||
if (!waitForGameCardAndUsb()) return false;
|
if (!waitForGameCardAndUsb()) return false;
|
||||||
|
|
||||||
utilsSetLongRunningProcessState(true);
|
utilsSetLongRunningProcessState(true);
|
||||||
|
|
||||||
LotusAsicFirmware lafw = {0};
|
GameCardSecurityInformation gc_security_information = {0};
|
||||||
bool success = false;
|
bool success = false;
|
||||||
u32 crc = 0;
|
u32 crc = 0;
|
||||||
|
char *filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, TitleFileNameIllegalCharReplaceType_IllegalFsChars);
|
||||||
|
|
||||||
if (!gamecardGetLotusAsicFirmware(&lafw))
|
if (!dumpGameCardSecurityInformation(&gc_security_information) || !filename) goto end;
|
||||||
{
|
|
||||||
consolePrint("failed to get console LAFW\n");
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 lafw_version = gamecardConvertLotusAsicFirmwareVersionBitmask(&lafw);
|
crc = crc32Calculate(&(gc_security_information.initial_data), sizeof(GameCardInitialData));
|
||||||
|
snprintf(path, MAX_ELEMENTS(path), "%s (Initial Data) (%08X).bin", filename, crc);
|
||||||
|
|
||||||
const char* filename = "";
|
if (!sendFileData(path, &(gc_security_information.initial_data), sizeof(GameCardInitialData))) goto end;
|
||||||
switch(lafw.fw_type) {
|
|
||||||
case LotusAsicFirmwareType_ReadFw: filename = "ReadFw"; break;
|
|
||||||
case LotusAsicFirmwareType_ReadDevFw: filename = "ReadDevFw"; break;
|
|
||||||
case LotusAsicFirmwareType_WriterFw: filename = "WriterFw"; break;
|
|
||||||
case LotusAsicFirmwareType_RmaFw: filename = "RmaFw"; break;
|
|
||||||
default: filename = "Unknown"; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
consolePrint("get console LAFW ok\n");
|
printf("successfully sent initial data as \"%s\"\n", path);
|
||||||
|
|
||||||
crc = crc32Calculate(&lafw, sizeof(LotusAsicFirmware));
|
|
||||||
snprintf(path, MAX_ELEMENTS(path), "LAFW (%s) (v%d) (%08X).bin", filename, lafw_version, crc);
|
|
||||||
|
|
||||||
if (!sendFileData(path, &lafw, sizeof(LotusAsicFirmware))) goto end;
|
|
||||||
|
|
||||||
printf("successfully sent lafw as \"%s\"\n", path);
|
|
||||||
success = true;
|
success = true;
|
||||||
|
|
||||||
end:
|
end:
|
||||||
|
if (filename) free(filename);
|
||||||
|
|
||||||
utilsSetLongRunningProcessState(false);
|
utilsSetLongRunningProcessState(false);
|
||||||
|
|
||||||
consolePrint("press any button to continue");
|
consolePrint("press any button to continue");
|
||||||
|
@ -637,7 +559,6 @@ end:
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool sendGameCardImageViaUsb(void)
|
static bool sendGameCardImageViaUsb(void)
|
||||||
{
|
{
|
||||||
if (!waitForGameCardAndUsb()) return false;
|
if (!waitForGameCardAndUsb()) return false;
|
||||||
|
@ -645,8 +566,6 @@ static bool sendGameCardImageViaUsb(void)
|
||||||
utilsSetLongRunningProcessState(true);
|
utilsSetLongRunningProcessState(true);
|
||||||
|
|
||||||
u64 gc_size = 0;
|
u64 gc_size = 0;
|
||||||
u32 key_area_crc = 0;
|
|
||||||
GameCardKeyArea gc_key_area = {0};
|
|
||||||
|
|
||||||
ThreadSharedData shared_data = {0};
|
ThreadSharedData shared_data = {0};
|
||||||
Thread read_thread = {0}, write_thread = {0};
|
Thread read_thread = {0}, write_thread = {0};
|
||||||
|
@ -655,7 +574,7 @@ static bool sendGameCardImageViaUsb(void)
|
||||||
|
|
||||||
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\nkeep certificate: %s | trim dump: %s\n\n", g_keepCertificate ? "yes" : "no", g_trimDump ? "yes" : "no");
|
||||||
|
|
||||||
filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, TitleFileNameIllegalCharReplaceType_IllegalFsChars);
|
filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, TitleFileNameIllegalCharReplaceType_IllegalFsChars);
|
||||||
if (!filename)
|
if (!filename)
|
||||||
|
@ -681,34 +600,13 @@ static bool sendGameCardImageViaUsb(void)
|
||||||
|
|
||||||
consolePrint("gamecard size: 0x%lX\n", gc_size);
|
consolePrint("gamecard size: 0x%lX\n", gc_size);
|
||||||
|
|
||||||
if (g_appendKeyArea)
|
snprintf(path, MAX_ELEMENTS(path), "%s (%s) (%s).xci", filename, g_keepCertificate ? "cert" : "certless", g_trimDump ? "trimmed" : "untrimmed");
|
||||||
{
|
|
||||||
gc_size += sizeof(GameCardKeyArea);
|
|
||||||
|
|
||||||
if (!dumpGameCardKeyArea(&gc_key_area)) goto end;
|
|
||||||
|
|
||||||
if (g_calcCrc)
|
|
||||||
{
|
|
||||||
key_area_crc = crc32Calculate(&gc_key_area, sizeof(GameCardKeyArea));
|
|
||||||
if (g_appendKeyArea) shared_data.full_xci_crc = key_area_crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
consolePrint("gamecard size (with key area): 0x%lX\n", gc_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf(path, MAX_ELEMENTS(path), "%s (%s) (%s) (%s).xci", filename, g_appendKeyArea ? "keyarea" : "keyarealess", g_keepCertificate ? "cert" : "certless", g_trimDump ? "trimmed" : "untrimmed");
|
|
||||||
if (!usbSendFilePropertiesCommon(gc_size, path))
|
if (!usbSendFilePropertiesCommon(gc_size, path))
|
||||||
{
|
{
|
||||||
consolePrint("failed to send file properties for \"%s\"!\n", path);
|
consolePrint("failed to send file properties for \"%s\"!\n", path);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_appendKeyArea && !usbSendFileData(&gc_key_area, sizeof(GameCardKeyArea)))
|
|
||||||
{
|
|
||||||
consolePrint("failed to send gamecard key area data!\n");
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
consolePrint("creating threads\n");
|
consolePrint("creating threads\n");
|
||||||
utilsCreateThread(&read_thread, read_thread_func, &shared_data, 2);
|
utilsCreateThread(&read_thread, read_thread_func, &shared_data, 2);
|
||||||
utilsCreateThread(&write_thread, write_thread_func, &shared_data, 2);
|
utilsCreateThread(&write_thread, write_thread_func, &shared_data, 2);
|
||||||
|
@ -789,13 +687,7 @@ static bool sendGameCardImageViaUsb(void)
|
||||||
printf("process completed in %lu seconds\n", start);
|
printf("process completed in %lu seconds\n", start);
|
||||||
success = true;
|
success = true;
|
||||||
|
|
||||||
if (g_calcCrc)
|
if (g_calcCrc) printf("xci crc: %08X\n", shared_data.xci_crc);
|
||||||
{
|
|
||||||
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);
|
||||||
|
@ -810,9 +702,43 @@ end:
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void changeKeyAreaOption(u32 idx)
|
static bool sendConsoleLafwBlobViaUsb(void)
|
||||||
{
|
{
|
||||||
g_appendKeyArea = (idx > 0);
|
if (!waitForGameCardAndUsb()) return false;
|
||||||
|
|
||||||
|
utilsSetLongRunningProcessState(true);
|
||||||
|
|
||||||
|
u64 lafw_version = 0;
|
||||||
|
LotusAsicFirmwareBlob lafw_blob = {0};
|
||||||
|
bool success = false;
|
||||||
|
u32 crc = 0;
|
||||||
|
|
||||||
|
if (!gamecardGetLotusAsicFirmwareBlob(&lafw_blob, &lafw_version))
|
||||||
|
{
|
||||||
|
consolePrint("failed to get console lafw blob\n");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *type_str = gamecardGetLafwTypeString(lafw_blob.fw_type);
|
||||||
|
if (!type_str) type_str = "Unknown";
|
||||||
|
|
||||||
|
consolePrint("get console lafw blob ok\n");
|
||||||
|
|
||||||
|
crc = crc32Calculate(&lafw_blob, sizeof(LotusAsicFirmwareBlob));
|
||||||
|
snprintf(path, MAX_ELEMENTS(path), "LAFW (%s) (v%lu) (%08X).bin", type_str, lafw_version, crc);
|
||||||
|
|
||||||
|
if (!sendFileData(path, &lafw_blob, sizeof(LotusAsicFirmwareBlob))) goto end;
|
||||||
|
|
||||||
|
printf("successfully sent lafw blob as \"%s\"\n", path);
|
||||||
|
success = true;
|
||||||
|
|
||||||
|
end:
|
||||||
|
utilsSetLongRunningProcessState(false);
|
||||||
|
|
||||||
|
consolePrint("press any button to continue");
|
||||||
|
utilsWaitForButtonPress(0);
|
||||||
|
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void changeCertificateOption(u32 idx)
|
static void changeCertificateOption(u32 idx)
|
||||||
|
@ -869,11 +795,7 @@ static void read_thread_func(void *arg)
|
||||||
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 */
|
/* Update checksum */
|
||||||
if (g_calcCrc)
|
if (g_calcCrc) shared_data->xci_crc = crc32CalculateWithSeed(shared_data->xci_crc, buf, blksize);
|
||||||
{
|
|
||||||
shared_data->xci_crc = crc32CalculateWithSeed(shared_data->xci_crc, buf, blksize);
|
|
||||||
if (g_appendKeyArea) shared_data->full_xci_crc = crc32CalculateWithSeed(shared_data->full_xci_crc, buf, blksize);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Wait until the previous data chunk has been written */
|
/* Wait until the previous data chunk has been written */
|
||||||
mutexLock(&g_fileMutex);
|
mutexLock(&g_fileMutex);
|
||||||
|
|
|
@ -40,6 +40,20 @@ extern "C" {
|
||||||
|
|
||||||
#define GAMECARD_CERTIFICATE_OFFSET 0x7000
|
#define GAMECARD_CERTIFICATE_OFFSET 0x7000
|
||||||
|
|
||||||
|
/// Plaintext area. Dumped from FS program memory.
|
||||||
|
/// Overall structure may change with each new LAFW version.
|
||||||
|
typedef struct {
|
||||||
|
u32 asic_security_mode; ///< Determines how the Lotus ASIC initialised the gamecard security mode. Usually 0xFFFFFFF9.
|
||||||
|
u32 asic_status; ///< Bitmask of the internal gamecard interface status. Usually 0x20000000.
|
||||||
|
FsCardId1 card_id1;
|
||||||
|
FsCardId2 card_id2;
|
||||||
|
u8 card_uid[0x40];
|
||||||
|
u8 reserved[0x190];
|
||||||
|
u8 asic_session_hash[0x20]; ///< Changes with each gamecard (re)insertion.
|
||||||
|
} GameCardSpecificData;
|
||||||
|
|
||||||
|
NXDT_ASSERT(GameCardSpecificData, 0x200);
|
||||||
|
|
||||||
/// Encrypted using AES-128-ECB with the common titlekek generator key (stored in the .rodata segment from the Lotus firmware).
|
/// Encrypted using AES-128-ECB with the common titlekek generator key (stored in the .rodata segment from the Lotus firmware).
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u64 package_id; ///< Matches package_id from GameCardHeader.
|
u64 package_id; ///< Matches package_id from GameCardHeader.
|
||||||
|
@ -59,6 +73,18 @@ typedef struct {
|
||||||
|
|
||||||
NXDT_ASSERT(GameCardInitialData, 0x200);
|
NXDT_ASSERT(GameCardInitialData, 0x200);
|
||||||
|
|
||||||
|
/// Plaintext area. Dumped from FS program memory.
|
||||||
|
/// This struct is returned by Lotus command "ChangeToSecureMode" (0xF). This means it is only available *after* the gamecard secure area has been mounted.
|
||||||
|
/// A copy of the gamecard header without the RSA-2048 signature and a plaintext GameCardInfo precedes this struct in FS program memory.
|
||||||
|
typedef struct {
|
||||||
|
GameCardSpecificData specific_data;
|
||||||
|
FsGameCardCertificate certificate;
|
||||||
|
u8 reserved[0x200];
|
||||||
|
GameCardInitialData initial_data;
|
||||||
|
} GameCardSecurityInformation;
|
||||||
|
|
||||||
|
NXDT_ASSERT(GameCardSecurityInformation, 0x800);
|
||||||
|
|
||||||
/// Encrypted using AES-128-CTR with the key and IV/counter from the `GameCardTitleKeyAreaEncryption` section. Assumed to be all zeroes in retail gamecards.
|
/// Encrypted using AES-128-CTR with the key and IV/counter from the `GameCardTitleKeyAreaEncryption` section. Assumed to be all zeroes in retail gamecards.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u8 titlekey[0x10]; ///< Decrypted titlekey from the `GameCardInitialData` section.
|
u8 titlekey[0x10]; ///< Decrypted titlekey from the `GameCardInitialData` section.
|
||||||
|
@ -77,7 +103,7 @@ typedef struct {
|
||||||
NXDT_ASSERT(GameCardTitleKeyAreaEncryption, 0x100);
|
NXDT_ASSERT(GameCardTitleKeyAreaEncryption, 0x100);
|
||||||
|
|
||||||
/// Used to secure communications between the Lotus and the inserted gamecard.
|
/// Used to secure communications between the Lotus and the inserted gamecard.
|
||||||
/// Precedes the gamecard header.
|
/// Supposedly precedes the gamecard header.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
GameCardInitialData initial_data;
|
GameCardInitialData initial_data;
|
||||||
GameCardTitleKeyArea titlekey_area;
|
GameCardTitleKeyArea titlekey_area;
|
||||||
|
@ -86,30 +112,6 @@ typedef struct {
|
||||||
|
|
||||||
NXDT_ASSERT(GameCardKeyArea, 0x1000);
|
NXDT_ASSERT(GameCardKeyArea, 0x1000);
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
u32 asic_security_mode;
|
|
||||||
u32 asic_status;
|
|
||||||
u32 cardid1;
|
|
||||||
u32 cardid2;
|
|
||||||
u8 card_uid[0x40];
|
|
||||||
u8 reserved[0x190];
|
|
||||||
u8 asic_session_hash[0x20];
|
|
||||||
} GameCardSpecificData;
|
|
||||||
|
|
||||||
NXDT_ASSERT(GameCardSpecificData, 0x200);
|
|
||||||
|
|
||||||
/// This struct is returned by Lotus command "ChangeToSecureMode" (0xF)
|
|
||||||
/// A copy of the gamecard header without the RSA-2048 signature and a plaintext GameCardInfo precedes this struct in FS program memory.
|
|
||||||
typedef struct {
|
|
||||||
GameCardSpecificData specific_data;
|
|
||||||
FsGameCardCertificate certificate;
|
|
||||||
u8 ffpadding[0x200];
|
|
||||||
GameCardInitialData initial_data;
|
|
||||||
} GameCardSecurityInformation;
|
|
||||||
|
|
||||||
NXDT_ASSERT(GameCardSecurityInformation, 0x800);
|
|
||||||
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
GameCardKekIndex_Version0 = 0,
|
GameCardKekIndex_Version0 = 0,
|
||||||
GameCardKekIndex_VersionForDev = 1
|
GameCardKekIndex_VersionForDev = 1
|
||||||
|
@ -250,6 +252,7 @@ typedef enum {
|
||||||
LotusAsicDeviceType_Prod2Dev = 3
|
LotusAsicDeviceType_Prod2Dev = 3
|
||||||
} LotusAsicDeviceType;
|
} LotusAsicDeviceType;
|
||||||
|
|
||||||
|
/// Plaintext Lotus ASIC firmware (LAFW) blob. Dumped from FS program memory.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u8 signature[0x100];
|
u8 signature[0x100];
|
||||||
u32 magic; ///< "LAFW".
|
u32 magic; ///< "LAFW".
|
||||||
|
@ -265,10 +268,9 @@ typedef struct {
|
||||||
char placeholder_str[0x10]; ///< "IDIDIDIDIDIDIDID".
|
char placeholder_str[0x10]; ///< "IDIDIDIDIDIDIDID".
|
||||||
u8 reserved_3[0x40];
|
u8 reserved_3[0x40];
|
||||||
u8 data[0x7680];
|
u8 data[0x7680];
|
||||||
} LotusAsicFirmware;
|
} LotusAsicFirmwareBlob;
|
||||||
|
|
||||||
NXDT_ASSERT(LotusAsicFirmware, 0x7800);
|
|
||||||
|
|
||||||
|
NXDT_ASSERT(LotusAsicFirmwareBlob, 0x7800);
|
||||||
|
|
||||||
/// Initializes data needed to access raw gamecard storage areas.
|
/// Initializes data needed to access raw gamecard storage areas.
|
||||||
/// Also spans a background thread to automatically detect gamecard status changes and to cache data from the inserted gamecard.
|
/// Also spans a background thread to automatically detect gamecard status changes and to cache data from the inserted gamecard.
|
||||||
|
@ -285,16 +287,7 @@ UEvent *gamecardGetStatusChangeUserEvent(void);
|
||||||
/// Returns the current GameCardStatus value.
|
/// Returns the current GameCardStatus value.
|
||||||
u8 gamecardGetStatus(void);
|
u8 gamecardGetStatus(void);
|
||||||
|
|
||||||
/// Used to read raw data from the inserted gamecard. Supports unaligned reads.
|
/// Fills the provided GameCardSecurityInformation pointer.
|
||||||
/// All required handles, changes between normal <-> secure storage areas and proper offset calculations are managed internally.
|
|
||||||
/// 'offset' + 'read_size' must not exceed the value returned by gamecardGetTotalSize().
|
|
||||||
bool gamecardReadStorage(void *out, u64 read_size, u64 offset);
|
|
||||||
|
|
||||||
/// Fills the provided GameCardKeyArea pointer. Only GameCardInitialData data is retrieved at this moment.
|
|
||||||
/// This area can't be read using gamecardReadStorage().
|
|
||||||
bool gamecardGetKeyArea(GameCardKeyArea *out);
|
|
||||||
|
|
||||||
/// Fills the provided GameCardSecurityInformation pointer
|
|
||||||
/// This area can't be read using gamecardReadStorage().
|
/// This area can't be read using gamecardReadStorage().
|
||||||
bool gamecardGetSecurityInformation(GameCardSecurityInformation* out);
|
bool gamecardGetSecurityInformation(GameCardSecurityInformation* out);
|
||||||
|
|
||||||
|
@ -302,6 +295,15 @@ bool gamecardGetSecurityInformation(GameCardSecurityInformation* out);
|
||||||
/// This area can't be read using gamecardReadStorage().
|
/// This area can't be read using gamecardReadStorage().
|
||||||
bool gamecardGetIdSet(FsGameCardIdSet *out);
|
bool gamecardGetIdSet(FsGameCardIdSet *out);
|
||||||
|
|
||||||
|
/// Fills the provided pointers with LAFW blob data from FS program memory.
|
||||||
|
/// 'out_lafw_blob' or 'out_lafw_version' may be set to NULL, but at least one of them must be a valid pointer.
|
||||||
|
bool gamecardGetLotusAsicFirmwareBlob(LotusAsicFirmwareBlob *out_lafw_blob, u64 *out_lafw_version);
|
||||||
|
|
||||||
|
/// Used to read raw data from the inserted gamecard. Supports unaligned reads.
|
||||||
|
/// All required handles, changes between normal <-> secure storage areas and proper offset calculations are managed internally.
|
||||||
|
/// 'offset' + 'read_size' must not exceed the value returned by gamecardGetTotalSize().
|
||||||
|
bool gamecardReadStorage(void *out, u64 read_size, u64 offset);
|
||||||
|
|
||||||
/// Fills the provided GameCardHeader pointer.
|
/// Fills the provided GameCardHeader pointer.
|
||||||
/// This area can also be read using gamecardReadStorage(), starting at offset 0.
|
/// This area can also be read using gamecardReadStorage(), starting at offset 0.
|
||||||
bool gamecardGetHeader(GameCardHeader *out);
|
bool gamecardGetHeader(GameCardHeader *out);
|
||||||
|
@ -342,11 +344,9 @@ const char *gamecardGetRequiredHosVersionString(u64 fw_version);
|
||||||
/// Returns NULL if the provided value is out of range.
|
/// Returns NULL if the provided value is out of range.
|
||||||
const char *gamecardGetCompatibilityTypeString(u8 compatibility_type);
|
const char *gamecardGetCompatibilityTypeString(u8 compatibility_type);
|
||||||
|
|
||||||
/// Fills the provided LotusAsicFirmware pointer with FS's current LAFW blob.
|
/// Takes a LotusAsicFirmwareType value. Returns a pointer to a string that represents the provided LAFW type.
|
||||||
bool gamecardGetLotusAsicFirmware(LotusAsicFirmware* out);
|
/// Returns NULL if the provided value is invalid.
|
||||||
|
const char *gamecardGetLafwTypeString(u32 lafw_type);
|
||||||
/// Convert LAFW version bitmask to an integer
|
|
||||||
u32 gamecardConvertLotusAsicFirmwareVersionBitmask(LotusAsicFirmware* lafw);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,7 @@ static FsEventNotifier g_gameCardEventNotifier = {0};
|
||||||
static Event g_gameCardKernelEvent = {0};
|
static Event g_gameCardKernelEvent = {0};
|
||||||
static bool g_openDeviceOperator = false, g_openEventNotifier = false, g_loadKernelEvent = false;
|
static bool g_openDeviceOperator = false, g_openEventNotifier = false, g_loadKernelEvent = false;
|
||||||
|
|
||||||
|
static LotusAsicFirmwareBlob *g_lafwBlob = NULL;
|
||||||
static u64 g_lafwVersion = 0;
|
static u64 g_lafwVersion = 0;
|
||||||
|
|
||||||
static Thread g_gameCardDetectionThread = {0};
|
static Thread g_gameCardDetectionThread = {0};
|
||||||
|
@ -119,7 +120,7 @@ static const char *g_gameCardCompatibilityTypeStrings[GameCardCompatibilityType_
|
||||||
|
|
||||||
/* Function prototypes. */
|
/* Function prototypes. */
|
||||||
|
|
||||||
static bool gamecardGetLotusAsicFirmwareVersion(void);
|
static bool gamecardReadLotusAsicFirmwareBlob(void);
|
||||||
|
|
||||||
static bool gamecardCreateDetectionThread(void);
|
static bool gamecardCreateDetectionThread(void);
|
||||||
static void gamecardDestroyDetectionThread(void);
|
static void gamecardDestroyDetectionThread(void);
|
||||||
|
@ -203,8 +204,8 @@ bool gamecardInitialize(void)
|
||||||
/* Create user-mode gamecard status change event. */
|
/* Create user-mode gamecard status change event. */
|
||||||
ueventCreate(&g_gameCardStatusChangeEvent, true);
|
ueventCreate(&g_gameCardStatusChangeEvent, true);
|
||||||
|
|
||||||
/* Retrieve LAFW version. */
|
/* Retrieve LAFW blob. */
|
||||||
if (!gamecardGetLotusAsicFirmwareVersion()) break;
|
if (!gamecardReadLotusAsicFirmwareBlob()) break;
|
||||||
|
|
||||||
/* Create gamecard detection thread. */
|
/* Create gamecard detection thread. */
|
||||||
if (!(g_gameCardDetectionThreadCreated = gamecardCreateDetectionThread())) break;
|
if (!(g_gameCardDetectionThreadCreated = gamecardCreateDetectionThread())) break;
|
||||||
|
@ -227,6 +228,13 @@ void gamecardExit(void)
|
||||||
g_gameCardDetectionThreadCreated = false;
|
g_gameCardDetectionThreadCreated = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Free LAFW blob buffer. */
|
||||||
|
if (g_lafwBlob)
|
||||||
|
{
|
||||||
|
free(g_lafwBlob);
|
||||||
|
g_lafwBlob = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* Close gamecard detection kernel event. */
|
/* Close gamecard detection kernel event. */
|
||||||
if (g_loadKernelEvent)
|
if (g_loadKernelEvent)
|
||||||
{
|
{
|
||||||
|
@ -283,30 +291,6 @@ u8 gamecardGetStatus(void)
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool gamecardReadStorage(void *out, u64 read_size, u64 offset)
|
|
||||||
{
|
|
||||||
bool ret = false;
|
|
||||||
SCOPED_LOCK(&g_gameCardMutex) ret = gamecardReadStorageArea(out, read_size, offset);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read full FS program memory by calling gamecardGetSecurityInformation, and then extracts out the GameCardInitialData block. */
|
|
||||||
bool gamecardGetKeyArea(GameCardKeyArea *out)
|
|
||||||
{
|
|
||||||
GameCardSecurityInformation securityInformation;
|
|
||||||
|
|
||||||
/* Clear output. */
|
|
||||||
memset(out, 0, sizeof(GameCardKeyArea));
|
|
||||||
|
|
||||||
if (!gamecardGetSecurityInformation(&securityInformation)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(&out->initial_data, &securityInformation.initial_data, sizeof(GameCardInitialData));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read full FS program memory to retrieve the GameCardSecurityInformation block. */
|
/* Read full FS program memory to retrieve the GameCardSecurityInformation block. */
|
||||||
/* In FS program memory, this is returned by Lotus command "ChangeToSecureMode" (0xF). */
|
/* In FS program memory, this is returned by Lotus command "ChangeToSecureMode" (0xF). */
|
||||||
/* This means it is only available *after* the gamecard secure area has been mounted, which is taken care of in gamecardReadSecurityInformation(). */
|
/* This means it is only available *after* the gamecard secure area has been mounted, which is taken care of in gamecardReadSecurityInformation(). */
|
||||||
|
@ -334,6 +318,33 @@ bool gamecardGetIdSet(FsGameCardIdSet *out)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool gamecardGetLotusAsicFirmwareBlob(LotusAsicFirmwareBlob *out_lafw_blob, u64 *out_lafw_version)
|
||||||
|
{
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
SCOPED_LOCK(&g_gameCardMutex)
|
||||||
|
{
|
||||||
|
if (!g_gameCardInterfaceInit || !g_lafwBlob || (!out_lafw_blob && !out_lafw_version)) break;
|
||||||
|
|
||||||
|
/* Copy LAFW blob data. */
|
||||||
|
if (out_lafw_blob) memcpy(out_lafw_blob, g_lafwBlob, sizeof(LotusAsicFirmwareBlob));
|
||||||
|
|
||||||
|
/* Copy LAFW version. */
|
||||||
|
if (out_lafw_version) *out_lafw_version = g_lafwVersion;
|
||||||
|
|
||||||
|
ret = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gamecardReadStorage(void *out, u64 read_size, u64 offset)
|
||||||
|
{
|
||||||
|
bool ret = false;
|
||||||
|
SCOPED_LOCK(&g_gameCardMutex) ret = gamecardReadStorageArea(out, read_size, offset);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
bool gamecardGetHeader(GameCardHeader *out)
|
bool gamecardGetHeader(GameCardHeader *out)
|
||||||
{
|
{
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
@ -529,12 +540,43 @@ const char *gamecardGetCompatibilityTypeString(u8 compatibility_type)
|
||||||
return (compatibility_type < GameCardCompatibilityType_Count ? g_gameCardCompatibilityTypeStrings[compatibility_type] : NULL);
|
return (compatibility_type < GameCardCompatibilityType_Count ? g_gameCardCompatibilityTypeStrings[compatibility_type] : NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool gamecardGetLotusAsicFirmware(LotusAsicFirmware* out)
|
const char *gamecardGetLafwTypeString(u32 lafw_type)
|
||||||
{
|
{
|
||||||
bool ret = false;
|
const char *type = NULL;
|
||||||
SCOPED_LOCK(&g_gameCardMutex)
|
|
||||||
|
switch(lafw_type)
|
||||||
{
|
{
|
||||||
bool found = false, dev_unit = utilsIsDevelopmentUnit();
|
case LotusAsicFirmwareType_ReadFw:
|
||||||
|
type = "ReadFw";
|
||||||
|
break;
|
||||||
|
case LotusAsicFirmwareType_ReadDevFw:
|
||||||
|
type = "ReadDevFw";
|
||||||
|
break;
|
||||||
|
case LotusAsicFirmwareType_WriterFw:
|
||||||
|
type = "WriterFw";
|
||||||
|
break;
|
||||||
|
case LotusAsicFirmwareType_RmaFw:
|
||||||
|
type = "RmaFw";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool gamecardReadLotusAsicFirmwareBlob(void)
|
||||||
|
{
|
||||||
|
u64 fw_version = 0;
|
||||||
|
bool ret = false, found = false, dev_unit = utilsIsDevelopmentUnit();
|
||||||
|
|
||||||
|
/* Allocate memory for the LAFW blob. */
|
||||||
|
g_lafwBlob = calloc(1, sizeof(LotusAsicFirmwareBlob));
|
||||||
|
if (!g_lafwBlob)
|
||||||
|
{
|
||||||
|
LOG_MSG("Failed to allocate memory for LAFW blob!");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
/* Temporarily set the segment mask to .data. */
|
/* Temporarily set the segment mask to .data. */
|
||||||
g_fsProgramMemory.mask = MemoryProgramSegmentType_Data;
|
g_fsProgramMemory.mask = MemoryProgramSegmentType_Data;
|
||||||
|
@ -554,15 +596,16 @@ bool gamecardGetLotusAsicFirmware(LotusAsicFirmware* out)
|
||||||
/* Look for the LAFW ReadFw blob in the FS .data memory dump. */
|
/* Look for the LAFW ReadFw blob in the FS .data memory dump. */
|
||||||
for(u64 offset = 0; offset < g_fsProgramMemory.data_size; offset++)
|
for(u64 offset = 0; offset < g_fsProgramMemory.data_size; offset++)
|
||||||
{
|
{
|
||||||
if ((g_fsProgramMemory.data_size - offset) < sizeof(LotusAsicFirmware)) break;
|
if ((g_fsProgramMemory.data_size - offset) < sizeof(LotusAsicFirmwareBlob)) break;
|
||||||
|
|
||||||
LotusAsicFirmware *lafw_blob = (LotusAsicFirmware*)(g_fsProgramMemory.data + offset);
|
LotusAsicFirmwareBlob *lafw_blob = (LotusAsicFirmwareBlob*)(g_fsProgramMemory.data + offset);
|
||||||
u32 magic = __builtin_bswap32(lafw_blob->magic), fw_type = lafw_blob->fw_type;
|
u32 magic = __builtin_bswap32(lafw_blob->magic), fw_type = lafw_blob->fw_type;
|
||||||
|
|
||||||
if (magic == LAFW_MAGIC && ((!dev_unit && fw_type == LotusAsicFirmwareType_ReadFw) || (dev_unit && fw_type == LotusAsicFirmwareType_ReadDevFw)))
|
if (magic == LAFW_MAGIC && ((!dev_unit && fw_type == LotusAsicFirmwareType_ReadFw) || (dev_unit && fw_type == LotusAsicFirmwareType_ReadDevFw)))
|
||||||
{
|
{
|
||||||
/* Jackpot. */
|
/* Jackpot. */
|
||||||
memcpy(out, lafw_blob, sizeof(LotusAsicFirmware));
|
memcpy(g_lafwBlob, lafw_blob, sizeof(LotusAsicFirmwareBlob));
|
||||||
|
fw_version = lafw_blob->fw_version;
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -574,48 +617,23 @@ bool gamecardGetLotusAsicFirmware(LotusAsicFirmware* out)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update flag. */
|
|
||||||
ret = true;
|
|
||||||
|
|
||||||
end:
|
|
||||||
memFreeMemoryLocation(&g_fsProgramMemory);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 gamecardConvertLotusAsicFirmwareVersionBitmask(LotusAsicFirmware* lafw) {
|
|
||||||
u64 fw_version_bitmask = lafw->fw_version;
|
|
||||||
u32 fw_version = 0;
|
|
||||||
|
|
||||||
while(fw_version_bitmask)
|
|
||||||
{
|
|
||||||
fw_version += (fw_version_bitmask & 1);
|
|
||||||
fw_version_bitmask >>= 1;
|
|
||||||
}
|
|
||||||
return fw_version;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool gamecardGetLotusAsicFirmwareVersion(void)
|
|
||||||
{
|
|
||||||
bool ret = false;
|
|
||||||
LotusAsicFirmware lafw;
|
|
||||||
|
|
||||||
/* Retrieve LAFW. */
|
|
||||||
ret = gamecardGetLotusAsicFirmware(&lafw);
|
|
||||||
if (!ret)
|
|
||||||
{
|
|
||||||
LOG_MSG("Failed to retrieve Lotus Asic Firmware!");
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Convert LAFW version bitmask to an integer. */
|
/* Convert LAFW version bitmask to an integer. */
|
||||||
g_lafwVersion = gamecardConvertLotusAsicFirmwareVersionBitmask(&lafw);
|
g_lafwVersion = 0;
|
||||||
|
|
||||||
|
while(fw_version)
|
||||||
|
{
|
||||||
|
g_lafwVersion += (fw_version & 1);
|
||||||
|
fw_version >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
LOG_MSG("LAFW version: %lu.", g_lafwVersion);
|
LOG_MSG("LAFW version: %lu.", g_lafwVersion);
|
||||||
|
|
||||||
/* Update flag. */
|
/* Update flag. */
|
||||||
ret = true;
|
ret = true;
|
||||||
|
|
||||||
end:
|
end:
|
||||||
|
memFreeMemoryLocation(&g_fsProgramMemory);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -938,11 +956,12 @@ static bool gamecardReadSecurityInformation(GameCardSecurityInformation *out)
|
||||||
if (!memcmp(tmp_hash, g_gameCardHeader.initial_data_hash, SHA256_HASH_SIZE))
|
if (!memcmp(tmp_hash, g_gameCardHeader.initial_data_hash, SHA256_HASH_SIZE))
|
||||||
{
|
{
|
||||||
/* Jackpot. */
|
/* Jackpot. */
|
||||||
memcpy(out, g_fsProgramMemory.data + offset - 0x600, sizeof(GameCardSecurityInformation));
|
memcpy(out, g_fsProgramMemory.data + offset + sizeof(GameCardInitialData) - sizeof(GameCardSecurityInformation), sizeof(GameCardSecurityInformation));
|
||||||
|
|
||||||
|
/* Clear out the current ASIC session hash. */
|
||||||
|
/* It's not actually part of the gamecard data, and this changes every time a gamecard (re)insertion takes place. */
|
||||||
|
memset(out->specific_data.asic_session_hash, 0xFF, sizeof(out->specific_data.asic_session_hash));
|
||||||
|
|
||||||
// Clear out the asic session hash of the current Lotus session with the console.
|
|
||||||
// It's not actually part of the gamecard data, and this changes every time the gamecard is reinserted.
|
|
||||||
memset(out->specific_data.asic_session_hash, 0xFF, 32);
|
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue