mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-11-22 18:26:39 +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 write_error;
|
||||
bool transfer_cancelled;
|
||||
u32 xci_crc, full_xci_crc;
|
||||
u32 xci_crc;
|
||||
} ThreadSharedData;
|
||||
|
||||
/* Function prototypes. */
|
||||
|
@ -73,13 +73,12 @@ static void consolePrint(const char *text, ...);
|
|||
|
||||
static u32 menuGetElementCount(const Menu *menu);
|
||||
|
||||
static bool sendGameCardKeyAreaViaUsb(void);
|
||||
static bool sendGameCardSpecificDataViaUsb(void);
|
||||
static bool sendGameCardCertificateViaUsb(void);
|
||||
static bool sendGameCardInitialDataViaUsb(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 changeTrimOption(u32 idx);
|
||||
static void changeCrcOption(u32 idx);
|
||||
|
@ -89,7 +88,7 @@ static void write_thread_func(void *arg);
|
|||
|
||||
/* 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 };
|
||||
|
||||
|
@ -100,16 +99,6 @@ static MenuElement *g_xciMenuElements[] = {
|
|||
.task_func = &sendGameCardImageViaUsb,
|
||||
.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){
|
||||
.str = "keep certificate",
|
||||
.child_menu = NULL,
|
||||
|
@ -151,12 +140,6 @@ static Menu g_xciMenu = {
|
|||
};
|
||||
|
||||
static MenuElement *g_rootMenuElements[] = {
|
||||
&(MenuElement){
|
||||
.str = "dump gamecard initial data",
|
||||
.child_menu = NULL,
|
||||
.task_func = &sendGameCardKeyAreaViaUsb,
|
||||
.element_options = NULL
|
||||
},
|
||||
&(MenuElement){
|
||||
.str = "dump gamecard specific data",
|
||||
.child_menu = NULL,
|
||||
|
@ -169,6 +152,12 @@ static MenuElement *g_rootMenuElements[] = {
|
|||
.task_func = &sendGameCardCertificateViaUsb,
|
||||
.element_options = NULL
|
||||
},
|
||||
&(MenuElement){
|
||||
.str = "dump gamecard initial data",
|
||||
.child_menu = NULL,
|
||||
.task_func = &sendGameCardInitialDataViaUsb,
|
||||
.element_options = NULL
|
||||
},
|
||||
&(MenuElement){
|
||||
.str = "dump gamecard xci",
|
||||
.child_menu = &g_xciMenu,
|
||||
|
@ -176,9 +165,9 @@ static MenuElement *g_rootMenuElements[] = {
|
|||
.element_options = NULL
|
||||
},
|
||||
&(MenuElement){
|
||||
.str = "dump console LAFW",
|
||||
.str = "dump console lafw blob",
|
||||
.child_menu = NULL,
|
||||
.task_func = &sendConsoleLafwViaUsb,
|
||||
.task_func = &sendConsoleLafwBlobViaUsb,
|
||||
.element_options = NULL
|
||||
},
|
||||
NULL
|
||||
|
@ -450,24 +439,6 @@ static bool sendFileData(const char *path, void *data, size_t data_size)
|
|||
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)
|
||||
{
|
||||
if (!out)
|
||||
|
@ -475,81 +446,46 @@ static bool dumpGameCardSecurityInformation(GameCardSecurityInformation *out)
|
|||
consolePrint("invalid parameters to dump gamecard security information!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!gamecardGetSecurityInformation(out))
|
||||
{
|
||||
consolePrint("failed to get gamecard security information\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
consolePrint("get gamecard security information ok\n");
|
||||
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)
|
||||
{
|
||||
if (!waitForGameCardAndUsb()) return false;
|
||||
|
||||
|
||||
utilsSetLongRunningProcessState(true);
|
||||
|
||||
|
||||
GameCardSecurityInformation gc_security_information = {0};
|
||||
bool success = false;
|
||||
u32 crc = 0;
|
||||
char *filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, TitleFileNameIllegalCharReplaceType_IllegalFsChars);
|
||||
|
||||
|
||||
if (!dumpGameCardSecurityInformation(&gc_security_information) || !filename) goto end;
|
||||
|
||||
|
||||
crc = crc32Calculate(&(gc_security_information.specific_data), sizeof(GameCardSpecificData));
|
||||
snprintf(path, MAX_ELEMENTS(path), "%s (Specific Data) (%08X).bin", filename, crc);
|
||||
|
||||
|
||||
if (!sendFileData(path, &(gc_security_information.specific_data), sizeof(GameCardSpecificData))) goto end;
|
||||
|
||||
|
||||
printf("successfully sent specific data as \"%s\"\n", path);
|
||||
success = true;
|
||||
|
||||
|
||||
end:
|
||||
if (filename) free(filename);
|
||||
|
||||
|
||||
utilsSetLongRunningProcessState(false);
|
||||
|
||||
|
||||
consolePrint("press any button to continue");
|
||||
utilsWaitForButtonPress(0);
|
||||
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
@ -591,44 +527,30 @@ end:
|
|||
return success;
|
||||
}
|
||||
|
||||
static bool sendConsoleLafwViaUsb(void)
|
||||
static bool sendGameCardInitialDataViaUsb(void)
|
||||
{
|
||||
if (!waitForGameCardAndUsb()) return false;
|
||||
|
||||
utilsSetLongRunningProcessState(true);
|
||||
|
||||
LotusAsicFirmware lafw = {0};
|
||||
GameCardSecurityInformation gc_security_information = {0};
|
||||
bool success = false;
|
||||
u32 crc = 0;
|
||||
char *filename = titleGenerateGameCardFileName(TitleNamingConvention_Full, TitleFileNameIllegalCharReplaceType_IllegalFsChars);
|
||||
|
||||
if (!gamecardGetLotusAsicFirmware(&lafw))
|
||||
{
|
||||
consolePrint("failed to get console LAFW\n");
|
||||
goto end;
|
||||
}
|
||||
if (!dumpGameCardSecurityInformation(&gc_security_information) || !filename) 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 = "";
|
||||
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;
|
||||
}
|
||||
if (!sendFileData(path, &(gc_security_information.initial_data), sizeof(GameCardInitialData))) goto end;
|
||||
|
||||
consolePrint("get console LAFW ok\n");
|
||||
|
||||
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);
|
||||
printf("successfully sent initial data as \"%s\"\n", path);
|
||||
success = true;
|
||||
|
||||
end:
|
||||
if (filename) free(filename);
|
||||
|
||||
utilsSetLongRunningProcessState(false);
|
||||
|
||||
consolePrint("press any button to continue");
|
||||
|
@ -637,7 +559,6 @@ end:
|
|||
return success;
|
||||
}
|
||||
|
||||
|
||||
static bool sendGameCardImageViaUsb(void)
|
||||
{
|
||||
if (!waitForGameCardAndUsb()) return false;
|
||||
|
@ -645,8 +566,6 @@ static bool sendGameCardImageViaUsb(void)
|
|||
utilsSetLongRunningProcessState(true);
|
||||
|
||||
u64 gc_size = 0;
|
||||
u32 key_area_crc = 0;
|
||||
GameCardKeyArea gc_key_area = {0};
|
||||
|
||||
ThreadSharedData shared_data = {0};
|
||||
Thread read_thread = {0}, write_thread = {0};
|
||||
|
@ -655,7 +574,7 @@ static bool sendGameCardImageViaUsb(void)
|
|||
|
||||
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);
|
||||
if (!filename)
|
||||
|
@ -681,34 +600,13 @@ static bool sendGameCardImageViaUsb(void)
|
|||
|
||||
consolePrint("gamecard size: 0x%lX\n", gc_size);
|
||||
|
||||
if (g_appendKeyArea)
|
||||
{
|
||||
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");
|
||||
snprintf(path, MAX_ELEMENTS(path), "%s (%s) (%s).xci", filename, g_keepCertificate ? "cert" : "certless", g_trimDump ? "trimmed" : "untrimmed");
|
||||
if (!usbSendFilePropertiesCommon(gc_size, path))
|
||||
{
|
||||
consolePrint("failed to send file properties for \"%s\"!\n", path);
|
||||
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");
|
||||
utilsCreateThread(&read_thread, read_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);
|
||||
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");
|
||||
}
|
||||
if (g_calcCrc) printf("xci crc: %08X\n", shared_data.xci_crc);
|
||||
|
||||
end:
|
||||
if (shared_data.data) free(shared_data.data);
|
||||
|
@ -810,9 +702,43 @@ end:
|
|||
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)
|
||||
|
@ -869,11 +795,7 @@ static void read_thread_func(void *arg)
|
|||
if (!g_keepCertificate && offset == 0) memset(buf + GAMECARD_CERTIFICATE_OFFSET, 0xFF, sizeof(FsGameCardCertificate));
|
||||
|
||||
/* Update checksum */
|
||||
if (g_calcCrc)
|
||||
{
|
||||
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);
|
||||
}
|
||||
if (g_calcCrc) shared_data->xci_crc = crc32CalculateWithSeed(shared_data->xci_crc, buf, blksize);
|
||||
|
||||
/* Wait until the previous data chunk has been written */
|
||||
mutexLock(&g_fileMutex);
|
||||
|
|
|
@ -40,6 +40,20 @@ extern "C" {
|
|||
|
||||
#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).
|
||||
typedef struct {
|
||||
u64 package_id; ///< Matches package_id from GameCardHeader.
|
||||
|
@ -59,6 +73,18 @@ typedef struct {
|
|||
|
||||
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.
|
||||
typedef struct {
|
||||
u8 titlekey[0x10]; ///< Decrypted titlekey from the `GameCardInitialData` section.
|
||||
|
@ -77,7 +103,7 @@ typedef struct {
|
|||
NXDT_ASSERT(GameCardTitleKeyAreaEncryption, 0x100);
|
||||
|
||||
/// Used to secure communications between the Lotus and the inserted gamecard.
|
||||
/// Precedes the gamecard header.
|
||||
/// Supposedly precedes the gamecard header.
|
||||
typedef struct {
|
||||
GameCardInitialData initial_data;
|
||||
GameCardTitleKeyArea titlekey_area;
|
||||
|
@ -86,30 +112,6 @@ typedef struct {
|
|||
|
||||
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 {
|
||||
GameCardKekIndex_Version0 = 0,
|
||||
GameCardKekIndex_VersionForDev = 1
|
||||
|
@ -250,6 +252,7 @@ typedef enum {
|
|||
LotusAsicDeviceType_Prod2Dev = 3
|
||||
} LotusAsicDeviceType;
|
||||
|
||||
/// Plaintext Lotus ASIC firmware (LAFW) blob. Dumped from FS program memory.
|
||||
typedef struct {
|
||||
u8 signature[0x100];
|
||||
u32 magic; ///< "LAFW".
|
||||
|
@ -265,10 +268,9 @@ typedef struct {
|
|||
char placeholder_str[0x10]; ///< "IDIDIDIDIDIDIDID".
|
||||
u8 reserved_3[0x40];
|
||||
u8 data[0x7680];
|
||||
} LotusAsicFirmware;
|
||||
|
||||
NXDT_ASSERT(LotusAsicFirmware, 0x7800);
|
||||
} LotusAsicFirmwareBlob;
|
||||
|
||||
NXDT_ASSERT(LotusAsicFirmwareBlob, 0x7800);
|
||||
|
||||
/// 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.
|
||||
|
@ -285,16 +287,7 @@ UEvent *gamecardGetStatusChangeUserEvent(void);
|
|||
/// Returns the current GameCardStatus value.
|
||||
u8 gamecardGetStatus(void);
|
||||
|
||||
/// 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 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
|
||||
/// Fills the provided GameCardSecurityInformation pointer.
|
||||
/// This area can't be read using gamecardReadStorage().
|
||||
bool gamecardGetSecurityInformation(GameCardSecurityInformation* out);
|
||||
|
||||
|
@ -302,6 +295,15 @@ bool gamecardGetSecurityInformation(GameCardSecurityInformation* out);
|
|||
/// This area can't be read using gamecardReadStorage().
|
||||
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.
|
||||
/// This area can also be read using gamecardReadStorage(), starting at offset 0.
|
||||
bool gamecardGetHeader(GameCardHeader *out);
|
||||
|
@ -342,11 +344,9 @@ const char *gamecardGetRequiredHosVersionString(u64 fw_version);
|
|||
/// Returns NULL if the provided value is out of range.
|
||||
const char *gamecardGetCompatibilityTypeString(u8 compatibility_type);
|
||||
|
||||
/// Fills the provided LotusAsicFirmware pointer with FS's current LAFW blob.
|
||||
bool gamecardGetLotusAsicFirmware(LotusAsicFirmware* out);
|
||||
|
||||
/// Convert LAFW version bitmask to an integer
|
||||
u32 gamecardConvertLotusAsicFirmwareVersionBitmask(LotusAsicFirmware* lafw);
|
||||
/// Takes a LotusAsicFirmwareType value. Returns a pointer to a string that represents the provided LAFW type.
|
||||
/// Returns NULL if the provided value is invalid.
|
||||
const char *gamecardGetLafwTypeString(u32 lafw_type);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
@ -66,6 +66,7 @@ static FsEventNotifier g_gameCardEventNotifier = {0};
|
|||
static Event g_gameCardKernelEvent = {0};
|
||||
static bool g_openDeviceOperator = false, g_openEventNotifier = false, g_loadKernelEvent = false;
|
||||
|
||||
static LotusAsicFirmwareBlob *g_lafwBlob = NULL;
|
||||
static u64 g_lafwVersion = 0;
|
||||
|
||||
static Thread g_gameCardDetectionThread = {0};
|
||||
|
@ -119,7 +120,7 @@ static const char *g_gameCardCompatibilityTypeStrings[GameCardCompatibilityType_
|
|||
|
||||
/* Function prototypes. */
|
||||
|
||||
static bool gamecardGetLotusAsicFirmwareVersion(void);
|
||||
static bool gamecardReadLotusAsicFirmwareBlob(void);
|
||||
|
||||
static bool gamecardCreateDetectionThread(void);
|
||||
static void gamecardDestroyDetectionThread(void);
|
||||
|
@ -203,8 +204,8 @@ bool gamecardInitialize(void)
|
|||
/* Create user-mode gamecard status change event. */
|
||||
ueventCreate(&g_gameCardStatusChangeEvent, true);
|
||||
|
||||
/* Retrieve LAFW version. */
|
||||
if (!gamecardGetLotusAsicFirmwareVersion()) break;
|
||||
/* Retrieve LAFW blob. */
|
||||
if (!gamecardReadLotusAsicFirmwareBlob()) break;
|
||||
|
||||
/* Create gamecard detection thread. */
|
||||
if (!(g_gameCardDetectionThreadCreated = gamecardCreateDetectionThread())) break;
|
||||
|
@ -227,6 +228,13 @@ void gamecardExit(void)
|
|||
g_gameCardDetectionThreadCreated = false;
|
||||
}
|
||||
|
||||
/* Free LAFW blob buffer. */
|
||||
if (g_lafwBlob)
|
||||
{
|
||||
free(g_lafwBlob);
|
||||
g_lafwBlob = NULL;
|
||||
}
|
||||
|
||||
/* Close gamecard detection kernel event. */
|
||||
if (g_loadKernelEvent)
|
||||
{
|
||||
|
@ -283,30 +291,6 @@ u8 gamecardGetStatus(void)
|
|||
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. */
|
||||
/* 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(). */
|
||||
|
@ -334,6 +318,33 @@ bool gamecardGetIdSet(FsGameCardIdSet *out)
|
|||
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 ret = false;
|
||||
|
@ -529,93 +540,100 @@ const char *gamecardGetCompatibilityTypeString(u8 compatibility_type)
|
|||
return (compatibility_type < GameCardCompatibilityType_Count ? g_gameCardCompatibilityTypeStrings[compatibility_type] : NULL);
|
||||
}
|
||||
|
||||
bool gamecardGetLotusAsicFirmware(LotusAsicFirmware* out)
|
||||
const char *gamecardGetLafwTypeString(u32 lafw_type)
|
||||
{
|
||||
bool ret = false;
|
||||
SCOPED_LOCK(&g_gameCardMutex)
|
||||
const char *type = NULL;
|
||||
|
||||
switch(lafw_type)
|
||||
{
|
||||
bool found = false, dev_unit = utilsIsDevelopmentUnit();
|
||||
|
||||
/* Temporarily set the segment mask to .data. */
|
||||
g_fsProgramMemory.mask = MemoryProgramSegmentType_Data;
|
||||
|
||||
/* Retrieve full FS program memory dump. */
|
||||
ret = memRetrieveProgramMemorySegment(&g_fsProgramMemory);
|
||||
|
||||
/* Clear segment mask. */
|
||||
g_fsProgramMemory.mask = 0;
|
||||
|
||||
if (!ret)
|
||||
{
|
||||
LOG_MSG("Failed to retrieve FS .data segment dump!");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Look for the LAFW ReadFw blob in the FS .data memory dump. */
|
||||
for(u64 offset = 0; offset < g_fsProgramMemory.data_size; offset++)
|
||||
{
|
||||
if ((g_fsProgramMemory.data_size - offset) < sizeof(LotusAsicFirmware)) break;
|
||||
|
||||
LotusAsicFirmware *lafw_blob = (LotusAsicFirmware*)(g_fsProgramMemory.data + offset);
|
||||
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)))
|
||||
{
|
||||
/* Jackpot. */
|
||||
memcpy(out, lafw_blob, sizeof(LotusAsicFirmware));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
LOG_MSG("Unable to locate Lotus %s blob in FS .data segment!", dev_unit ? "ReadDevFw" : "ReadFw");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Update flag. */
|
||||
ret = true;
|
||||
|
||||
end:
|
||||
memFreeMemoryLocation(&g_fsProgramMemory);
|
||||
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 ret;
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
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)
|
||||
static bool gamecardReadLotusAsicFirmwareBlob(void)
|
||||
{
|
||||
bool ret = false;
|
||||
LotusAsicFirmware lafw;
|
||||
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. */
|
||||
g_fsProgramMemory.mask = MemoryProgramSegmentType_Data;
|
||||
|
||||
/* Retrieve full FS program memory dump. */
|
||||
ret = memRetrieveProgramMemorySegment(&g_fsProgramMemory);
|
||||
|
||||
/* Clear segment mask. */
|
||||
g_fsProgramMemory.mask = 0;
|
||||
|
||||
/* Retrieve LAFW. */
|
||||
ret = gamecardGetLotusAsicFirmware(&lafw);
|
||||
if (!ret)
|
||||
{
|
||||
LOG_MSG("Failed to retrieve Lotus Asic Firmware!");
|
||||
LOG_MSG("Failed to retrieve FS .data segment dump!");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Look for the LAFW ReadFw blob in the FS .data memory dump. */
|
||||
for(u64 offset = 0; offset < g_fsProgramMemory.data_size; offset++)
|
||||
{
|
||||
if ((g_fsProgramMemory.data_size - offset) < sizeof(LotusAsicFirmwareBlob)) break;
|
||||
|
||||
LotusAsicFirmwareBlob *lafw_blob = (LotusAsicFirmwareBlob*)(g_fsProgramMemory.data + offset);
|
||||
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)))
|
||||
{
|
||||
/* Jackpot. */
|
||||
memcpy(g_lafwBlob, lafw_blob, sizeof(LotusAsicFirmwareBlob));
|
||||
fw_version = lafw_blob->fw_version;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
LOG_MSG("Unable to locate Lotus %s blob in FS .data segment!", dev_unit ? "ReadDevFw" : "ReadFw");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
|
||||
/* Update flag. */
|
||||
ret = true;
|
||||
|
||||
end:
|
||||
memFreeMemoryLocation(&g_fsProgramMemory);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -938,11 +956,12 @@ static bool gamecardReadSecurityInformation(GameCardSecurityInformation *out)
|
|||
if (!memcmp(tmp_hash, g_gameCardHeader.initial_data_hash, SHA256_HASH_SIZE))
|
||||
{
|
||||
/* 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;
|
||||
break;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue