Table of Contents

R4 (NDS) programming

Original research provided mostly by lifehackerhansol.

Control flags

Commands

Get card status (B0 00 00 00 00 00 00 00)

31                 bit                 0
 ???? ???? ???? ???? ???? ???L LLml lsss
                             | |||| ||||
                             | |||| |+++- Initialization status:
                             | |||| |      0 - (in progress)
                             | |||| |      1 - SD/TF card not found
                             | |||| |      2 - SD/TF card read error
                             | |||| |      3 - could not find _DS_MENU.DAT
                             | |||| |      4 - success
                             | |||+-+---- Language:
                             | |||         1 - Japanese
                             | |||         2 - English
                             | |||         3 - Chinese
                             | ||+------- Model:
                             | ||          0 - M3DS Simply
                             | ||          1 - R4 Revolution for DS
                             +-++-------- Sub-language:
                                           2:2  - French
                                           2:4  - Korean
                                           3:0? - Simplified Chinese
                                           3:1? - Traditional Chinese

Save read request (B2 sd sc sb sa 00 00 00)

SD read request, but using the “save” FAT entry table.

Save read data (B3 00 00 00 00 00 00 00)

SD read data, but using the “save” FAT entry table.

Set FAT table entry (B4 sd sc sb sa 00 00 00)

31                 bit                 0
 ssss ssss ssss ssss ssss ssss sss? ???t
 |||| |||| |||| |||| |||| |||| |||     |
 |||| |||| |||| |||| |||| |||| |||     +- Type:
 |||| |||| |||| |||| |||| |||| |||         0 - ROM
 |||| |||| |||| |||| |||| |||| |||         1 - Save
 ++++-++++-++++-++++-++++-++++-+++------- Directory table address
                                           (aligned to 0x20 per FAT spec)

ROM read request (B6 sd sc sb sa 00 00 00)

SD read request, but using the “ROM” FAT entry table.

Points to _DS_MENU.DAT on initialization.

ROM read data (B7 00 00 00 00 00 00 00)

SD read data, but using the “ROM” FAT entry table.

Get card ID (B8 00 00 00 00 00 00 00)

Quoting lifehackerhansol:

Card ID commands are not patched and use the timings from the retail game ROM header, but without L1 latency (that's what the SDK does). Those can be as fast as a single cycle of L2 latency, depending on the game.

SD read request (B9 sd sc sb sa 00 00 00)

Returns 0x00000000 when data is ready to access, 0x00000001 if ongoing.

SD read data (BA 00 00 00 00 00 00 00)

Returns the 512 bytes read via the “SD read start” command.

SD write start (BB sd sc sb sa 00 00 00)

Followed by 512 bytes of SD card data.

SD write status (BC sd sc sb sa 00 00 00)

Returns 0x00000000 when data is ready to access, 0x00000001 if ongoing.

Save write start (BD sd sc sb sa 00 00 00)

SD write start, but using the “save” FAT entry table.

Save write status (BE sd sc sb sa 00 00 00)

SD write status, but using the “save” FAT entry table.

ROM read decrypted data (BF 00 00 00 00 00 00 00)

Same as SD read data, but applying a XOR-based obfuscation algorithm to the buffer's contents.

This is used by the stage 1 loader to decrypt _DS_MENU.DAT.

The algorithm was initially discovered by Yasu in 2007, and example source code is provided as follows, where key is a 16-bit constant:

uint8_t[512] in;
uint8_t[512] out;
uint16_t key1 = key ^ currentSector;
    
for (int i = 0; i < 512; i++)
{
    // Derive key2 from key1.
    uint8_t key2 = ((key1 >> 7) & 0x80)
        | ((key1 >> 6) & 0x60)
        | ((key1 >> 5) & 0x10)
        | ((key1 >> 4) & 0x0C)
        | (key1 & 0x03);
        
    // Decrypt byte.
    dest[i] = src[i] ^ key2;

    // Derive next key1 from key2.
    uint16_t tmp = ((src[i] << 8) ^ key1);
    uint16_t tmpXor = 0;
    for (int ii = 0; ii < 16; ii++)
        tmpXor ^= (tmp >> ii);

    uint16_t newKey1 = 0;
    newKey1 |= ((tmpXor & 0x80) | (tmp & 0x7C)) << 8;
    newKey1 |= ((tmp ^ (tmpXor >> 14)) << 8) & 0x0300;
    newKey1 |= (((tmp >> 1) ^ tmp) >> 6) & 0xFC;
    newKey1 |= ((tmp ^ (tmpXor >> 1)) >> 8) & 0x03;

    key1 = newKey1;
}