ROM-Reader for Super Nintendo / Super Famicom Game Cartridges

Here's a logo for the LCD that I use during bootup:

//Nintendo Logo 104x16
static const unsigned char PROGMEM nintylogo [] = {
0xF0, 0x3C, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x3C, 0xF0, 0x3C, 0xF0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x42, 0xFC, 0x3C, 0xF0, 0x00, 0x0F, 0x00,
0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0xB9, 0xFC, 0x3C, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00,
0x03, 0xC0, 0x00, 0xA5, 0xFC, 0x3C, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00,
0xB9, 0xFC, 0x3C, 0x00, 0x00, 0x3F, 0xC0, 0x00, 0x00, 0x00, 0x03, 0xC0, 0x00, 0xA5, 0xF3, 0x3C,
0xF3, 0xCF, 0x0F, 0x0F, 0xF0, 0xF3, 0xC0, 0xFF, 0xC3, 0xFC, 0x42, 0xF3, 0x3C, 0xF3, 0xCF, 0x0F,
0x0F, 0xF0, 0xF3, 0xC0, 0xFF, 0xC3, 0xFC, 0x3C, 0xF3, 0x3C, 0xF3, 0xF3, 0xCF, 0x3C, 0x3C, 0xFC,
0xF3, 0xC3, 0xCF, 0x0F, 0x00, 0xF3, 0x3C, 0xF3, 0xF3, 0xCF, 0x3C, 0x3C, 0xFC, 0xF3, 0xC3, 0xCF,
0x0F, 0x00, 0xF0, 0xFC, 0xF3, 0xC3, 0xCF, 0x3F, 0xFC, 0xF0, 0xF3, 0xC3, 0xCF, 0x0F, 0x00, 0xF0,
0xFC, 0xF3, 0xC3, 0xCF, 0x3F, 0xFC, 0xF0, 0xF3, 0xC3, 0xCF, 0x0F, 0x00, 0xF0, 0xFC, 0xF3, 0xC3,
0xCF, 0x3C, 0x00, 0xF0, 0xF3, 0xC3, 0xCF, 0x0F, 0x00, 0xF0, 0xFC, 0xF3, 0xC3, 0xCF, 0x3C, 0x00,
0xF0, 0xF3, 0xC3, 0xCF, 0x0F, 0x00, 0xF0, 0x3C, 0xF3, 0xC3, 0xCF, 0x0F, 0xFC, 0xF0, 0xF0, 0xFF,
0xC3, 0xFC, 0x00, 0xF0, 0x3C, 0xF3, 0xC3, 0xCF, 0x0F, 0xFC, 0xF0, 0xF0, 0xFF, 0xC3, 0xFC, 0x00
};

void setup() {
...
  display.clearDisplay();
  display.drawBitmap(12, 24, nintylogo, 104, 16, 1);
  display.display();
  delay(1000);
...
}

Here’s the Adafruit Clock Generator code:

//**********************************************************************************

// Adafruit Clock Generator
#include <si5351.h>

Si5351 clockgen;

//**********************************************************************************

void setup() {
...
  // Adafruit Clock Generator
  clockgen.init(SI5351_CRYSTAL_LOAD_8PF, 0);
  clockgen.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
  // Setup Clock Generator to 21.477272MHz
  clockgen.set_freq(2147727200ULL, SI5351_PLL_FIXED, SI5351_CLK0);
  clockgen.output_enable(SI5351_CLK0, 1);
  clockgen.output_enable(SI5351_CLK1, 0);
  clockgen.output_enable(SI5351_CLK2, 0);
...
}

Wire the Clock Generator to the I2C (along with power and ground) then connect CLK0 to Pin 1 on the cart. If you’re using only the center 46 pins then you’ll need to add a connection to Pin 1. I couldn’t source a matching 8 pin (2x4) connector to add next to my 46 pin connector so I cut up a spare connector and added it to my sanni clone.

Edit: If you’re not using a version of sanni’s code with the LCD, then you need to also add the I2C code (Wire.h):

// Adafruit Clock Generator
#include <Wire.h>
#include <si5351.h>

Si5351 clockgen;

Nice work yet again, this is getting better and better :)

Here’s the code changes to dump SPC7110 carts.

I added SPC7110Check() to check the status of SPC7110 registers 0x4831-0x4834 which are the bank control registers. setDataOut() and setDataIn() were added to wrap around the calls to setSRAMByte() when setting the SPC7110 registers.

void SPC7110Check(){
    // Check content of mapping registers...
    // Initial values of 0x4831-0x4834 should be set to {0, 1, 2, 0}
    byte SPC7110Reg1 = dumpByte(0, 18481, false); //0x4831
    byte SPC7110Reg2 = dumpByte(0, 18482, false); //0x4832
    byte SPC7110Reg3 = dumpByte(0, 18483, false); //0x4833
    byte SPC7110Reg4 = dumpByte(0, 18484, false); //0x4834
    display.println("Register Settings:");
    display.print("0x4831: ");
    display.print(SPC7110Reg1);
    display.print(",");
    display.print(SPC7110Reg2);
    display.print(",");
    display.print(SPC7110Reg3);
    display.print(",");
    display.println(SPC7110Reg4);
    display.display();
}

