Add SD card + Card ID Set support to gamecard PoC.

This commit is contained in:
Pablo Curiel 2022-02-04 03:04:43 +01:00
parent 8168a5ac84
commit 8c312ca636
4 changed files with 199 additions and 60 deletions

View file

@ -65,6 +65,7 @@ typedef struct
bool write_error; bool write_error;
bool transfer_cancelled; bool transfer_cancelled;
u32 xci_crc; u32 xci_crc;
FILE *fp;
} ThreadSharedData; } ThreadSharedData;
/* Function prototypes. */ /* Function prototypes. */
@ -73,12 +74,14 @@ static void consolePrint(const char *text, ...);
static u32 menuGetElementCount(const Menu *menu); static u32 menuGetElementCount(const Menu *menu);
static bool sendGameCardSpecificDataViaUsb(void); static bool saveGameCardSpecificData(void);
static bool sendGameCardCertificateViaUsb(void); static bool saveGameCardCertificate(void);
static bool sendGameCardInitialDataViaUsb(void); static bool saveGameCardInitialData(void);
static bool sendGameCardImageViaUsb(void); static bool saveGameCardIdSet(void);
static bool sendConsoleLafwBlobViaUsb(void); static bool saveGameCardImage(void);
static bool saveConsoleLafwBlob(void);
static void changeStorageOption(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);
@ -88,15 +91,16 @@ static void write_thread_func(void *arg);
/* Global variables. */ /* Global variables. */
static bool g_keepCertificate = false, g_trimDump = false, g_calcCrc = false; static bool g_useUsbHost = false, g_keepCertificate = false, g_trimDump = false, g_calcCrc = false;
static const char *g_storageOptions[] = { "sd card", "usb host", NULL };
static const char *g_xciOptions[] = { "no", "yes", NULL }; static const char *g_xciOptions[] = { "no", "yes", NULL };
static MenuElement *g_xciMenuElements[] = { static MenuElement *g_xciMenuElements[] = {
&(MenuElement){ &(MenuElement){
.str = "start dump", .str = "start dump",
.child_menu = NULL, .child_menu = NULL,
.task_func = &sendGameCardImageViaUsb, .task_func = &saveGameCardImage,
.element_options = NULL .element_options = NULL
}, },
&(MenuElement){ &(MenuElement){
@ -143,19 +147,25 @@ static MenuElement *g_rootMenuElements[] = {
&(MenuElement){ &(MenuElement){
.str = "dump gamecard specific data", .str = "dump gamecard specific data",
.child_menu = NULL, .child_menu = NULL,
.task_func = &sendGameCardSpecificDataViaUsb, .task_func = &saveGameCardSpecificData,
.element_options = NULL .element_options = NULL
}, },
&(MenuElement){ &(MenuElement){
.str = "dump gamecard certificate", .str = "dump gamecard certificate",
.child_menu = NULL, .child_menu = NULL,
.task_func = &sendGameCardCertificateViaUsb, .task_func = &saveGameCardCertificate,
.element_options = NULL .element_options = NULL
}, },
&(MenuElement){ &(MenuElement){
.str = "dump gamecard initial data", .str = "dump gamecard initial data",
.child_menu = NULL, .child_menu = NULL,
.task_func = &sendGameCardInitialDataViaUsb, .task_func = &saveGameCardInitialData,
.element_options = NULL
},
&(MenuElement){
.str = "dump gamecard id set",
.child_menu = NULL,
.task_func = &saveGameCardIdSet,
.element_options = NULL .element_options = NULL
}, },
&(MenuElement){ &(MenuElement){
@ -167,9 +177,19 @@ static MenuElement *g_rootMenuElements[] = {
&(MenuElement){ &(MenuElement){
.str = "dump console lafw blob", .str = "dump console lafw blob",
.child_menu = NULL, .child_menu = NULL,
.task_func = &sendConsoleLafwBlobViaUsb, .task_func = &saveConsoleLafwBlob,
.element_options = NULL .element_options = NULL
}, },
&(MenuElement){
.str = "output storage",
.child_menu = NULL,
.task_func = NULL,
.element_options = &(MenuElementOption){
.selected = 0,
.options_func = &changeStorageOption,
.options = g_storageOptions
}
},
NULL NULL
}; };
@ -234,6 +254,8 @@ int main(int argc, char *argv[])
consoleInit(NULL); consoleInit(NULL);
chdir(DEVOPTAB_SDMC_DEVICE GAMECARD_PATH);
while(appletMainLoop()) while(appletMainLoop())
{ {
consoleClear(); consoleClear();
@ -376,7 +398,7 @@ static u32 menuGetElementCount(const Menu *menu)
return cnt; return cnt;
} }
static bool waitForGameCardAndUsb(void) static bool waitForGameCard(void)
{ {
consoleClear(); consoleClear();
consolePrint("waiting for gamecard...\n"); consolePrint("waiting for gamecard...\n");
@ -406,34 +428,58 @@ static bool waitForGameCardAndUsb(void)
return false; return false;
} }
return true;
}
static void waitForUsb(void)
{
if (usbIsReady()) return;
consolePrint("waiting for usb session...\n"); consolePrint("waiting for usb session...\n");
while(true) while(true)
{ {
if (usbIsReady()) break; if (usbIsReady()) break;
} }
return true;
} }
static bool sendFileData(const char *path, void *data, size_t data_size) static bool saveFileData(const char *path, void *data, size_t data_size)
{ {
if (!path || !strlen(path) || !data || !data_size) if (!path || !strlen(path) || !data || !data_size)
{ {
consolePrint("invalid parameters to send file data!\n"); consolePrint("invalid parameters to save file data!\n");
return false; return false;
} }
if (!usbSendFilePropertiesCommon(data_size, path)) if (g_useUsbHost)
{ {
consolePrint("failed to send file properties for \"%s\"!\n", path); if (!usbSendFilePropertiesCommon(data_size, path))
return false; {
} consolePrint("failed to send file properties for \"%s\"!\n", path);
return false;
}
if (!usbSendFileData(data, data_size)) if (!usbSendFileData(data, data_size))
{ {
consolePrint("failed to send file data for \"%s\"!\n", path); consolePrint("failed to send file data for \"%s\"!\n", path);
return false; return false;
}
} else {
FILE *fp = fopen(path, "wb");
if (!fp)
{
consolePrint("failed to open \"%s\" for writing!\n", path);
return false;
}
size_t ret = fwrite(data, 1, data_size, fp);
fclose(fp);
if (ret != data_size)
{
consolePrint("failed to write 0x%lX byte(s) to \"%s\"! (%d)\n", data_size, path, errno);
remove(path);
}
} }
return true; return true;
@ -457,25 +503,26 @@ static bool dumpGameCardSecurityInformation(GameCardSecurityInformation *out)
return true; return true;
} }
static bool sendGameCardSpecificDataViaUsb(void) static bool saveGameCardSpecificData(void)
{ {
if (!waitForGameCardAndUsb()) return false; if (!waitForGameCard()) return false;
if (g_useUsbHost) waitForUsb();
utilsSetLongRunningProcessState(true); utilsSetLongRunningProcessState(true);
GameCardSecurityInformation gc_security_information = {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); char *filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, g_useUsbHost ? TitleFileNameIllegalCharReplaceType_IllegalFsChars : TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly);
if (!dumpGameCardSecurityInformation(&gc_security_information) || !filename) goto end; if (!dumpGameCardSecurityInformation(&gc_security_information) || !filename) goto end;
crc = crc32Calculate(&(gc_security_information.specific_data), sizeof(GameCardSpecificData)); crc = crc32Calculate(&(gc_security_information.specific_data), sizeof(GameCardSpecificData));
snprintf(path, MAX_ELEMENTS(path), "%s (Specific Data) (%08X).bin", filename, crc); snprintf(path, MAX_ELEMENTS(path), "%s (Specific Data) (%08X).bin", filename, crc);
if (!sendFileData(path, &(gc_security_information.specific_data), sizeof(GameCardSpecificData))) goto end; if (!saveFileData(path, &(gc_security_information.specific_data), sizeof(GameCardSpecificData))) goto end;
printf("successfully sent specific data as \"%s\"\n", path); printf("successfully saved specific data as \"%s\"\n", path);
success = true; success = true;
end: end:
@ -489,16 +536,17 @@ end:
return success; return success;
} }
static bool sendGameCardCertificateViaUsb(void) static bool saveGameCardCertificate(void)
{ {
if (!waitForGameCardAndUsb()) return false; if (!waitForGameCard()) return false;
if (g_useUsbHost) waitForUsb();
utilsSetLongRunningProcessState(true); utilsSetLongRunningProcessState(true);
FsGameCardCertificate gc_cert = {0}; FsGameCardCertificate gc_cert = {0};
bool success = false; bool success = false;
u32 crc = 0; u32 crc = 0;
char *filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, TitleFileNameIllegalCharReplaceType_IllegalFsChars); char *filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, g_useUsbHost ? TitleFileNameIllegalCharReplaceType_IllegalFsChars : TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly);
if (!gamecardGetCertificate(&gc_cert) || !filename) if (!gamecardGetCertificate(&gc_cert) || !filename)
{ {
@ -511,9 +559,9 @@ static bool sendGameCardCertificateViaUsb(void)
crc = crc32Calculate(&gc_cert, sizeof(FsGameCardCertificate)); crc = crc32Calculate(&gc_cert, sizeof(FsGameCardCertificate));
snprintf(path, MAX_ELEMENTS(path), "%s (Certificate) (%08X).bin", filename, crc); snprintf(path, MAX_ELEMENTS(path), "%s (Certificate) (%08X).bin", filename, crc);
if (!sendFileData(path, &gc_cert, sizeof(FsGameCardCertificate))) goto end; if (!saveFileData(path, &gc_cert, sizeof(FsGameCardCertificate))) goto end;
printf("successfully sent certificate as \"%s\"\n", path); printf("successfully saved certificate as \"%s\"\n", path);
success = true; success = true;
end: end:
@ -527,25 +575,26 @@ end:
return success; return success;
} }
static bool sendGameCardInitialDataViaUsb(void) static bool saveGameCardInitialData(void)
{ {
if (!waitForGameCardAndUsb()) return false; if (!waitForGameCard()) return false;
if (g_useUsbHost) waitForUsb();
utilsSetLongRunningProcessState(true); utilsSetLongRunningProcessState(true);
GameCardSecurityInformation gc_security_information = {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); char *filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, g_useUsbHost ? TitleFileNameIllegalCharReplaceType_IllegalFsChars : TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly);
if (!dumpGameCardSecurityInformation(&gc_security_information) || !filename) goto end; if (!dumpGameCardSecurityInformation(&gc_security_information) || !filename) goto end;
crc = crc32Calculate(&(gc_security_information.initial_data), sizeof(GameCardInitialData)); crc = crc32Calculate(&(gc_security_information.initial_data), sizeof(GameCardInitialData));
snprintf(path, MAX_ELEMENTS(path), "%s (Initial Data) (%08X).bin", filename, crc); snprintf(path, MAX_ELEMENTS(path), "%s (Initial Data) (%08X).bin", filename, crc);
if (!sendFileData(path, &(gc_security_information.initial_data), sizeof(GameCardInitialData))) goto end; if (!saveFileData(path, &(gc_security_information.initial_data), sizeof(GameCardInitialData))) goto end;
printf("successfully sent initial data as \"%s\"\n", path); printf("successfully saved initial data as \"%s\"\n", path);
success = true; success = true;
end: end:
@ -559,9 +608,43 @@ end:
return success; return success;
} }
static bool sendGameCardImageViaUsb(void) static bool saveGameCardIdSet(void)
{ {
if (!waitForGameCardAndUsb()) return false; if (!waitForGameCard()) return false;
if (g_useUsbHost) waitForUsb();
utilsSetLongRunningProcessState(true);
FsGameCardIdSet id_set = {0};
bool success = false;
u32 crc = 0;
char *filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, g_useUsbHost ? TitleFileNameIllegalCharReplaceType_IllegalFsChars : TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly);
if (!gamecardGetIdSet(&id_set) || !filename) goto end;
crc = crc32Calculate(&id_set, sizeof(FsGameCardIdSet));
snprintf(path, MAX_ELEMENTS(path), "%s (Card ID Set) (%08X).bin", filename, crc);
if (!saveFileData(path, &id_set, sizeof(FsGameCardIdSet))) goto end;
printf("successfully saved gamecard id set as \"%s\"\n", path);
success = true;
end:
if (filename) free(filename);
utilsSetLongRunningProcessState(false);
consolePrint("press any button to continue");
utilsWaitForButtonPress(0);
return success;
}
static bool saveGameCardImage(void)
{
if (!waitForGameCard()) return false;
if (g_useUsbHost) waitForUsb();
utilsSetLongRunningProcessState(true); utilsSetLongRunningProcessState(true);
@ -574,9 +657,9 @@ static bool sendGameCardImageViaUsb(void)
bool success = false; bool success = false;
consolePrint("gamecard image dump\nkeep certificate: %s | trim dump: %s\n\n", g_keepCertificate ? "yes" : "no", g_trimDump ? "yes" : "no"); consolePrint("gamecard image dump\nkeep certificate: %s | trim dump: %s | calculate crc32: %s\n\n", g_keepCertificate ? "yes" : "no", g_trimDump ? "yes" : "no", g_calcCrc ? "yes" : "no");
filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, TitleFileNameIllegalCharReplaceType_IllegalFsChars); filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, g_useUsbHost ? TitleFileNameIllegalCharReplaceType_IllegalFsChars : TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly);
if (!filename) if (!filename)
{ {
consolePrint("failed to generate gamecard filename!\n"); consolePrint("failed to generate gamecard filename!\n");
@ -601,10 +684,26 @@ static bool sendGameCardImageViaUsb(void)
consolePrint("gamecard size: 0x%lX\n", gc_size); consolePrint("gamecard size: 0x%lX\n", gc_size);
snprintf(path, MAX_ELEMENTS(path), "%s (%s) (%s).xci", filename, g_keepCertificate ? "cert" : "certless", g_trimDump ? "trimmed" : "untrimmed"); snprintf(path, MAX_ELEMENTS(path), "%s (%s) (%s).xci", filename, g_keepCertificate ? "cert" : "certless", g_trimDump ? "trimmed" : "untrimmed");
if (!usbSendFilePropertiesCommon(gc_size, path))
if (g_useUsbHost && !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;
} else
if (!g_useUsbHost)
{
if (gc_size > FAT32_FILESIZE_LIMIT && !utilsCreateConcatenationFile(path))
{
consolePrint("failed to create concatenation file for \"%s\"!\n", path);
goto end;
}
shared_data.fp = fopen(path, "wb");
if (!shared_data.fp)
{
consolePrint("failed to open \"%s\" for writing!\n", path);
goto end;
}
} }
consolePrint("creating threads\n"); consolePrint("creating threads\n");
@ -674,7 +773,7 @@ static bool sendGameCardImageViaUsb(void)
if (shared_data.read_error || shared_data.write_error) if (shared_data.read_error || shared_data.write_error)
{ {
consolePrint("usb transfer error\n"); consolePrint("i/o error\n");
goto end; goto end;
} }
@ -690,6 +789,14 @@ static bool sendGameCardImageViaUsb(void)
if (g_calcCrc) printf("xci crc: %08X\n", shared_data.xci_crc); if (g_calcCrc) printf("xci crc: %08X\n", shared_data.xci_crc);
end: end:
if (shared_data.fp)
{
fclose(shared_data.fp);
shared_data.fp = NULL;
}
if (!success && !g_useUsbHost) utilsRemoveConcatenationFile(path);
if (shared_data.data) free(shared_data.data); if (shared_data.data) free(shared_data.data);
if (filename) free(filename); if (filename) free(filename);
@ -702,9 +809,10 @@ end:
return success; return success;
} }
static bool sendConsoleLafwBlobViaUsb(void) static bool saveConsoleLafwBlob(void)
{ {
if (!waitForGameCardAndUsb()) return false; if (!waitForGameCard()) return false;
if (g_useUsbHost) waitForUsb();
utilsSetLongRunningProcessState(true); utilsSetLongRunningProcessState(true);
@ -719,17 +827,20 @@ static bool sendConsoleLafwBlobViaUsb(void)
goto end; goto end;
} }
const char *type_str = gamecardGetLafwTypeString(lafw_blob.fw_type); const char *fw_type_str = gamecardGetLafwTypeString(lafw_blob.fw_type);
if (!type_str) type_str = "Unknown"; if (!fw_type_str) fw_type_str = "Unknown";
const char *dev_type_str = gamecardGetLafwDeviceTypeString(lafw_blob.device_type);
if (!dev_type_str) dev_type_str = "Unknown";
consolePrint("get console lafw blob ok\n"); consolePrint("get console lafw blob ok\n");
crc = crc32Calculate(&lafw_blob, sizeof(LotusAsicFirmwareBlob)); crc = crc32Calculate(&lafw_blob, sizeof(LotusAsicFirmwareBlob));
snprintf(path, MAX_ELEMENTS(path), "LAFW (%s) (v%lu) (%08X).bin", type_str, lafw_version, crc); snprintf(path, MAX_ELEMENTS(path), "LAFW (%s) (%s) (v%lu) (%08X).bin", fw_type_str, dev_type_str, lafw_version, crc);
if (!sendFileData(path, &lafw_blob, sizeof(LotusAsicFirmwareBlob))) goto end; if (!saveFileData(path, &lafw_blob, sizeof(LotusAsicFirmwareBlob))) goto end;
printf("successfully sent lafw blob as \"%s\"\n", path); printf("successfully saved lafw blob as \"%s\"\n", path);
success = true; success = true;
end: end:
@ -741,6 +852,11 @@ end:
return success; return success;
} }
static void changeStorageOption(u32 idx)
{
g_useUsbHost = (idx > 0);
}
static void changeCertificateOption(u32 idx) static void changeCertificateOption(u32 idx)
{ {
g_keepCertificate = (idx > 0); g_keepCertificate = (idx > 0);
@ -841,13 +957,19 @@ static void write_thread_func(void *arg)
if (shared_data->read_error || shared_data->transfer_cancelled) if (shared_data->read_error || shared_data->transfer_cancelled)
{ {
if (shared_data->transfer_cancelled) usbCancelFileTransfer(); if (shared_data->transfer_cancelled && g_useUsbHost) usbCancelFileTransfer();
mutexUnlock(&g_fileMutex); mutexUnlock(&g_fileMutex);
break; break;
} }
/* Write current file data chunk */ /* Write current file data chunk */
shared_data->write_error = !usbSendFileData(shared_data->data, shared_data->data_size); if (g_useUsbHost)
{
shared_data->write_error = !usbSendFileData(shared_data->data, shared_data->data_size);
} else {
shared_data->write_error = (fwrite(shared_data->data, 1, shared_data->data_size, shared_data->fp) != shared_data->data_size);
}
if (!shared_data->write_error) if (!shared_data->write_error)
{ {
shared_data->data_written += shared_data->data_size; shared_data->data_written += shared_data->data_size;

View file

@ -47,7 +47,7 @@ NXDT_ASSERT(FsGameCardCertificate, 0x200);
typedef struct { typedef struct {
u8 maker_code; ///< Usually 0xC2 (Macronix). u8 maker_code; ///< Usually 0xC2 (Macronix).
u8 memory_capacity; ///< Matches GameCardRomSize. u8 memory_capacity; ///< Matches GameCardRomSize.
u8 reserved; ///< Usually 0x0A / 0x09. u8 reserved; ///< Known values: 0x06, 0x09, 0x0A.
u8 memory_type; ///< Usually 0x21. u8 memory_type; ///< Usually 0x21.
} FsCardId1; } FsCardId1;

View file

@ -249,7 +249,8 @@ typedef enum {
LotusAsicDeviceType_Test = 0, LotusAsicDeviceType_Test = 0,
LotusAsicDeviceType_Dev = 1, LotusAsicDeviceType_Dev = 1,
LotusAsicDeviceType_Prod = 2, LotusAsicDeviceType_Prod = 2,
LotusAsicDeviceType_Prod2Dev = 3 LotusAsicDeviceType_Prod2Dev = 3,
LotusAsicDeviceType_Count = 4 ///< Not a real value.
} LotusAsicDeviceType; } LotusAsicDeviceType;
/// Plaintext Lotus ASIC firmware (LAFW) blob. Dumped from FS program memory. /// Plaintext Lotus ASIC firmware (LAFW) blob. Dumped from FS program memory.
@ -346,7 +347,11 @@ const char *gamecardGetCompatibilityTypeString(u8 compatibility_type);
/// Takes a LotusAsicFirmwareType value. Returns a pointer to a string that represents the provided LAFW type. /// Takes a LotusAsicFirmwareType value. Returns a pointer to a string that represents the provided LAFW type.
/// Returns NULL if the provided value is invalid. /// Returns NULL if the provided value is invalid.
const char *gamecardGetLafwTypeString(u32 lafw_type); const char *gamecardGetLafwTypeString(u32 fw_type);
/// Takes a LotusAsicDeviceType value. Returns a pointer to a string that represents the provided LAFW device type.
/// Returns NULL if the provided value is out of range.
const char *gamecardGetLafwDeviceTypeString(u64 device_type);
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -118,6 +118,13 @@ static const char *g_gameCardCompatibilityTypeStrings[GameCardCompatibilityType_
[GameCardCompatibilityType_Terra] = "Terra" [GameCardCompatibilityType_Terra] = "Terra"
}; };
static const char *g_lafwDeviceTypeStrings[LotusAsicDeviceType_Count] = {
[LotusAsicDeviceType_Test] = "Test",
[LotusAsicDeviceType_Dev] = "Dev",
[LotusAsicDeviceType_Prod] = "Prod",
[LotusAsicDeviceType_Prod2Dev] = "Prod2Dev"
};
/* Function prototypes. */ /* Function prototypes. */
static bool gamecardReadLotusAsicFirmwareBlob(void); static bool gamecardReadLotusAsicFirmwareBlob(void);
@ -540,11 +547,11 @@ 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);
} }
const char *gamecardGetLafwTypeString(u32 lafw_type) const char *gamecardGetLafwTypeString(u32 fw_type)
{ {
const char *type = NULL; const char *type = NULL;
switch(lafw_type) switch(fw_type)
{ {
case LotusAsicFirmwareType_ReadFw: case LotusAsicFirmwareType_ReadFw:
type = "ReadFw"; type = "ReadFw";
@ -565,6 +572,11 @@ const char *gamecardGetLafwTypeString(u32 lafw_type)
return type; return type;
} }
const char *gamecardGetLafwDeviceTypeString(u64 device_type)
{
return (device_type < LotusAsicDeviceType_Count ? g_lafwDeviceTypeStrings[device_type] : NULL);
}
static bool gamecardReadLotusAsicFirmwareBlob(void) static bool gamecardReadLotusAsicFirmwareBlob(void)
{ {
u64 fw_version = 0; u64 fw_version = 0;