ROM-Reader for Super Nintendo / Super Famicom Game Cartridges

Hi Arduino community,

would you like to read the ROM-contents of your Super Nintendo / Super Famicom game cartridges?

Using an Arduino and some 74HC595 shift-out registers, you can build a game cartridge-reader. Schematics and source code are included (please login to get access to the attachments). It even reads the Japanese game cartridge “Star Ocean” (one of the few game cartridges that utilise the “S-DD1” chip for graphics compression).

Have fun & best regards,
Michael
PS: This is my first Arduino-based project and I am an amateur in electrical engineering - it may contain severe errors and blow up your entire equipment, but at least it worked fine for me without doing any damage to my equipment… :slight_smile:

Cart_ROM_Reader_in_action.jpg

Cart_ROM_Reader_v0.1_2013-04-07.zip (34.4 KB)

Edit from the future: For the 2020 version follow this link https://github.com/sanni/cartreader/wiki/How-to-build-the-PCB

Original Post: I followed your instructions and build the dumper myself. It's working great. :) Did you ever implement sram reading?

Nice! :)

Sorry, I did never dump actual SRAM-data from my carts.

However, using the same sources as I did, you should be able to implement it quite easily: 1. Cartridge addressing scheme, i.e. which lines you have to set low / high to tell the cartridge that you want to read SRAM: http://www.emulatronia.com/doctec/consolas/snes/sneskart.html#cartridgeaddressing 2. Code in Python for the Raspberry Pi by waterbury that includes SRAM-reading (just do a text-search for SRAM on this page): https://github.com/waterbury/SNES-Pi/blob/master/MCP23017_CartReader/cart_reader.py 3. Reading SRAM-size and decoding Romtype (lorom / hirom) from the cart-header is already implemented in my code...

HTH :) & best regards Michael

With the help of the SdFat library and by changing "Serial.write(dumpByte(currBank, currByte));" to "myFile.write(dumpByte(currBank, currByte));" it was very easy to dump the rom to an SD card instead of sending it over serial to a PC. It takes 12.5 minutes for a 1MB LoROM/SlowROM game compared to 18 minutes over serial. And 100 minutes for a 8MB ExHiROM/FastROM.

You just got to love the arduino :)

Ah, nice speed boost!

Sorry, I am curious ;) : Is it an Arduino Pro Mini and did you use the analog pins for SPI-communication to the sd card reader?

Best regards Michael

Yes it's one of those $4 Arduino Pro Mini's from ebay with the Atmega328. I'm using pins 10 to 13 for the SD card and the analog pins for the shift register. Had to remove the debug features though because the Arduino IDE complained about low memory now that the SdFat lib is also in use.

Hi Guys!

@MichlK: Thanks for posting this project, I have been fantasizing over a snes hardware sampler for ages!

I ordered the parts to make your reader and have already been studiying how the audio is stored. Hope to parse the rom and dump the samples onto an sd to play back afterwards via midi.

This is going to be fun and exciting! I'll definitely keep you posted

Thanks again, Alex

I played a little bit more with your code as a base :grin:

sanni: I played a little bit more with your code as a base :grin:

Wow - amazing what you have done there! 8) Thanks for sharing!!!

Best regards Michael PS: From Chrono Trigger to EarthBound - the dumper is in very good company... ;)

I also follow the instruction and have my dumper done successfully. Thanks for the sharing!

I am extending my code to support carts with SPC7110, with partial success – I can read most of the ROM data correctly, but have to be done by parts.

SPC7110 is a special chip that appears in a few SNES carts in 1995-1997. Carts with this chip features a 8Mb program ROM and a variable-size data ROM. In Far East of Eden Zero (Tengai Makyo Zero), the size of the data ROM is 32Mb.

A few things that we need to know in order to read the data from the ROMs.

  • If an unlock sequence is not written to registered addressed 0x4834, only the first 8Mb of the data ROM can be read. Attempts to read the other parts just give you the same first 8Mb of the data ROM. The unlock sequence is as follows: 0x80, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00.
  • By default, the program ROM is mapped to addresses 0xc00000 to 0xcfffff while the first 24Mb of the data ROM is mapped to 0xd00000 to 0xffffff.
  • Register at 0x4833 can be used to decide which 8Mb of data ROM data be mapped to addresses 0xf00000 to 0xffffff. If it has 3 as its value, the last 8Mb of the 40Mb data ROM is mapped to that range of addresses. To write to that register, it appears that we need to put 0xff to register 0x4834, as shown in the ucon64 swc.c code.