void setDataOut(){
  pinMode(d0Pin, OUTPUT);
  pinMode(d1Pin, OUTPUT);
  pinMode(d2Pin, OUTPUT);
  pinMode(d3Pin, OUTPUT);
  pinMode(d4Pin, OUTPUT);
  pinMode(d5Pin, OUTPUT);
  pinMode(d6Pin, OUTPUT);
  pinMode(d7Pin, OUTPUT);
}

void setDataIn(){
  pinMode(d0Pin, INPUT);
  pinMode(d1Pin, INPUT);
  pinMode(d2Pin, INPUT);
  pinMode(d3Pin, INPUT);
  pinMode(d4Pin, INPUT);
  pinMode(d5Pin, INPUT);
  pinMode(d6Pin, INPUT);
  pinMode(d7Pin, INPUT);
  
  // Activate pull-up resistors for input-pins
  writeByteD(255);
  delay(400);
}

I changed romSizeExp in getCartInfo() since we use it to identify the different SPC7110 carts.

void getCartInfo() {
...
  // Check RomSize
  romSizeExp = dumpByte(0, 65495, false); // 0xFFD7
  byte romSizeCalc = romSizeExp - 7;
  romSize = 1;
  while (romSizeCalc--)
    romSize *= 2;
  numBanks = (long(romSize) * 1024 * 1024 / 8) / (32768 + (long(romType) * 32768));
...
}

The dumpROM() routine adds new code to handle carts with romChips 245 and 249. We’ll have to ignore the other non-SPC7110 romChips 245 cart (Hayazashi Nidan Morita Shougi 2) for right now.

void dumpROM() {
...
  else if((romChips == 245)||(romChips == 249)){
    // SPC7110
    // Momotarou Dentetsu Happy/Super Power League 4: romChips = 245
    // Far East of Eden Zero: romChips = 249

    // Super Power League 4: 0x0B  
    // Momotarou Dentetsu Happy: 0x0C    
    // Tengai Far East of Eden Zero: 0x0D
    if (romSizeExp == 0x0B){ 
      romSize = 16;
      numBanks = 32;
    } 
    else if(romSizeExp == 0x0C){
      romSize = 24;
      numBanks = 48;
    }
    else if(romSizeExp == 0x0D){
      romSize = 40;
      numBanks = 80;  //HiROM banks
    }
 
    display.println("Dumping HiROM");
    display.print("Banks: ");
    display.println(numBanks);
    display.display();

    display.clearDisplay();
    display.setCursor(0, 0);
    display.display();
    SPC7110Check();

// 0xC00000-0xDFFFFF
    display.println("Dumping 0xC0-DF");
    display.display();
    for(int currBank=192; currBank < 224; currBank++) {
      display.print(".");
      display.display();

      // blink led
      if (currBank % 2 != 0)
        rgbLed(off_color);
      else
        rgbLed(busy_color);
  		for(long currByte=0; currByte < 65536; currByte++) {
 		    myFile.write(dumpByte(currBank, currByte, false));
 		    dumpedBytes++;
  		}
    }

    if(numBanks > 32){
      display.clearDisplay();
      display.setCursor(0, 0);
      display.println("Unlocking SPC7110");
      display.display();
      
      setDataOut();
      // Set 0x4834 to 0xFF
      setSRAMByte(0, 18484, 255, false);
      setDataIn();
      SPC7110Check();

// 0xE00000-0xEFFFFF 
      display.println("Dumping 0xE0-EF");
      display.display();
      for(int currBank=224; currBank < 240; currBank++) {
        display.print(".");
        display.display();
      
        // blink led
        if (currBank % 2 != 0)
          rgbLed(off_color);
        else
          rgbLed(busy_color);
  	  	for(long currByte=0; currByte < 65536; currByte++) {
 		      myFile.write(dumpByte(currBank, currByte, false));
 		      dumpedBytes++;
  	  	}
      }
         
      if(numBanks > 48){
// 0xF00000-0xFFFFFF
        display.println("");
        display.println("Dumping 0xF0-FF");
        display.display();
        for(int currBank=240; currBank < 256; currBank++) {
          display.print(".");
          display.display();
        
          // blink led
          if (currBank % 2 != 0)
            rgbLed(off_color);
          else
            rgbLed(busy_color);
  	    	for(long currByte=0; currByte < 65536; currByte++) {
 		        myFile.write(dumpByte(currBank, currByte, false));
 		        dumpedBytes++;
  	    	}
        }

        display.clearDisplay();
        display.setCursor(0, 0);
        display.display();
        display.println("Remapping Final Bank");
        display.display();
        
        setDataOut();
        // Set 0x4833 to 3
        setSRAMByte(0, 18483, 3, false);
        setDataIn();
        SPC7110Check();

// 0xF00000-0xFFFFFF
        display.println("Dumping 0xF0-FF");
        display.display();
        for(int currBank=240; currBank < 256; currBank++) {
          display.print(".");
          display.display();
        
          // blink led
          if (currBank % 2 != 0)
            rgbLed(off_color);
          else
            rgbLed(busy_color);
          for(long currByte=0; currByte < 65536; currByte++) {
 		        myFile.write(dumpByte(currBank, currByte, false));
 		        dumpedBytes++;
          }
        }
      }      
      // Return mapping registers to initial settings...
      setDataOut();
      setSRAMByte(0, 18483, 2, false); //0x4833: 0x2
      setSRAMByte(0, 18484, 0, false); //0x4834: 0x0
      setDataIn();
     }
  }
...
}

