<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/sodnpoo.xsl"?>
<xml>
<post>
  <title>mega mega dumper</title>
  <date>05 Aug 2012</date>
  <p>
  </p>
  <image src="/posts.assets/mega_mega_dumper1.jpg"/>
  <p>
  Based on from the prototype <a href="http://www.sodnpoo.com/posts.xml/mega_mega_reader.xml">mega mega reader</a> I built a couple of weeks ago - I've rebuilt the hardware as something more permanent and made changes to the code to dump the full ROM image to the serial port.
  </p>
  <p>
  I was expecting the serial communications to be pretty straight forward but after dumping the first cartridge I discovered that my file was too short. Looking a a hex dump of the file compared to the same ROM downloaded from the internet I could see I was missing any bytes with the value of 0x11. Turns out that 0x11 (and 0x13) are used for XON/XOFF flow control on the arduino's serial port and so these values were being silently eaten. I tried again this time disabling flow control and found that if I slowed the rate the arduino was sending data to a crawl, I could extract a perfect (and playable) ROM image.
  </p>
  <p>
  To increase the dumping speed I went back to using XON/XOFF but encoded each byte as it's ascii representation - it's a bit wasteful as every byte is now two bytes long on the wire, but it was very quick and easy to implement and I can make use of other non-printable characters provide in-band signaling.
  </p>
  <p>
  The code running on the arduino emits a start and end marker, before and after sending the ROM data. This makes it easier on the PC end to know when to start writing and when to exit.
  </p>
  <pre>
//address pins (note - cartridge interface starts at A1 so A[0] == A1, A[1] == A2 etc)
int A[23] = {37,33,29,3,5,27,23,8,53,51,9,25,6,4,2,31,35,49,47,45,43,41,39};

//data pins
int D[16] = {38,44,30,24,26,32,42,36,40,34,28,22, 52,50,48,46};

void resetAddressBus(){
  for(byte i=0; i&lt;23; i++){
    pinMode(A[i], OUTPUT);
    digitalWrite(A[i], LOW);
  }
}

void writeAddress(long addr){
  long mask = 1;
  for(int i=0; i&lt;23; i++){
    long test = (mask &amp; addr) &gt;&gt; i;
    if(test == 1){
      digitalWrite(A[i], HIGH);
    }else{
      digitalWrite(A[i], LOW);
    }
    mask = mask &lt;&lt; 1;
  }  
}

word readData(){
  word d = 0;
  word mask2 = 0b1;
  for(int i=0; i&lt;16; i++){
    int b = digitalRead(D[i]);
    
    if(b == HIGH){
      d = d | mask2;
    } 
    mask2 = mask2 &lt;&lt; 1;
  }
  return d;
}

long readLong(long addr){
  writeAddress(addr);
  delay(50);
  word msb = readData();
  writeAddress(addr+1);
  delay(50);
  word lsb = readData();

  long result = msb;
  result = (result &lt;&lt; 16) | lsb;
  return result;  
}

void setup(){
  Serial.begin(115200);

  resetAddressBus();
  for(int i=0; i&lt;16; i++){
    pinMode(D[i], INPUT);
  }
  
  Serial1.begin(9600);
  Serial1.print(0xFE, BYTE);
  Serial1.print(0x01, BYTE);
  Serial1.print(0xFE, BYTE);
  Serial1.print(0x80, BYTE);
  
  for(byte i=0; i&lt;8; i++){
    writeAddress(0x90 + i);
    delay(5);
    //read the data bus
    word d = readData();
    //convert the 16 bit word to two bytes
    byte d2 = d &amp; 0xFF;
    byte d1 = (d &amp; 0xFF00) &gt;&gt; 8;
    Serial1.print(d1);
    Serial1.print(d2);
  }


  Serial.print(0xFE, BYTE);
  Serial.print(0xFE, BYTE);

  long romend = readLong(0xd2);
  romend = (romend+1)/2;
  long romstart = readLong(0xd0);
    
  for(long i=romstart; i&lt;romend; i++){
    if((i % 512) == 0){
      Serial1.print(0xFE, BYTE);
      Serial1.print(0xC0, BYTE);
      Serial1.print(i*2, DEC);
      Serial1.print('/');
      Serial1.print(romend*2, DEC);
    }

    writeAddress(i);
    delay(2);
    //read the data bus
    word d = readData();
    //convert the 16 bit word to two bytes
    byte d2 = d &amp; 0xFF;
    byte d1 = (d &amp; 0xFF00) &gt;&gt; 8;
    
    if(d1 &lt; 0x10){
      Serial.print('0');
    }
    Serial.print(d1, HEX);
    
    if(d2 &lt; 0x10){
      Serial.print('0');
    }
    Serial.print(d2, HEX);
  }
  
  Serial1.print(0xFE, BYTE);
  Serial1.print(0xC0, BYTE);
  Serial1.print(romend*2, DEC);
  Serial1.print('/');
  Serial1.print(romend*2, DEC);
  
  Serial.print(0xFF, BYTE);
  Serial.print(0xFF, BYTE);
  
  Serial.end();
}

void loop(){
  return;
}
  </pre>
  <p>
  On the PC I first need to set up the tty (this is probably linux specific - I did try to use pyserial so it would be platform independent but I couldn't get it to work reliably) :
  </p>
  <pre>
  stty -F /dev/ttyUSB0 115200 raw ixon ixoff
  </pre>
  <p>
  The python code reads the serial data looking for the start marker ("\xfe\xfe") to tell it to start decoding from the ascii values back to the binary and then write them to a file. Finally it sees the end marker ("\xff\xff") and closes the file and exits.
  </p>
  <pre>
import sys

if (len(sys.argv) &gt; 2):
  portname = sys.argv[1]
  filename = sys.argv[2]
else:
  print "dumper.py &lt;port&gt; &lt;filename&gt;"
  exit()

print "reading from", portname
print "writing to", filename

write = False

with open(portname,"rb") as f:
  while f:
    b = f.read(2)
    if b == "\xff\xff": #end marker
      print "end"
      f.close()
      f = None
    elif b == "\xfe\xfe": #start marker
      print "start"
      write = True
      dump = open(filename,"wb")
    elif write == True:
      b = int(b, 16)
      dump.write( chr(b) )
      dump.flush()
  </pre>
  <p>
  The dumper has been successfully tested with all the cartridges I have (Sonic the Hedgehog, Altered Beast, Desert Strike, Madden NFL 94, NFL 93, Castle of Illusion and Madden NFL 96). Once dumped each one was loaded on to the SD card in my Blaze Megadrive Handheld to verify that they ran.
  </p>
  <p>
  Madden 96
  </p>
  <image src="/posts.assets/mega_mega_dumper2.jpg"/>
  <p>
  Altered Beast
  </p>
  <image src="/posts.assets/mega_mega_dumper3.jpg"/>
  <p>
  </p>
</post>
</xml>