For some unknown reason, my reader fails to write the registers most of the time. I need to take the following alternative approach.

  1. Upload and run the unlock0 script which just makes sure we have correct initial register values. Once you start running the script, open the Serial Monitor. If the program says 0x4844-0x4841 are not of values 0, 2, 1, 0, close the serial monitor, disconnect, reconnect the Arduino USB/power and open the serial monitor again.
  2. Without disconnecting the Arduino power, upload and run the read0 script. This time, use a terminal console to obtain the data stream from Arduino. This gives you the program ROM and the 1st 8Mb of the data ROM data. Note you may need to manually edit the two hex starting at 00100000 to 01.
  3. Upload and run the unlock1 script which just unlocks the cart, without changing the register at 0x4833. Once you start running the script, open the Serial Monitor. If the program says 0x4844 is not of value 7, close the serial monitor, disconnect, reconnect the Arduino USB/power and open the serial monitor again. Only when the program says the value is 7, we are sure the unlock sequence has been successfully written and the data ROM unlock.
  4. Without disconnecting the Arduino power, upload and run the read1 script. This time, use a terminal console to obtain the data stream from Arduino. This gives you the 2nd and 3rd 8Mb of the data ROM data. Note that you may need to manually edit the two hex at the beginning to 20.
  5. Upload and run the unlock2 script which unlocks the cart and changes the register at 0x4833 to 3. Once you start running the script, open the Serial Monitor. If the program says 0x4844 is not of value 7, close the serial monitor, disconnect, reconnect the Arduino USB/power and open the serial monitor again. Only when the program says the value is 7, we are sure that the data ROM has been unlocked. The program should also say the value of the 0x4833 register is 3.
  6. Without disconnecting the Arduino power, upload and run the read2 script. This time, use a terminal console to obtain the data stream from Arduino. This gives you the program ROM and the last 8Mb of the data ROM data. Note that you may need to manually edit the two hex at the beginning to BF.
  7. Use some program to join the file obtained in the above steps. Now you should have got the complete ROM.

arduino_spc7110.zip (29 KB)

tylau0:
I am extending my code to support carts with SPC7110, with partial success – I can read most of the ROM data correctly, but have to be done by parts.

For some unknown reason, my reader fails to write the registers most of the time. I need to take the following alternative approach.

Very cool - SPC7110 support - thanks for sharing! :slight_smile:

I do not own a cart with the SPC7110 (let alone the legendary Far East of Eden Zero ;)) but as far as I can think of, there may be two reasons for the problem with writing the registers:

  1. I often had the problem that the connection between the socket for the cart and the jumper wires was not good enough, so I put a folded sheet of paper between the two rows of the jumper wires to improve connectivity…
  2. I do not know, which timing is expected when sending the unlock codes (needs to be clocked???). Maybe a small delay between sending each byte could help? Switching between 0x00 and 0x80 would not necessarily mean that a specific timing is necessary, but as the sequence includes two consecutive 0x80, I wonder if there is a specific timing and/or if you e.g. can read the registers in between the sequence and the value changes shortly after you write to it, so you know that you can send the next byte…

Just my 2 cents…

Best regards
Michael

Hey guys

Made one of these, works pretty good. Can't seem to dump some common games like mario kart, mario world, pilotwings, and a few others but works pretty good for the rest of them. I haven't really looked into it yet, if anybody knows whats up with that let me know.

Anyways. I managed to get it to go quite a bit faster.

I cut the code down a bit, took out the delays (the snes would read from the cart way faster than the arduino, seems to work) and I used the fastDigitalWrite and fastDigitalRead functions I found here which are closer to bare port manipulation than the stock digitalRead and digitalWrite. A bunch of things are changed from variables to defines so that the pins have fixed values at compile time. To make the fastDigital functions work right.

So far it looks like it takes 177s (2m 57s) to dump a 1mb cart, where before it was taking 1113s (18m 33s). About 6x faster.

I made a little python program for it too that makes it a little easier to work with and shows progress as it dumps. It doesn't start dumping automatically now, it waits for "R" to be sent to it. So the python program can tell it to dump or do other things, like "I" to just give you cart info or read/write SRAM (which is what I plan to do next, has anyone done this with theirs yet?). You can swap cartridges without unplugging because it'll always be idle without the computer telling it to do something.