The code is tested with FEOEZ but I hope to test with the other SPC7110 carts (MDH and SPL4) soon.

The dumping process should show changes to the registers as they’re set. For FEOEZ, the 0x4831-0x4834 registers should go from {0,1,2,0} to {0,1,2,7} to {0,1,3,7} during the dumping process. If the registers never change from {0,1,2,0}, then the clock signal is most likely not getting to Pin 1.

I’ll post the Read/Write SRAM code next. The RTC read code is working (I think). I have to read the RTC documents to see how to interpret the data.

Adafruit Clock Generator added to the sanni board.

|500x375 |500x375

Here’s the SPC7110 Read/Write SRAM. First, I made a change to romType in getCartInfo() to classify SDD1 and SPC7110 carts as HiROM. I found it easier to handle the changes to read/write SRAM that way. I should also get around to modify dumpROM() to reflect the romType change but since the existing code works, I’ll wait to clean it up later.

void getCartInfo() {
...
  // Check if LoROM or HiROM
  romType = dumpByte(0, 65493, false); // 0xFFD5
  if ((romType == 0x32)||(romType == 0x3A)) // SDD1/SPC7110
    romType = 1;  //HiROM
  else
    romType = romType & 1; // 0xFFD5
...
}

Code was added to readSRAM() to enable the SRAM using register 0x4830. The modifications to readSRAM() are in the Dump HiRom SRAM section:

