/* * Nintendo 64 Memory Card Dumper * Written by Mortal (http://www.nintendojo.fr/) * Thanks to : * - Waffle (http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?action=viewprofile;username=Waffle) for the n64cmd function. See this Arduino forum thread for info: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1261980415 * - Micah Dowty for the CRC address table algorithm. See http://svn.navi.cx/misc/trunk/wasabi/devices/cube64/notes/addr_encoder.py * * Connect N64 controller +3.3V line to Arduino 3.3V line * Connect N64 controller ground line to Arduino ground * Connect N64 controller data line to an Arduino I/O pin with a 1K pull-up resistor to 3.3V line * * Set Arduino pin used for data line in the follow three #defines: * Example for Arduino pin 9: N64_DATA_DDR = DDRB, N64_DATA_PIN = PINB, N64_DATA_PIN_NO = PINB1 */ #define N64_DATA_DDR DDRB #define N64_DATA_PIN PINB #define N64_DATA_PIN_NO PINB1 #include /* based off the 'N64/Gamecube controller to USB adapter' by Raphaël Assénat (http://www.raphnet.net/electronique/gc_n64_usb/index_en.php) * modifications/improvements: * - adjusted timing for 16 MHz * - support writing variable length data * - receive variable length data directly packed into destination buffer */ uint8_t n64cmd(uint8_t rxdata[], uint8_t rxlen, uint8_t txdata[], uint8_t txlen) { uint8_t num = 0; uint8_t oldSREG = SREG; cli(); asm volatile( "nextByte%=: \n" " cpi %[txlen], 0 \n" // 1 " breq done%= \n" // 1 " dec %[txlen] \n" // 1 " ld r16, z+ \n" // 2 " ldi r17, 0x80 \n" // 1 "nextBit%=: \n" " mov r18, r16 \n" // 1 " and r18, r17 \n" // 1 " breq send0%= \n" // 2 " nop \n" // 1us low, 1us high "send1%=: \n" " sbi %[ddr], %[pinNo] \n" // 2 " nop\nnop\nnop\nnop \n" // 4 " nop\nnop\nnop\nnop \n" // 4 " nop\nnop\nnop\nnop \n" // 4 " nop\nnop \n" // 2 " cbi %[ddr], %[pinNo] \n" // 2 " ldi r19, 11 \n" // 1 "lp1%=: dec r19 \n" // 1 " brne lp1%= \n" // 2 " lsr r17 \n" // 1 " breq nextByte%= \n" // 1 " nop\nnop\nnop\nnop \n" // 4 " nop \n" // 1 " rjmp nextBit%= \n" // 2 // 3us low, 1us high "send0%=: sbi %[ddr], %[pinNo] \n" // 2 " ldi r19, 15 \n" // 1 "lp0%=: dec r19 \n" // 1 " brne lp0%= \n" // 2 " nop \n" // 1 " cbi %[ddr], %[pinNo] \n" // 2 " nop \n" // 1 " lsr r17 \n" // 1 " breq nextByte%= \n" // 1 " nop\nnop\nnop\nnop \n" // 4 " nop \n" // 1 " rjmp nextBit%= \n" // 2 // finished sending, sync up to the stop bit time "done%=: \n" " nop\nnop\nnop\nnop \n" // 4 " nop\nnop\nnop \n" // 3 // stop bit " sbi %[ddr], %[pinNo] \n" // 2 " nop\nnop\nnop\nnop \n" // 4 " nop\nnop\nnop\nnop \n" // 4 " nop\nnop\nnop\nnop \n" // 4 " nop\nnop \n" // 2 " cbi %[ddr], %[pinNo] \n" // stop now if there's nothing to receive " cpi %[rxlen], 0 \n" // 1 " breq end%= \n" // 1 // receiving " clr r18 \n" // 1 current byte " ldi r17, 0x80 \n" // 1 current bit "st%=: \n" " ldi r16, 0xff \n" // 1 setup timeout "waitFall%=: \n" " dec r16 \n" // 1 " breq end%= \n" // 1 " sbic %[pin], %[pinNo] \n" // 2 " rjmp waitFall%= \n" // wait about 2us to check the state " nop\nnop\nnop\nnop \n" // 4 " nop\nnop\nnop\nnop \n" // 4 " nop\nnop\nnop\nnop \n" // 4 " nop\nnop\nnop\nnop \n" // 4 " nop\nnop\nnop\nnop \n" // 4 " nop\nnop\nnop\nnop \n" // 4 " nop\nnop\nnop\nnop \n" // 4 " sbic %[pin], %[pinNo] \n" // 2 " or r18, r17 \n" " lsr r17 \n" // 1 " brne nextRxBit%= \n" // 2 "nextRxByte%=: \n" " st x+, r18 \n" // 2 store the value " inc %[num] \n" // 1 increase number of received bytes " cp %[rxlen], %[num] \n" // 1 check for finish " breq end%= \n" // 1 " clr r18 \n" // 1 " ldi r17, 0x80 \n" // 1 "nextRxBit%=: \n" " ldi r16, 0xff \n" // 1 setup timeout "waitHigh%=: \n" " dec r16 \n" // 1 decrement timeout " breq end%= \n" // 1 handle timeout condition " sbis %[pin], %[pinNo] \n" // 2 " rjmp waitHigh%= \n" " rjmp st%= \n" // 2 "end%=: \n" : [num] "=r"(num) : [ddr] "I"(_SFR_IO_ADDR(N64_DATA_DDR)), [pin] "I"(_SFR_IO_ADDR(N64_DATA_PIN)), [pinNo] "I"(N64_DATA_PIN_NO), [rxdata] "x"(rxdata), [rxlen] "r"(rxlen), [txdata] "z"(txdata), [txlen] "r"(txlen), "0"(num) : "r16", "r17", "r18", "r19" ); SREG = oldSREG; _delay_us(100); // some commands leave the controller unresponsive for a while return num; } /* * The N64 uses a CRC algorithm to address the memory card. Memory cards are using adress from 0x0000 to 0x7e80 on 32 octets boundaries (with 0x20 steps). * Input : * @encodedAddr = an array of 2 elements containing the upper and lower bytes of a memory card address system ; * @hAddr = a tiny int containing the non-encoded upper byte of a memory card address ; * @bAddr = a tiny int containing the non-encoded lower byte of a memory card address ; * Output : * - void */ void addrEncode (uint8_t* encodedAddr, uint8_t hAddr, uint8_t bAddr) { // calculate a 16 bits (2 bytes) address from 2 * 8 bits upper and lower addresses uint16_t addr = (hAddr<<8) + bAddr; // default CRC table uint8_t crc_table[11] = {0x15, 0x1F, 0x0B, 0x16, 0x19, 0x07, 0x0E, 0x1C, 0x0D, 0x1A, 0x01}; // calculating the CRC address int _bit; for (_bit = 0; _bit < 11; _bit++) { if (addr & (1 << (_bit + 5))) { addr ^= crc_table[_bit]; } } // modifying the encodedAddr passed in argument encodedAddr[0] = addr >> 8; encodedAddr[1] = addr & 0x00ff; return; } /* * Generates every possible adress for the N64 memory card (from 0x0000 to 0x8000) and prints it on the Serial output under the format : * : */ void Backup () { // variables initialization int i, j, k; uint8_t command[40]; uint8_t result[40]; // initializing the controller (perhaps useless) command[0] = 0x03; command[1] = 0x80; command[2] = 0x01; memset(command + 3, 0x80, 32); n64cmd(result, 1, command, 35); delay(100); // the big loop ! // it generates adresses with 0x20 steps for (i = 0x00; i < 0x80; i++) { for (j = 0x00; j < 0x100; j += 0x20) { char buffer[5]; uint8_t addr[2]; command[0] = 0x02; // read command addrEncode (addr, i, j); // encode addr command[1] = addr[0]; command[2] = addr[1]; // returns 32 bytes of data and 1 byte of CRC // the data CRC is not yet implemented uint8_t num = n64cmd(result, 33, command, 3); sprintf(buffer, "%02x%02x", i, j); Serial.print(buffer); Serial.print(":"); for (k = 0; k < 32; k++) { sprintf(buffer, "%02x", result[k]); Serial.print(buffer); } Serial.print('\n'); delay(100); } } // signaling the end of dump with a \n Serial.print('\n'); } /* * Reads Serial until a \n char is encountered. Parse the line with the same format as above (:), put it in a tiny int array and write it on the right memory spot. */ void Restore () { // variables initialization int i; uint8_t command[40]; uint8_t result[40]; // initializing the controller (perhaps useless) command[0] = 0x03; command[1] = 0x80; command[2] = 0x01; memset(command + 3, 0x80, 32); n64cmd(result, 1, command, 35); delay(100); while(1) { char buffer[100]; // re-initialize the buffer memset(buffer, '\0', 100); if(Serial.available() > 0) { Serial.readBytesUntil('\n', buffer, 100); if (strlen(buffer) <= 1) { return; } command[0] = 0x03; // write command char* poschar = buffer; uint8_t addr[2], encodedAddr[2]; sscanf(poschar, "%02x", addr); // reads first byte poschar +=2; sscanf(poschar, "%02x", (addr + 1)); // reads second byte addrEncode (encodedAddr, addr[0], addr[1]); // encode addr // concat command with encodedAddr command[1] = encodedAddr[0]; command[2] = encodedAddr[1]; // position on first data byte, erasing ':' char poschar += 3; // reads 32 byte of data and insert them into command vector for (i = 0; i < 32; i++) { sscanf(poschar, "%02x", (command + 3 + i)); poschar += 2; } n64cmd(result, 1, command, 35); } } } void setup() { Serial.begin(115200); } void loop() { char whattodo; if(Serial.available() > 0) { whattodo = Serial.read(); switch(whattodo) { case 's': Backup(); break; case 'r' : Restore(); break; default: Serial.println("Error: wrong key !"); } } }