If anyone wants the code let me know. It's a bit of a mess.

Here is my current version of MichlK's code, it's a shield for an Arduino Mega. The rom gets saved to an SD card. You can also read/write save files to the SRAM, display information about the cartridge on the LCD or calculate the checksum of your rom dump.

You control it using the push button on the left. One click moves the selection down, a double click moves it up and a long press executes the current menu option.

I used this Prototype PCB from ebay item # 201205316404 (you need to cut a trace on the top of the pcb leading from the switch to the reset pin) That SD module from ebay item # 290901349431 And a white 0.96" IIC I2C 128X64 OLED LCD from ebay item # 291216700457

I also incorporated the DigitalIOPerformance lib now and dumping an 1MByte (8Mbit) LoRom game takes 62 seconds while dumping a 4Mbyte (32Mbit) HiRom game takes 4 minutes and 10 seconds.

Success!

I finally finished building a duplicate of sanni's cart reader. The hardest part was finding the cart connector (aside from waiting on parts from China).

If anyone needs a SNES cart connector, I located a Centronic 46P connector. I contacted a distributor and found out that they sell on eBay. The seller is wonderco_buy and they added the part listing "Industrial Card Edge Slot Socket Connector 23x2P 46P 2.54mm 0.1" 3A". Shipping to the US from Taiwan is fast (less than a week).

I want to thank MichlK and sanni for all of their help with this project!

If anyone wants to dump SDD-1 carts (Star Ocean or Street Fighter Alpha/Zero 2), then you must enable verifySORead. Set verifySORead=1 in the code. Otherwise, the dump will have random corrupt bytes. The dump will take longer but it will produce a proper dump.

To dump Street Fighter Alpha/Zero 2, changes need to be made to the dump code. It requires the same dumping setup as Star Ocean. Street Fighter Alpha/Zero 2 is romChips=67.

if ((romChips != 67)&&(romChips != 69))
...
    if(romChips==69)
    {
        romSize = 48;
        numBanks = 96;  //HiROM banks
    }
    // Street Fighter Alpha/Zero 2
    else if(romChips==67)
    {
        romSize = 32;
        numBanks = 64;  //HiROM banks
    }

I also found that sanni's calc_checksum needed an addition to handle Star Ocean (and Tales of Phantasia):

    // Star Ocean/Tales of Phantasia Fix 6MB (48Mbit)
    else if (calcFilesize == 48) {
      // Add the 4MB (32Mbit) start
      for (unsigned long j = 0; j < 4194304; j++) {
        calcChecksum += myFile.read();
      }
      // Add the 2MB (16Mbit) end
      for (unsigned long j = 0; j < 2097152; j++) {
        calcChecksumChunk += myFile.read();
      }
      calcChecksum +=  2 * calcChecksumChunk;
    }

One last change was to sanni's code that write/verify SRAM. There needs to be a delay in the dumpByte() code or the VERIFY.SRM will always be wrong. The problem is that any delay added to dumpByte() ripples through all routines that call it which greatly slows everything down.

My solution was a small modification to the readSRAM() code to fix the problem. The SRAM dumps and verifies quickly and now it does it properly.

  // Dump LoRom
  if (romType == 0) {
    dumpByte(112, 0, false); // Preconfigure to fix the corrupt 1st byte
    // Sram size
    long lastByte = (long(sramSize) * 128);
    for (long currByte = 0; currByte < lastByte; currByte++) { //startAddr = 0x0000
      myFile.write(dumpByte(112, currByte, false)); //startBank = 0x70; CS low
    }
  }

  // Dump HiRom
  else  if (romType == 1) {
     dumpByte(48, 24576, true); // Preconfigure to fix the corrupt 1st byte
    // Sram size
    long lastByte = (long(sramSize) * 128) + 24576;
    for (long currByte = 24576; currByte < lastByte; currByte++) { //startAddr = 0x6000
      myFile.write(dumpByte(48, currByte, true)); //startBank = 0x30; CS high
    }
  }

I forgot one last addition to sanni's code in dumpROM(). The original code is missing the dumpedBytes++ at the end of the Star Ocean section:

       // Dump the bytes to SD one by one
        for (long currByte = 0; currByte < 65536; currByte++) {
          do {
            currDumpedByte = dumpByte(currBank, currByte, false);
            if (verifySORead)
              currDumpedByteVerify = dumpByte(currBank, currByte, false);
            else
              currDumpedByteVerify = currDumpedByte;
          }
          while (currDumpedByte != currDumpedByteVerify);
          myFile.write(currDumpedByte);
          dumpedBytes++;
        }