void readSRAM (char* fileName, char* folder) {
...
  // Dump HiRom SRAM
  else if (romType == 1) {
    // SPC7110 SRAM
    if ((romChips == 245)||(romChips == 249)) {
      // Configure SPC7110 SRAM Register
      setDataOut();
      // Set 0x4830 to 0x80
      setSRAMByte(0, 18480, 128, false);
      setDataIn();

      dumpByte(48, 24576, false); // Preconfigure to fix the corrupt 1st byte
      // Sram size
      long lastByte = (long(sramSize) * 128) + 24576;
      display.println("HiROM SRAM");
      display.print("SRAM Size: ");
      display.println(lastByte);
  	  display.display();
      for (long currByte = 24576; currByte < lastByte; currByte++) { //startAddr = 0x6000
        myFile.write(dumpByte(48, currByte, false)); //startBank = 0x30
      }
      setDataOut();
      // Reset 0x4830 to 0x0
      setSRAMByte(0, 18480, 0, false);
      setDataIn();
    } 	
    else {
      dumpByte(48, 24576, true); // Preconfigure to fix the corrupt 1st byte
      // Sram size
      long lastByte = (long(sramSize) * 128) + 24576;
      display.println("HiROM SRAM");
      display.print("SRAM Size: ");
      display.println(lastByte);
  	  display.display();
      for (long currByte = 24576; currByte < lastByte; currByte++) { //startAddr = 0x6000
        myFile.write(dumpByte(48, currByte, true)); //startBank = 0x30; CS high
      }
    }
  }

I made similar changes to writeSRAM() also in the HiRom section:

void writeSRAM () {
...

// HiRom
      else if (sramSize != 0 && romType == 1) {
        // SPC7110 SRAM
        if ((romChips == 245)||(romChips == 249)) {
          // Configure SPC7110 SRAM Register
          // Set 0x4830 to 0x80
          setSRAMByte(0, 18480, 128, false);
          
          long lastByte = (long(sramSize) * 128) + 24576;
          
          display.print(sramSize / 8);
          display.println("Kb HiROM");
          display.println("Writing SRAM...");
          display.display();
          
          // Write to sram bank
          for (long currByte = 24576; currByte < lastByte; currByte++) { //startAddr = 0x6000
            setSRAMByte(48, currByte, myFile.read(), false); //startBank = 0x30
            // blink led
            if (currByte % 100 != 0)
              rgbLed(busy_color);
            else
              rgbLed(off_color);
            // Print Progess to LCD
            progress = currByte * 100 / lastByte;
            if ((progress - lastprogress) > 9) {
              display.print(".");
              display.display();
              lastprogress = progress;
            }
          }
          // Reset SPC7110 SRAM Register
          setDataOut();
          // Reset 0x4830 to 0x0
          setSRAMByte(0, 18480, 0, false);
          setDataIn();
        }
        else {
          long lastByte = (long(sramSize) * 128) + 24576;

          display.print(sramSize / 8);
          display.println("Kb HiROM");
          display.println("Writing SRAM...");
          display.display();
          
          // Write to sram bank
          for (long currByte = 24576; currByte < lastByte; currByte++) { //startAddr = 0x6000
            setSRAMByte(48, currByte, myFile.read(), true); //startBank = 0x30; CS high
            // blink led
              if (currByte % 100 != 0)
                rgbLed(busy_color);
              else
                rgbLed(off_color);
            // Print Progess to LCD
            progress = currByte * 100 / lastByte;
            if ((progress - lastprogress) > 9) {
              display.print(".");
              display.display();
              lastprogress = progress;
            }
          }
        }
      }
...
}

Coming up next: the FEOEZ RTC reading code. I’ve only done the read RTC code so far. I’m not sure if the write RTC code is really necessary.

Here’s the FEOEZ RTC Read code. I currently have the call to the RTC code at the end of printinfoGLCD(). The normal cart info is displayed then it checks to see if the cart is FEOEZ. If the cart is FEOEZ, then it waits for a button press to clear the screen and read the RTC.

void printinfoGLCD() {
...
  if(romChips == 249) {
    wait();
    readRTC();
  }
}

I added two routines - showRTC() and readRTC(). readRTC() sets the SPC7110 registers to read the RTC and outputs the data to a file - rtc.ram. showRTC() opens the rtc.ram file, interprets the data, and displays the date, time, and weekday on screen.

void showRTC() {
int   rtc_data[16];
int		seconds;
int		minutes;
int		hours;
int		day;
int		month;
int		year;

int   w;
char *weekday[7] =
        {
           "Sunday",
           "Monday",
           "Tuesday",
           "Wednesday",
           "Thursday",
           "Friday",
           "Saturday"
        };
  //open file on sd card
    if (!myFile.open("rtc.ram", O_RDWR | O_CREAT)) {
    rgbLed(error_color);
    display.println("SD ERROR");
    display.display();
    while (1);
  }

  for(int r = 0; r < 16; r++)
    rtc_data[r] = myFile.read();

  seconds = ((rtc_data[1] & 7)*10) + rtc_data[0];
	minutes = ((rtc_data[3] & 7)*10) + rtc_data[2];
	hours = ((rtc_data[5] & 3)*10) + rtc_data[4];
	year =  rtc_data[11]*10 + rtc_data[10];
	year += ( 1900 );
	month = ((rtc_data[9] & 1)*10) + rtc_data[8];
	day = ((rtc_data[7] & 3)*10) + rtc_data[6];

  display.print("DATE:  ");
  display.print(month);
  display.print("/");
  display.print(day);
  display.print("/");
  display.println(year);
  display.print("TIME:  ");
  display.print(hours);
  display.print(":");
  display.print(minutes);
  display.print(":");
  display.print(seconds);

  if((rtc_data[15] & 4) == 0)	{ //Check 12/24: 1 = 24H, 0 = 12H 
	  if((rtc_data[5] & 4) == 4) //If 12H, check PM/AM: 1 = PM, 0 = AM 
	    display.println(" PM");
	  else display.println(" AM");
	}
	else display.println("");
	w = (rtc_data[12] & 7);
  display.print("DAY:   ");
  display.println(weekday[w]);
  display.display();

  // Close the file:
  myFile.close();
}

void readRTC() {
  // Read FEOEZ RTC-4513 Chip 

  //open file on sd card
    if (!myFile.open("rtc.ram", O_RDWR | O_CREAT)) {
    rgbLed(error_color);
    display.println("SD ERROR");
    display.display();
    while (1);
  }
  display.clearDisplay();
  display.setCursor(0, 0);

  // Signal beginning of dumping process
  display.println("Reading RTC Chip...");
  display.display();
  
	setDataOut();
  // Enable RTC Chip:  0x4840: 0x1
  setSRAMByte(0, 18496, 1, false); //0x4840
  // Set RTC Chip to Read Mode:  0x4841: 0xC
  setSRAMByte(0, 18497, 0xC, false); //0x4841
  // Set RTC to Data Byte 0
  setSRAMByte(0, 18497, 0, false); //0x4841
	setDataIn();

  for(int r = 0; r < 16; r++) {   
    // Check RTC Status:  0x4842
    dumpByte(0, 18498, false); //0x4842
    myFile.write(dumpByte(0, 18497, false)); //0x4841
  }
  // Disable RTC Chip:  0x4840: 0x0
  setDataOut();
  setByte(0, 18496, 0);
  setDataIn();

  // Close the file:
  myFile.close();

  showRTC();

  // Signal end of process
  rgbLed(success_color);
  display.println("Saved as rtc.ram");
  display.display();
}

Ran into an interesting problem while reading a new cart with my sanni clone. I normally connect the reader thru a USB hub and have not had any problems reading/writing carts.

I recently picked up a copy of F1 ROC II (Exhaust Heat) which is a Seta DSP (ST-010 chip) cart. When I first connected the new cart to the reader, the reader could not detect the cart. I modified the code to force dump all of the banks and they were all FFs. I moved the reader to a different PC to run more extensive tests and found it dumped 100% with the original code. The only difference was the direct USB connection vs the USB hub. I moved the reader back to the original PC and bypassed the USB hub and it worked.

I should be getting the other Seta DSP/RISC carts (ST-011/ST-018) so I'll test those out. If anyone has a cart that doesn't detect properly (or dumps all FFs), make sure your reader is connected directly to USB (no hub).

Found a small checksum error bug in the compare_checksum(). Similar to the earlier checksum error bug in getCartInfo(). Need to make sure we compare 4 characters. The original sprintf code used “%X” which drops leading 0s resulting in an error.

void compare_checksum() {
...
  char calcsumStr[5];
  sprintf(calcsumStr, "%04X", calc_checksum(fileName, folder));
...
}

I discovered the bug dumping Super Power League 4 because it has checksum 0x01AA. I can now confirm that my SPC7110 code changes work with all 3 carts - FEOEZ, MDH, and SPL4.

The checksum verification code need to be tweaked for MDH - the checksum should be checksum*2. EDIT: Here’s the calc_checksum code addition:

unsigned int calc_checksum (char* fileName, char* folder) {
...
    else if (calcFilesize == 24 || calcFilesize == 28) {
      // Momotarou Dentestu Happy Fix 3MB (24Mbit)
      if ((calcFilesize == 24) && (romSizeExp = 0x0C)) {
       for (unsigned long i = 0; i < myFile.fileSize(); i++) {
          calcChecksum += myFile.read();
       }
       calcChecksum = 2 * calcChecksum;
      }
      else {
        for (unsigned long i = 0; i < (calcFilesize / 16); i++ ) {
          // Add all the bits in the 16Mbit chunks
          for (unsigned long j = 0; j < 2097152; j++) {
            calcChecksum += myFile.read();
          }
          // Add all the chunks together
          calcChecksum +=  calcChecksumChunk;
          calcChecksumChunk = 0;
        }
        // Add the 8Mbit rest
        for (unsigned long j = 0; j < 1048576; j++) {
          calcChecksumChunk += myFile.read();
        }
        calcChecksum +=  2 * calcChecksumChunk;
      }
    }
...
}

There is a modification to the SPC7110 dumpROM() code that I posted earlier. The change is needed to handle Hayazashi Nidan Morita Shougi 2 which is the only Seta ST018 cart. Since the cart uses romChips = 245 (0xF5), we need to separate it from two of the SPC7110 games - Momotarou Dentestu Happy (MDH) and Super Power League 4 (SPL4). Since MDH and SPL4 are HiROMs and Hayazashi 2 is LoROM, I added a romType check to the SPC7110 section of dumpROM().

void dumpROM () {
...
  else if((romChips == 245)||(romChips == 249)){
    if (romType == 0) {
      // ST018
      // Hayazashi Nidan Morita Shougi 2: romChips = 245
      <add LoROM dumping routines>
    ...
    }
    else if (romType == 1) {
      // SPC7110
      // Momotarou Dentetsu Happy/Super Power League 4: romChips = 245
      // Far East of Eden Zero: romChips = 249
      <see SPC7110 code posted above>  
     ...
    }
  }
}

I’ve confirmed all of the SDD1, SuperFX, SPC7110 and Seta DSP (ST010/ST011/ST018) carts dump properly. I also tested one of the CX4 carts - Rockman/Mega Man X2. The other CX4 cart - Rockman/Mega Man X3 should work but it is untested.

There is currently a lot of duplication of routines in dumpROM() that needs to be cleaned up. I’ll work on streamlining the code and post the complete code after I wrap up my testing of enhanced carts.

Next up, I’ll be testing the ExHiROM cart Tales of Phantasia once it arrives.

Got around to redumping Star Ocean. With the Adafruit Clock Generator connected, Star Ocean cart no longer needs the verifySORead code. The SDD1 carts now dump correctly every time.

EDIT: Ran tests without the clock and Star Ocean still dumped okay. Maybe it isn't the Clock Generator? I did a couple runs and both dumps were good without the verifySORead. On the other hand, the clock is definitely needed for FEOEZ.

Made a huge time savings in calc_checksum(). Added a 512 byte buffer for the myFile.read() and it now completes the task in seconds versus minutes. I'll post the code after I clean it up.

EDIT: Applied the same 512 byte buffer to myFile.write() in dumpROM() and it cut my dump times by more than half! Got a 2MB (16Mbit) LoROM dumping AND checking the checksum in 1 minute - it used to take around ~3:50.

In case anyone cares, the 512 byte buffer was chosen as it is the minimum block size used by the SD card. Reading and writing anything less than 512 bytes to the SD card slows things down.

skaman: I've confirmed all of the SDD1, SuperFX, SPC7110 and Seta DSP (ST010/ST011/ST018) carts dump properly. I also tested one of the CX4 carts - Rockman/Mega Man X2. The other CX4 cart - Rockman/Mega Man X3 should work but it is untested.

This is so amazing :) It perfectly shows the power of open source 8)

