I'm trying to launch DS cards that have been inserted after power-on, but I'm having trouble with the encryption. I've implemented everything as best I can understand according to
GBATek. Once I'm in KEY1 mode, all I seem to get back from the cartridge is random data, even when nothing is inserted. (With nothing inserted, before entering KEY1 mode, everything is 0xFF.) I've tried sending commands 1lllliiijjjkkkkk to get the encrypted chip ID, and Flllliiijjjkkkkk which is invalid and should return an endless stream of KEY2-encrypted zeros. In both cases the data I get back (both before and after decryption) is completely random; sending 4llllmmmnnnkkkkk beforehand to enable KEY2 mode doesn't seem to make a difference. (And yes, I am sending KEY1-encrypted commands.)
For i, j, l, and the encryption seeds, which are supposed to all be random, I've simply used zero. k starts out as zero and is incremented after every KEY1 command as specified. I can't test whether KEY1 encryption actually works, since all the returned data is either raw or KEY2. I've written zero to 0x040001B0 through 0x040001BB too.
My best guess (aside from the code simply being wrong) is that once these seeds are set by the BIOS, they can't be changed, and so I have to find out which seeds it used and continue to use them. It must be possible in any case since ARDS and Nitro Hax do it.
These are the crypto routines. Yeah, they need to be cleaned up and commented and so on, I haven't got that far yet.
Code:
#define BYTESWAP32U(x) (u32)((((x) & 0xFF000000) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | (((x) & 0x000000FF) << 24))
#define CARD_FLAG_KEY1_LEN1(x) ((x) & 0x1FFF)
#define CARD_FLAG_DATA_SCRAMBLE BIT(13)
#define CARD_FLAG_BIT14 BIT(14) //Same as bit 13?
#define CARD_FLAG_BIT15 BIT(15) //Unknown
#define CARD_FLAG_KEY1_LEN2(x) (((x) & 0x3F) << 16)
#define CARD_FLAG_CMD_SCRAMBLE BIT(22)
#define CARD_FLAG_WORD_READY BIT(23) //Read-only
#define CARD_FLAG_BLOCK_SIZE(x) (((x) & 7) << 24) //0=none, 1-6 = (0x100 << x) bytes, 7=4 bytes
#define CARD_FLAG_SLOW_CLOCK BIT(27) //Use 4.2mhz instead of 6.7mhz
#define CARD_FLAG_BIT28 BIT(28) //"Secure area mode (0=normal, 1=other)"
#define CARD_FLAG_BIT29 BIT(29) //Always 1?
#define CARD_FLAG_BIT30 BIT(30) //Always 0?
#define CARD_FLAG_START BIT(31) //Start transfer
[...]
const u8 Key2Seed[] = {0xE8, 0x4D, 0x5A, 0xB1, 0x17, 0x8F, 0x99, 0xD5};
[...]
u32 KEY1[1042];
u32 KeyCode[3];
bool ReinitKey2 = true;
Code:
/*
Initializes the KeyCode buffer.
Inputs:
-IDCode: Game code or firmware ID code, depending what is being decrypted.
-Level: How many times to apply the keycode (1-3).
-Modulo: Passed to ApplyKeyCode.
*/
void InitKeyCode(u32 IDCode, u32 Level, u32 Modulo)
{
//Todo: read key from BIOS
memcpy(KEY1, KEY1_DATA, sizeof(KEY1));
KeyCode[0] = IDCode;
KeyCode[1] = IDCode >> 1;
KeyCode[2] = IDCode << 1;
/*
KeyCode[0] = IDCode & 0xFF;
KeyCode[1] = (IDCode >> 8) & 0xFF);
KeyCode[2] = (IDCode >> 16) & 0xFF);
KeyCode[3] = (IDCode >> 24) & 0xFF);
KeyCode[4] = (IDCode >> 1) & 0xFF;
KeyCode[5] = ((IDCode >> 1) >> 8) & 0xFF);
KeyCode[6] = ((IDCode >> 1) >> 16) & 0xFF);
KeyCode[7] = ((IDCode >> 1) >> 24) & 0xFF);
KeyCode[8] = (IDCode << 1) & 0xFF;
KeyCode[9] = ((IDCode << 1) >> 8) & 0xFF);
KeyCode[10] = ((IDCode << 1) >> 16) & 0xFF);
KeyCode[11] = ((IDCode << 1) >> 24) & 0xFF);
*/
if(Level >= 1) ApplyKeyCode(Modulo);
if(Level >= 2) ApplyKeyCode(Modulo);
KeyCode[1] <<= 1;
KeyCode[2] >>= 1;
/*
KeyCode[4] <<= 1;
KeyCode[5] <<= 1;
KeyCode[6] <<= 1;
KeyCode[7] <<= 1;
KeyCode[8] >>= 1;
KeyCode[9] >>= 1;
KeyCode[10] >>= 1;
KeyCode[11] >>= 1;
*/
if(Level >= 3) ApplyKeyCode(Modulo);
/*
copy [arm7bios+0030h..1077h] to [keybuf+0..1047h]
[keycode+0]=[idcode]
[keycode+4]=[idcode]/2
[keycode+8]=[idcode]*2
IF level>=1 THEN apply_keycode(modulo) ;first apply (always)
IF level>=2 THEN apply_keycode(modulo) ;second apply (optional)
[keycode+4]=[keycode+4]*2
[keycode+8]=[keycode+8]/2
IF level>=3 THEN apply_keycode(modulo) ;third apply (optional)
*/
}
/*
Used by InitKeyCode().
*/
void ApplyKeyCode(u32 Modulo)
{
u32 Scratch[2];
Crypt64bit(true, &KeyCode[1]);
Crypt64bit(true, &KeyCode[0]);
Scratch[0] = 0;
Scratch[1] = 0;
for(u32 i = 0; i < 11; i++)
KEY1[i] = KEY1[i] ^ BYTESWAP32(KeyCode[i % Modulo]);
for(u32 i = 0; i < 0x410; i += 2)
{
Crypt64bit(true, Scratch);
KEY1[i] = Scratch[0];
KEY1[i + 1] = Scratch[1];
}
/*
crypt_64bit_up(keycode+4)
crypt_64bit_up(keycode+0)
[scratch]=0000000000000000h ;S=0 (64bit)
FOR I=0 TO 44h STEP 4 ;xor with reversed byte-order (bswap)
[keybuf+I]=[keybuf+I] XOR bswap_32bit([keycode+(I MOD modulo)])
NEXT I
FOR I=0 TO 1040h STEP 8
crypt_64bit_up(scratch) ;encrypt S (64bit) by keybuf
[keybuf+I]=[scratch] ;write S (64bit) to keybuf
NEXT I
*/
}
/*
Used by InitKeyCode().
*/
void Crypt64bit(bool Up, u32* Ptr)
{
u32 X, Y, Z;
s32 init, check, step;
Y = Ptr[0];
X = Ptr[1];
if(Up)
{
init = 0;
check = 0xF;
step = 1;
}
else {
init = 0x11;
check = 2;
step = -1;
}
for(s32 i = init; i != check; i += step)
{
Z = KEY1[i << 2] ^ X;
X = KEY1[0x12 + (((Z >> 24) & 0xFF) << 2)];
X = KEY1[0x112 + (((Z >> 16) & 0xFF) << 2)] + X;
X = KEY1[0x212 + (((Z >> 8) & 0xFF) << 2)] ^ X;
X = KEY1[0x312 + ((Z & 0xFF) << 2)] + X;
X = Y ^ X;
Y = Z;
}
if(Up) {
Ptr[0] = X ^ KEY1[0x10];
Ptr[1] = Y ^ KEY1[0x11];
}
else {
Ptr[0] = X ^ KEY1[1];
Ptr[1] = Y ^ KEY1[0];
}
/*
Y=[ptr+0]
X=[ptr+4]
FOR I=0 TO 0Fh (up), or FOR I=11h TO 02h (down)
Z=[keybuf+I*4] XOR X
X=[keybuf+048h+((Z SHR 24) AND FFh)*4]
X=[keybuf+448h+((Z SHR 16) AND FFh)*4] + X
X=[keybuf+848h+((Z SHR 8) AND FFh)*4] XOR X
X=[keybuf+C48h+((Z SHR 0) AND FFh)*4] + X
X=Y XOR X
Y=Z
NEXT I
[ptr+0]=X XOR [keybuf+40h], or [ptr+0]=X XOR [keybuf+4h] (down)
[ptr+4]=Y XOR [keybuf+44h], or [ptr+4]=Y XOR [keybuf+0h] (down)
*/
}
/*
Encrypts/decrypts KEY2 data.
*/
void Key2Enc(u8* Data, u8 NumBytes)
{
static u64 S1 = 0, S2 = 0, Mask = 0x7FFFFFFFFF;
u64 SeedTemp;
if(ReinitKey2)
{
S1 = 0x6000 + Key2Seed[NDSHeader.deviceType & 3];
S2 = 0x506CECF09D;
//Reverse bit order
SeedTemp = 0;
for(u32 i = 0; i < 39; i++)
SeedTemp |= ((S1 >> i) & 1) << (39 - i);
S1 = SeedTemp;
//S2 is already reversed
ReinitKey2 = false;
}
for(u32 i=0; i<NumBytes; i++)
{
S1 = ((((S1 >> 5) ^ (S1 >> 17) ^ (S1 >> 18) ^ (S1 >> 31)) & 0xFF) + (S1 << 8)) & Mask;
S2 = ((((S2 >> 5) ^ (S2 >> 23) ^ (S2 >> 18) ^ (S2 >> 31)) & 0xFF) + (S2 << 8)) & Mask;
Data[i] = (Data[i] ^ S1 ^ S2) & 0xFF;
}
/*
x = S1, y = S2
x = (((x shr 5)xor(x shr 17)xor(x shr 18)xor(x shr 31)) and 0FFh)+(x shl 8)
y = (((y shr 5)xor(y shr 23)xor(y shr 18)xor(y shr 31)) and 0FFh)+(y shl 8)
data = (data xor x xor y) and 0FFh
*/
}
/*
Sends a raw command to the DS card and reads the 32-bit return value.
Inputs:
-Command: Command data.
-Flags: Card control flags.
Returns: First word returned from the card.
*/
u32 RawNDSCardCommand(u8* Command, u32 Flags)
{
CARD_CR1H = CARD_CR1_ENABLE | CARD_CR1_IRQ;
for(u32 i=0; i<8; i++)
CARD_COMMAND[i] = Command[i];
CARD_CR2 = Flags;
while(!(CARD_CR2 & CARD_DATA_READY));
return CARD_DATA_RD;
}
/*
Same as RawNDSCardCommand(), but instead of returning the first word received,
the data is copied into a buffer.
Inputs:
-Command: Command data.
-Buf: Buffer to copy words to.
-NumWords: Maximum number of words to copy.
-SkipWords: Number of words to ignore before starting to copy. Mainly used
for GetEncryptedChipID() since the command returns a long stream of dummy
bytes before the ID.
-Flags: Card control flags.
Returns: Number of words read (including ignored words).
*/
u32 MultiNDSCardCommand(u8* Command, u32* Buf, u32 NumWords, u32 SkipWords, u32 Flags)
{
u32 Data, Index = 0, WordsRead = 0;
CARD_CR1H = CARD_CR1_ENABLE | CARD_CR1_IRQ;
for(u32 i=0; i<8; i++)
CARD_COMMAND[i] = Command[i];
CARD_CR2 = Flags;
do {
if(CARD_CR2 & CARD_DATA_READY)
{
WordsRead++;
Data = CARD_DATA_RD;
if(SkipWords) SkipWords--;
else if(Index < NumWords)
{
Buf[Index] = Data;
Index++;
}
}
} while(CARD_CR2 & CARD_BUSY);
return WordsRead;
}
/*
Boots the inserted DS card.
Returns: On success, does not return. On failure, returns one of BE_xxx.
*/
BOOTERROR BootDSCard()
{
u32 ChipID, ChipID2, EncChipID;
bool SecureAreaTransferMode = true; //true=2x800h, false=1000h
u32 CmdCount = 0, WordCount;
u32 GameCode;
u8 Command[8];
u32 Flags, EncrFlags;
ConsoleClear();
ConsoleMoveTo(0, 0);
ConsoleSetColour(CONSOLE_COL_WHITE);
ConsoleEnableDigitSep(false);
ReinitKey2 = true;
//Set encryption seeds
CARD_1B0 = 0;
CARD_1B4 = 0;
CARD_1B8 = 0;
CARD_1BA = 0;
Flags = CARD_FLAG_KEY1_LEN1(0x1FFF) | CARD_FLAG_KEY1_LEN2(0x3F) | CARD_FLAG_BIT29 | CARD_FLAG_START;
EncrFlags = Flags | CARD_FLAG_DATA_SCRAMBLE | CARD_FLAG_BIT14 | CARD_FLAG_CMD_SCRAMBLE;
//Read card header
ConsolePrint("Read header... ");
//cardReadHeader((u8*)&NDSHeader.gameTitle[0]);
for(u32 i=0; i<8; i++) Command[i] = 0;
WordCount = MultiNDSCardCommand(Command, (u32*)&NDSHeader.gameTitle[0], 128, 0, Flags | CARD_FLAG_BLOCK_SIZE(1));
GameCode = NDSHeader.gameCode[0] | (NDSHeader.gameCode[1] << 8) | (NDSHeader.gameCode[2] << 16) | (NDSHeader.gameCode[3] << 24);
ConsolePrintf("%X\nTitle: ", WordCount);
for(u32 i=0; i<12; i++) ConsolePrintChar(NDSHeader.gameTitle[i]);
ConsolePrint(" [");
for(u32 i=0; i<4; i++) ConsolePrintChar(NDSHeader.gameCode[i]);
ConsolePrint("]\n");
for(u32 i=0; i<12; i += 2) ConsolePrintf("%02X%02X ", NDSHeader.gameTitle[i], NDSHeader.gameTitle[i + 1]);
ConsolePrint("\n");
for(u32 i=0; i<4; i++) ConsolePrintf("%02X ", NDSHeader.gameCode[i]);
ConsolePrintf("\nExec: %08X, %08X\nChip ID: ", NDSHeader.arm7executeAddress, NDSHeader.arm9executeAddress);
//Read ROM chip ID, which tells us which secure area transfer mode to use
Command[0] = 0x90;
for(u32 i=1; i<8; i++) Command[i] = 0;
ChipID = RawNDSCardCommand(Command, Flags | CARD_FLAG_BLOCK_SIZE(7));
ConsolePrintf("%08X\nEnter KEY1 mode... ", ChipID);
if(ChipID & 0x80000000) SecureAreaTransferMode = false;
/*
Enter KEY1 mode: 3C ii ij jj xk kk kk xx
i, j = anything, must be the same in later commands
k = anything, must be incremented after each command after this one
x = ignored
*/
Command[0] = 0x3C;
for(u32 i=1; i<8; i++) Command[i] = 0;
RawNDSCardCommand(Command, Flags | CARD_FLAG_BLOCK_SIZE(7));
ConsolePrint("OK\nInit key code");
InitKeyCode(GameCode, 1, 8);
ConsolePrint(" 1");
Crypt64bit(false, &NDSHeader.bfPrime1);
InitKeyCode(GameCode, 2, 8);
ConsolePrint(" 2 OK\n");
//Enter KEY2 mode
//4l ll lm mm nn nk kk kk
Command[0] = 0x40;
for(u32 i=1; i<5; i++) Command[i] = 0;
Command[5] = (CmdCount >> 16) & 0xF;
Command[6] = (CmdCount >> 8) & 0xFF;
Command[7] = CmdCount & 0xFF;
ConsolePrintf("%02X %02X %02X %02X %02X %02X %02X %02X\n",
Command[0], Command[1], Command[2], Command[3], Command[4], Command[5], Command[6], Command[7]);
Crypt64bit(true, (u32*)Command);
ConsolePrintf("%02X %02X %02X %02X %02X %02X %02X %02X\n",
Command[0], Command[1], Command[2], Command[3], Command[4], Command[5], Command[6], Command[7]);
ConsolePrint("Enter KEY2 mode... ");
WordCount = MultiNDSCardCommand(Command, NULL, 0, 0x244, Flags | CARD_FLAG_BLOCK_SIZE(4));
ConsolePrintf("%X\n", WordCount);
//ConsolePrint("OK\n");
CmdCount++;
ConsolePrintf("Cart seed %u (%02X)\n", NDSHeader.deviceType & 3, Key2Seed[NDSHeader.deviceType & 3]);
//Read ROM chip ID again
ConsolePrint("Chip ID: ");
ChipID2 = 0xAAAAAAAA;
Command[0] = 0x10;
Command[1] = 0x00;
Command[2] = 0x00;
Command[3] = 0x00;
Command[4] = 0x00;
Command[5] = (CmdCount >> 16) & 0xF;
Command[6] = (CmdCount >> 8) & 0xFF;
Command[7] = CmdCount & 0xFF;
Crypt64bit(true, (u32*)Command);
/*
Encryption test
*/
Command[0] = 0xF0;
Command[1] = 0x00;
Command[2] = 0x00;
Command[3] = 0x00;
Command[4] = 0x00;
Command[5] = (CmdCount >> 16) & 0xF;
Command[6] = (CmdCount >> 8) & 0xFF;
Command[7] = CmdCount & 0xFF;
Crypt64bit(true, (u32*)Command);
ChipID2 = 0;
Key2Enc((u8*)&ChipID2, 4);
ReinitKey2 = true;
ConsolePrintf("\n00000000 -> %08X\n", ChipID2);
WordCount = MultiNDSCardCommand(Command, &ChipID2, 1, 0x250, CARD_FLAG_KEY1_LEN1(0x1FFF) | CARD_FLAG_KEY1_LEN2(0x3F) | CARD_FLAG_BIT29 | CARD_FLAG_START
| CARD_FLAG_DATA_SCRAMBLE | CARD_FLAG_BIT14 | CARD_FLAG_BLOCK_SIZE(6));
ConsolePrintf("Returned -> %08X (%X)\n", ChipID2, WordCount);
Key2Enc((u8*)&ChipID2, 4);
ConsolePrintf("Decr. 1 -> %08X\n", ChipID2);
return BE_UNKNOWN_ERROR;
/*
End test
*/
}
KEY1_DATA is a copy of the KEY1 table from the BIOS since I haven't got around to making it actually read from the BIOS yet.
I've heard you need to write the command bytes in reverse order, but that doesn't seem to be true. Reading the header and chip ID in raw mode work just fine without that. O_o