Fixed a bug in getCartInfo() where a checksum byte with upper nibble of 0 has the zero dropped. This resulted in erroneous checksum error messages.

  // Get Checksum as string
  sprintf(checksumStr, "%02X%02X", dumpByte(0, 65503, false), dumpByte(0, 65502, false));

Added code to dumpROM() for CX4 carts. Actually only really needed for Mega Man/Rockman X2.

  if ((romChips != 67)&&(romChips != 69)&&(romChips != 243)) {
...

  else if(romChips == 243){
   // CX4 - Mega Man X2/Mega Man X3/Rockman X2/Rockman X3: romChips = 243
    display.println("Dumping LoROM");
    display.print("Banks: ");
    display.println(numBanks);
    display.display();

    // Check initial content of mapping register...
    // Mega Man/Rockman X2 should return 1, Mega Man/Rockman X3 should return 0
    byte initialCX4Map = dumpByte(0, 32594, false); //0x7F52
      
    // Mega Man/Rockman X2 needs to be set to 0 before dumping
    setByte(0, 32594, 0);  //0x7F52

      // Read up to 96 banks starting at bank 0×00.
    for (int currBank = 0; currBank < numBanks; currBank++) {

      // give status updates via LCD or LED
      display.print(".");
      display.display();

      // blink led
      if (currBank % 2 != 0)
        rgbLed(off_color);
      else
        rgbLed(busy_color);

      // Dump the bytes to SD one by one
      for (long currByte = 32768; currByte < 65536; currByte++) {
        myFile.write(dumpByte(currBank, currByte, false));
        dumpedBytes++;
      }
    }
    // Return mapping register to initial setting...
    setByte(0, 32594, initialCX4Map);  //0x7F52
  }

credits to byuu (and waterbury) for the CX4 info

Success! I managed to enable reliable SPC7110 dumping (FEOEZ). The dumping code runs all-inclusive (no switching back and forth) and works 100% of the time. It requires a hardware addition and some changes to the code.

For the hardware addition, you need to add a 21.477272MHz clock signal to Pin 1. I used the Adafruit Clock Generator module with the Si5351 chip (https://learn.adafruit.com/adafruit-si5351-clock-generator-breakout/overview) to generate the clock signal. I wired the module up to the I2C with output to Pin 1 of the cart. Adafruit has a library for the module but I used the Si5351Arduino library (https://github.com/etherkit/Si5351Arduino).

For the code, one big change and a bunch of small changes need to be made. The big change is that you cannot use setByte() to set the SPC7110 registers - the timing is off. The slower byte setting routine needs to be added. sanni's setSRAMByte() code works to set the registers. The small changes that need to be added are the SPC7110 register settings to fully unlock and dump the cart. Most of the register changes are outlined in tylau0's code but there are a couple other settings that (may) need to be changed.

I'll clean up my code and post it sometime.

credits to caitsith2 for the SPC7110 unlocking details

skaman: Success! I managed to enable reliable SPC7110 dumping (FEOEZ). The dumping code runs all-inclusive (no switching back and forth) and works 100% of the time. It requires a hardware addition and some changes to the code.

Never thought that this would be possible with an Arduino... :o Congratz & thanks for sharing!

Best regards Michael PS: Back in September last year, I ordered such a clock generator module myself - pretty handy stuff... :)

Thanks to you, Michael, for getting this started. Time to add the clock generator to your reader setup!

In the process of trying to add SRAM read/write for FEOEZ, I came across a post by byuu that states that the SPC7110 unlock sequence is unnecessary. I removed the unlock sequence from my code and the FEOEZ cart still dumps! The key element is the Clock to Pin 1. Without the clock, the register changes don't work.

I'll post the code once I figure out why my FEOEZ SRAM dumps are weird. Anyone got a FEOEZ .srm file they care to share? If not, I'll have to sit down and actually play this game for awhile.

EDIT: Nevermind on the savefile. I figured out why my SRAM dumps were bad - dying battery! Pulled the old battery and put in a new battery + holder. FEOEZ SRAM dumping works (well it actually always did just couldn't tell). Going to add SRAM writes and then look at adding the RTC stuff.