Okay, here’s the SD buffer code to speed up reads and writes. I’ll post the complete dump code after I test the ExHiROM carts and a couple other enhanced chip carts.

First off we add the SDBuffer[512] in the define section.

Next, my current calc_checksum() code. Some of it is untested as I don’t have all of the different sizes (missing 1.5MB, 2.5MB & “Normal” 3MB). I referred to ROMs from the GoodSets where needed.

//Define Cart Reader Variables
...
byte SDBuffer[512];
...

//*************************CALCULATE CHECKSUM**************************************
unsigned int calc_checksum (char* fileName, char* folder) {
  unsigned int calcChecksum = 0;
  unsigned int calcChecksumChunk = 0;
  int calcFilesize = 0;
  unsigned int c = 0;
  unsigned long i = 0;
  unsigned long j = 0;

  if (strcmp(folder, "root") != 0)
    sd.chdir(folder);

  // If file exists
  if (myFile.open(fileName, O_READ)) {
    calcFilesize = myFile.fileSize() * 8 / 1024 / 1024;
    if (calcFilesize == 12 || calcFilesize == 20) {
      // Divide filesize by 8 to get number of 8Mbit chunks
      for (i = 0; i < (calcFilesize / 8); i++ ) {
        // Add all the bits in the 8Mbit chunks
        for (j = 0; j < (1048576 / 512); j++) {
          myFile.read(SDBuffer, 512);
          for (c = 0; c < 512; c++) {
            calcChecksumChunk += SDBuffer[c];
          }
        }
        calcChecksum = calcChecksumChunk;
      }
      calcChecksumChunk = 0;
      // Add the 4Mbit rest
      for (j = 0; j < (524288 / 512); j++) {
        myFile.read(SDBuffer, 512);
        for (c = 0; c < 512; c++) {
          calcChecksumChunk += SDBuffer[c];
        }
      }
      calcChecksum +=  2 * calcChecksumChunk;
    }
    else if (calcFilesize == 24 || calcFilesize == 28) {
      // Momotarou Dentestu Happy Fix 3MB (24Mbit)
      if ((calcFilesize == 24) && (romSizeExp = 0x0C)) {
        for (i = 0; i < (myFile.fileSize() / 512); i++) {
          myFile.read(SDBuffer, 512);
          for (c = 0; c < 512; c++) {
            calcChecksumChunk += SDBuffer[c];
          }
        }
        calcChecksum = 2 * calcChecksumChunk;
      }
      else {
        for (i = 0; i < (calcFilesize / 16); i++ ) {
          // Add all the bits in the 16Mbit chunks
          for (j = 0; j < (2097152 / 512); j++) {
            myFile.read(SDBuffer, 512);
            for (c = 0; c < 512; c++) {
              calcChecksumChunk += SDBuffer[c];
            }
          }
          calcChecksum = calcChecksumChunk;
        }
        calcChecksumChunk = 0;
        // Add the 8Mbit rest
        for (j = 0; j < (1048576 / 512); j++) {
          myFile.read(SDBuffer, 512);
          for (c = 0; c < 512; c++) {
            calcChecksumChunk += SDBuffer[c];
          }
        }
        calcChecksum +=  2 * calcChecksumChunk;
      }
    }
    else if (calcFilesize == 48) {
 // Star Ocean/Tales of Phantasia Fix 6MB (48Mbit)
 // Add the 4MB (32Mbit) start
      for (j = 0; j < (4194304 / 512); j++) {
        myFile.read(SDBuffer, 512);
        for (c = 0; c < 512; c++) {
          calcChecksumChunk += SDBuffer[c];
        }
        calcChecksum = calcChecksumChunk;
      }
      calcChecksumChunk = 0;
      // Add the 2MB (16Mbit) end
      for (j = 0; j < (2097152 / 512); j++) {
        myFile.read(SDBuffer, 512);
        for (c = 0; c < 512; c++) {
          calcChecksumChunk += SDBuffer[c];
        }
      }
      calcChecksum +=  2 * calcChecksumChunk;
    }
    else {
    //calcFilesize == 2, 4, 8, 16, 32, 40, etc
      for (i = 0; i < (myFile.fileSize() / 512); i++) {
        myFile.read(SDBuffer, 512);
        for (c = 0; c < 512; c++) {
          calcChecksumChunk += SDBuffer[c];
        }
        calcChecksum = calcChecksumChunk;
      }
    }
    myFile.close();
    sd.chdir();
    return (calcChecksum);
  }
  // Else show error
  else {
    display.println("ERROR");
    display.println("DUMP ROM 1ST");
    display.display();
    return 0;
  }
}

