304 lines
8.7 KiB
C++
304 lines
8.7 KiB
C++
/*
|
|
* 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 <micah@navi.cx> 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 <util/delay.h>
|
|
|
|
/* 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 :
|
|
* <address(2 bytes)>:<data(32 bytes)>
|
|
*/
|
|
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 (<address(2 bytes)>:<data(32 bytes)>), 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 !");
|
|
}
|
|
}
|
|
}
|