GodMode9 icon indicating copy to clipboard operation
GodMode9 copied to clipboard

Dump cart ID2 properly in private header

Open AriA99 opened this issue 1 year ago • 2 comments

The private header currently stores the 0x40 unique ID and the cart ID that contains the maker code (e.g. 0xc2 for Macronix). At +0x44, it stores four zero-bytes. This is actually the ID2. 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 proposed new 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.

The Switch Lotus3 has CartId1 and CartId2 fields. These map almost 1:1 to the 3DS. It is therefore a natural assumption that these names would match for the 3DS, too.

I propose doing a squash merge instead of a simple merge: These edits were made in the GitHub web editor, one file at a time and then downloaded, build and tested separately (don't ask why). The commit history is as messy as one might expect from a genesis of this sort.

AriA99 avatar Jun 01 '24 13:06 AriA99

Hey, thanks a lot for the contribution! I see you're also the person who edited the Gamecards page in 3dbrew, so thanks for that one too.

The code itself looks good to me, I'm just a bit concerned with the behavior. Do you have any source for this info? Be it datasheets, other cart dumpers, etc?

Wolfvak avatar Jun 16 '24 18:06 Wolfvak

Code looks good to me as well, but I'd feel much better merging this one with a reply from @AriA99 to @Wolfvak 's posted question.

d0k3 avatar Mar 11 '25 10:03 d0k3

Hey, thanks a lot for the contribution! I see you're also the person who edited the Gamecards page in 3dbrew, so thanks for that one too.

The code itself looks good to me, I'm just a bit concerned with the behavior. Do you have any source for this info? Be it datasheets, other cart dumpers, etc?

The source is an argument implying what the reasonable behavior to do is.

The "private header" was initially introduced by Gateway. Real 3DS cartridges don't have anything at the offset the private header is conventionally written. Other cart dumpers essentially copied the .3ds sequencing and format from Gateway. But Gateway themselves must have had reason to put 0x00 in the middle. After all, normally, you would put padding at the end. Plus the padding in this case is 0xff. Why would you pad one value with zeroes and other values with ones? This strongly suggests that private header offset +0x44 is a value and not padding.

If we look at the existing GodMode9 code, we send a 0xA0 command as part of the initialization. The result currently goes unused except as a parameter to CTR_SetSecKey(). If you look at Process9 (11.16 retail but you can also go as far back as some pre-1.0 versions of Process9), you can see that the result of 0xA0 is used to change the behavior of the game card initialization if ((result of cmd 0xA0) & 0x8000) != 0.

We know that Gateway has been doing experimentation with cartridge dumping very early on, see the history of the 3dbrew Gamecards page. The 0xA0 command initially got documented there as "Unknown." It therefore stands to reason that you would want to dump the result of 0xA0 just to be sure in case things change in the future. After all, what if Big N changes this behavior and starts making cartridges that use 0xA0 in other cases, possibly mid-production of a title? Gateway designed the private header format and placed it in an otherwise unused region of the NCSD. Additionally, the code for handling ID1 and ID2 is right next to each other in Process9. If you were Gateway, then obviously, you would store related things next to each other in the private header.

Finally, let's turn our attention to the Switch. As can be seen on the Lotus3 page on switchbrew, the CardKeyArea has three IDs: +0x08 ID1 (0x04 len), +0x0c ID2 (0x04 len) and +0x10 UID (0x40 len). This matches the 3DS private header, just in a different order: 0x40 UID, 0x44 ID1, 0x48 mystery meat (ID2). Extrapolating from the newer Lotus3 to the 3DS card controller, ID1 essentially functions the same. UID is the same size. It therefore stands to reason that ID2 would also function the same.

On all known retail carts, ID2 is always all-zero. This explains why that field is always zero. Looking at Process9, however, there may be carts (or cart-slot-attached partner debuggers?) that introduce themselves with a different ID2.

To me, it therefore seems clear that there's no way that Gateway could have possibly done anything but placed ID2 at private header+0x44.

AriA99 avatar Aug 31 '25 15:08 AriA99

ID2 is not all-zero on "all known retail carts", there are several games with ID2[0] = 02h (notably Pokemon Ultra Sun/Ultra Moon and Persona Q2), and a handful with ID2[0] = 01h (notably Mario & Luigi: Paper Jam Bros). Devkit carts have ID2[0] = 03h. This is why some 3DS-mode flashcarts like the Stargate3DS can't work with unmodified ROMs of Pokemon Ultra Sun/Ultra Moon, because the Stargate3DS bitstream only contains keys for ID2[0] = 00h or 01h, defaulting to ID2[0] = 00h if it encounters 02h. The original Sky3DS (non-Plus) only had keys for 00h, meaning all 01h and 02h games fail to run. The Sky3DS Plus is the only known hardware outside of official hardware to actually have keys for 02h.

fox8091 avatar Sep 07 '25 17:09 fox8091