/*
 * 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 !");
    }
  }
}