The checksum calculation greatly benefits from the 512 byte buffer. We read the file on the SD card in 512 byte chunks to perform our calculation. Calculations used to take minutes but now it takes only seconds to complete.

Now for the dumping code. In an effort to streamline the dumpROM() code, I created a dumpLoROM() and dumpHiROM(). These routines dump 512 bytes into the SDBuffer then writes the buffer to the file on the SD card.

long dumpLoROM() {
  display.println("Dumping LoROM");
  display.print("Banks: ");
  display.println(numBanks);
  display.display();
  long dumpedBytes = 0;
  // Read up to 96 banks starting at bank 0x00.
  for (int currBank = 0; currBank < numBanks; currBank++) {
    // give status updates via LCD
    display.print(".");
    display.display();

    // Dump the bytes to SD
    for (long currByte = 0x8000; currByte < 0x10000; currByte += 512) {
      for (unsigned long c = 0; c < 512; c++) {
        SDBuffer[c] = dumpByte(currBank, currByte + c, false);
        dumpedBytes++;
      }
      myFile.write(SDBuffer, 512);
    }
  }
  return(dumpedBytes);
}

long dumpHiROM(int currBank, int endBank, long dumpedBytes) {
  for(currBank; currBank < endBank; currBank++) {
    // give status updates via LCD
    display.print(".");
    display.display();
    
    // Dump the bytes to SD
   for(long currByte=0; currByte < 0x10000; currByte += 512) {
      for (unsigned long c = 0; c < 512; c++) {
        SDBuffer[c] = dumpByte(currBank, currByte + c, false);
        dumpedBytes++;
      }
      myFile.write(SDBuffer, 512);
   }
  }
  return(dumpedBytes);
}

