Dump cart ID2 properly in private header (#862)

* Add cart_id2 to gamecart.h

This prepares changes to fix private header dumps.

The name ID2 matches Lotus3 (see Switchbrew) since it's evident Lotus3 is just a continuation of the 3DS cart controller

* Add Cart_GetID2() to protocol.h

This prepares changes to fix private header dumps.

The name ID2 matches Lotus3 (see Switchbrew) since it's evident Lotus3 is just a continuation of the 3DS cart controller.

* gc protocol: Add support to get ID2

This renames the unknowna0_cmd to its proper name and the A0_Response to CartID2, matching Lotus3 terminology.

* Store ID2 in private header at +0x44

The ID2 contains important information that in particular determines the cryptographic keys used. It is impossible to decrypt a dump of cart<->controller communications without knowing the ID2 or trying all possible keys.

This behavior matches Gateway. I suppose that it was presumed that Gateway would always store zeroes there because regular cartridges on retail would always report zero and then everybody just copied this false assumption.

* fix build (gamecart.c): memset->memcpy

* fix build (protocol.c): Fix dupe definition of Cart_GetID()
This commit is contained in:
AriA99 2026-03-20 14:53:37 +00:00 committed by GitHub
parent 9310455cac
commit 65b55f9d60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 17 additions and 7 deletions

View File

@ -22,6 +22,7 @@ typedef struct {
u8 unused[0x4000 + 0x8000 - PRIV_HDR_SIZE]; // 0xFF
u32 cart_type;
u32 cart_id;
u32 cart_id2;
u64 cart_size;
u64 data_size;
u32 save_size;
@ -37,6 +38,7 @@ typedef struct {
u8 modcrypt_area[0x4000];
u32 cart_type;
u32 cart_id;
u32 cart_id2; // meaningless on TWL
u64 cart_size;
u64 data_size;
u32 save_size;
@ -190,6 +192,7 @@ u32 InitCartRead(CartData* cdata) {
// init, NCCH header
static u32 sec_keys[4];
u8* ncch_header = cdata->header + 0x1000;
cdata->cart_id2 = Cart_GetID2();
CTR_CmdReadHeader(ncch_header);
Cart_Secure_Init((u32*) (void*) ncch_header, sec_keys);
@ -214,7 +217,7 @@ u32 InitCartRead(CartData* cdata) {
u8* priv_header = cdata->header + 0x4000;
CTR_CmdReadUniqueID(priv_header);
memcpy(priv_header + 0x40, &(cdata->cart_id), 4);
memset(priv_header + 0x44, 0x00, 4);
memcpy(priv_header + 0x44, &(cdata->cart_id2), 4);
memset(priv_header + 0x48, 0xFF, 8);
// save data

View File

@ -23,6 +23,7 @@ typedef struct {
u8 storage[0x8000]; // encrypted secure area + modcrypt area / unused
u32 cart_type;
u32 cart_id;
u32 cart_id2; // crypto type, some special dev stuff, normally all-0 for retail
u64 cart_size;
u64 data_size;
u32 save_size;

View File

@ -33,7 +33,7 @@
u32 CartID = 0xFFFFFFFFu;
u32 CartType = 0;
static u32 A0_Response = 0xFFFFFFFFu;
static u32 CartID2 = 0xFFFFFFFFu;
static u32 rand1 = 0;
static u32 rand2 = 0;
@ -85,6 +85,11 @@ u32 Cart_GetID(void)
return CartID;
}
u32 Cart_GetID2(void)
{
return CartID2;
}
void Cart_Init(void)
{
ResetCardSlot(); //Seems to reset the cart slot?
@ -95,8 +100,8 @@ void Cart_Init(void)
// 3ds
if (CartID & 0x10000000) {
u32 unknowna0_cmd[2] = { 0xA0000000, 0x00000000 };
NTR_SendCommand(unknowna0_cmd, 0x4, 0, &A0_Response);
u32 getid2_cmd[2] = { 0xA0000000, 0x00000000 };
NTR_SendCommand(getid2_cmd, 0x4, 0, &CartID2);
NTR_CmdEnter16ByteMode();
SwitchToCTRCARD();
@ -120,7 +125,7 @@ static u8 card_aes(u32 *out, u32 *buff, size_t size) { // note size param ignore
//const u8 is_dev_unit = *(vu8*)0x10010010;
//if(is_dev_unit) //Dev unit
const u8 is_dev_cart = (A0_Response&3)==3;
const u8 is_dev_cart = (CartID2&3)==3;
if(is_dev_cart) //Dev unit
{
AES_SetKeyControl(0x11);
@ -180,7 +185,7 @@ void Cart_Secure_Init(u32 *buf, u32 *out)
ARM_WaitCycles(0xF0000 * 8);
CTR_SetSecKey(A0_Response);
CTR_SetSecKey(CartID2);
CTR_SetSecSeed(out, true);
rand1 = 0x42434445;//*((vu32*)0x10011000);
@ -200,7 +205,7 @@ void Cart_Secure_Init(u32 *buf, u32 *out)
const u32 A3_cmd[4] = { 0xA3000000, 0x00000000, rand1, rand2 };
CTR_SendCommand(A3_cmd, 4, 1, 0x701002C, &test2);
if(test==CartID && test2==A0_Response)
if(test==CartID && test2==CartID2)
{
const u32 C5_cmd[4] = { 0xC5000000, 0x00000000, rand1, rand2 };
CTR_SendCommand(C5_cmd, 0, 1, 0x100002C, NULL);

View File

@ -18,5 +18,6 @@
void Cart_Init(void);
int Cart_IsInserted(void);
u32 Cart_GetID(void);
u32 Cart_GetID2(void);
void Cart_Secure_Init(u32* buf, u32* out);
void Cart_Dummy(void);