So substitute dumpLoROM() and dumpHiROM() into dumpROM() where needed. Here’s the calls as I have them right now.

void dumpROM() {
...
  long dumpedBytes = 0;
  ...
  if ((romChips != 67)&&(romChips != 69)&&(romChips != 245)&&(romChips != 249)) {
    if (romType == 0) {
     ...
       dumpedBytes = dumpLoROM();
    ...
    } 
    else if (romType == 1) {
      ... 
        dumpedBytes = dumpHiROM(0xC0, numBanks + 0xC0, dumpedBytes);
    ...
    }
  }
  else if((romChips == 67)||(romChips ==69)) {
    // SDD1
    // Street Fighter Alpha/Zero 2 (J, U, E): romChips = 67
    // Star Ocean (J): romChips = 69 
    ...
      dumpedBytes = dumpHiROM(0xF0, 0x100, dumpedBytes);
  ...
  }
  else if((romChips == 245)||(romChips == 249)){
    if (romType == 0) {
      // ST018
      // Hayazashi Nidan Morita Shougi 2: romChips = 245
      dumpedBytes = dumpLoROM();
    }
    else if (romType == 1) {
      // SPC7110
      // Momotarou Dentetsu Happy/Super Power League 4: romChips = 245
      // Far East of Eden Zero: romChips = 249
    ...
      dumpedBytes = dumpHiROM(0xC0, 0xE0, dumpedBytes);
    ...
      dumpedBytes = dumpHiROM(0xE0, 0xF0, dumpedBytes);
    ...         
      dumpedBytes = dumpHiROM(0xF0, 0x100, dumpedBytes);
    ...
      dumpedBytes = dumpHiROM(0xF0, 0x100, dumpedBytes);
    ...
    } 
  }
}

Got a code change for the CX4 (Rockman/Mega Man X2 & X3) carts. I had previously only tested the code on X2 but I was finally able to test on X3.

Rockman/Mega Man X2 & X3 both start with the mapping register 0x7F52 set to 1. X2 needs to be set to 0 in order to fully dump the ROM. X3 needs to be set to 1 to dump the entire ROM.

I added code to differentiate between X2 and X3 and also switched the register setting code to use the slower setSRAMByte() to make sure the changes worked.

Code changes:

void dumpROM() {
...
    if (romType == 0) {
      byte initialCX4Map = 0;
      byte mmxType = 0;
      // CX4 (Mega Man X2/Mega Man X3/Rockman X2/Rockman X3) romChips = 243 (0xF3)
      if(romChips == 243){
        // Check initial content of mapping register...
        // Mega Man/Rockman X2 & X3 should both return 1
        initialCX4Map = dumpByte(0, 0x7F52, false); //0x7F52
        
        display.print("CX4 Register: ");
        display.println(initialCX4Map);
        display.display();

        // Check ROM Name to determine whether X2 or X3       
        mmxType = dumpByte(0, 0xFFC9, false) & 0xF; // 0xFFC9
     
        // Mega Man/Rockman X2 needs to be set to 0
        if((mmxType == 2)&&(initialCX4Map != 0)){
          setDataOut();
          // Set 0x7F52 to 0
          setSRAMByte(0, 0x7F52, 0, false);
          setDataIn();
        }
        // Mega Man/Rockman X3 needs to be set to 1
        if((mmxType == 3)&&(initialCX4Map == 0)){ 
          setDataOut();
          // Set 0x7F52 to 1
          setSRAMByte(0, 0x7F52, 1, false);
          setDataIn();
        }
      }
      dumpedBytes = dumpLoROM();
      if(romChips == 243){
        // Return mapping register to initial setting...
        setDataOut();
        setSRAMByte(0, 0x7F52, initialCX4Map, false);
        setDataIn();
      }
    }
...
}

Is the latest version of this source made available public? I don't want to make a mistake pasting pieces of code from the forums together... :confused:

My code is in constant flux so I haven't posted the complete code. I've been testing the special enhanced chip games. So far, I've dumped all of the CX4, OBC1, SDD1, S-RTC, ST010, ST011, ST018, and SuperFX carts. ExHiROM carts also work but it requires byte verification code like verifySORead.

I'm finishing up testing on the ExHiROM and S-RTC (Daikaijuu Monogatari 2) code. I'll post my complete code after I test a couple other carts that should be arriving soon. I also have to fix a bug that prevents writing to the SD card (any cart that uses an extended ASCII character in the ROM name will not be able to write to the SD).

If you're building the reader, then the original codes that have been posted will get you up and running. There are only a small number of enhanced chip games.

Some progress notes...

S-RTC, DSP1, DSP2, and DSP3 all dump, read/write SRAM. I only tested one DSP1 cart (Super Mario Kart) but it works. Waiting for a DSP4 cart to get here.

The S-RTC cart has been kicking my @ss trying to get the RTC data. There isn't much documentation on the S-RTC chip so communicating with it is like flying blind. I've got the chip changing modes but the read command isn't working for me right now. Too bad the chip isn't fully documented like the EPSON RTC used in FEOEZ which has the full datasheet available.

I also added code to dump the Nintendo Power (SF Memory Cassette) cart and I'm trying to see if the SRAM is accessible without any special commands.

Breakthrough!

I managed to dump the SA-1 cart. I actually dumped PGA Tour 96 yesterday before my previous post. I was going to include the news in that post but since I only had only managed it once (I finished the hardware addition that same day), I deleted the details before posting. I managed to dump the same cart again today and verified the contents of the ROM which appears to be a previously undumped version of the game. My ROM and SRAM dump both work in higan although I haven’t played thru a game yet.

Dumping the SA-1 cart requires attaching a CIC chip in lock configuration. I built two different versions of the CIC lock - one using a SuperCIC 16F630 and one with a snesCIC 12F629. I’ve dumped the PGA Tour 96 using both versions. The cart connector CIC pins (24, 25, 55, 56) need to be connected to the CIC lock chip. Pin 56 is connected to the CIC chip and a 3.072MHz clock signal (I’m using one of the other clock outputs from the Adafruit Clock Generator).

The CIC-SA1 connection is a bit hit-or-miss right now. Most of the time it doesn’t connect but it will eventually work. I think I have to tweak the reset timings to get it working more consistently. What was amazing is that I managed to dump Super Mario RPG on the first attempt using the SuperCIC. It was nice to finally get a copy of my savegame off the cart. Kirby Super Star dumped on the first attempt with the snesCIC. Kirby’s Dream Land 3 dumped ok but is giving inconsistent SRAM dumps so I suspect the battery might be dying.

Another good thing about the CIC addition is that it works fine with the other non-SA1 carts. There is one cart that does need to have the CIC module removed and it is the Nintendo Power (SF Memory Cassette). With the CIC module connected, the Nintendo Power cart won’t be recognized by the reader. I’m assuming the CIC module and the MX15001TFC processor chip don’t get along. I’ll have to do more testing to see if there is a workaround.

EDIT: The changes to the setup() code with the CIC Lock Reset outlined below have fixed the issue with the one problematic Nintendo Power cart.

I’ll post more details after further testing.

Excellent! Thanks for sharing the big news.

I am now looking forward to receiving my electronic parts so I can try it out here.