ROM-Reader for Super Nintendo / Super Famicom Game Cartridges

Indeed the GB memory carts are very expensive. However it would be a lot of fun to replicate everything the Japanese machine did with SF and GB memory using arduino! ;D ;D ;D

Has anyone had any luck with writing the N64 repros? I see that sanni made an update about a month ago that added some N64 writing functions, but it's quite slow.

Does anyone have that working faster? Or can someone point me to the place in the code that controls that so I can start poking around the code and trying things out? :slight_smile:

There were two types of N64 repros being sold at aliexpress. The "old" ones with the Spansion S29GL256N and the newer ones with the Intel 4400L0ZDQ0.

So far the cart reader only supports the Spansion S29GL256N (Datasheet).

Now the S29GL256N can be written in two ways:

  • 2 bytes(=1 words) at a time
  • 32 bytes(=16 words) at a time using the write buffer

Only the first method is currently implemented which results in pretty slow programming.

The S29GL256N is hardwired in word mode (pin 53 #BYTE connected to VCC).

That means that the command definitions for 16bit mode on page 73 of the datasheet should be used. But for some reason they don't work and instead you have to use the 8bit mode commands from page 76. Very confusing, most likely something to do with the MaxII chip inside the repro.

The Write Buffer Programming method is described on page 60. There is also a diagram on page 63.

Furthermore there is the source code download for Spansion's Low Level Driver for NOR Flash where the most interesting part is the "lld_WriteBufferProgramOp" function found in the lld_S29GLxxxN.c file that gives a C code example on how to use the write buffer.

/******************************************************************************
* 
* lld_WriteBufferProgramOp - Performs a Write Buffer Programming Operation.
*
* Function programs a write-buffer overlay of addresses to data 
* passed via <data_buf>.
* Function issues all required commands and polls for completion.
*
* There are 4 main sections to the function:
*  Set-up and write command sequence
*  Determine number of locations to program and load buffer
*  Start operation with "Program Buffer to Flash" command
*  Poll for completion
*
* REQUIREMENTS:
*  Data in the buffer MUST be properly aligned with the Flash bus width.
*  No parameter checking is performed. 
*  The <word_count> variable must be properly initialized.  
*  Valid <byte_cnt> values: 
*   min = 1 byte (only valid when used with a single x8 Flash)
*   max = write buffer size in bytes * number of devices in parallel
      (e.g. 32-byte buffer per device, 2 x16 devices = 64 bytes)
*
* RETURNS: DEVSTATUS
*/
DEVSTATUS lld_WriteBufferProgramOp
(
FLASHDATA *   base_addr,  /* device base address in system     */
ADDRESS   offset,     /* address offset from base address  */
WORDCOUNT word_count, /* number of words to program        */
FLASHDATA *data_buf   /* buffer containing data to program */
)
{ 
  DEVSTATUS status;
  FLASHDATA write_data = 0;
  FLASHDATA read_data = 0;
  ADDRESS   last_loaded_addr;
  ADDRESS   current_offset;
  ADDRESS   end_offset;
  FLASHDATA wcount;

  /* Initialize variables */
  current_offset   = offset;
  end_offset       = offset + word_count - 1;
  last_loaded_addr = offset;

  /* don't try with a count of zero */
  if (!word_count) 
  {
    return(DEV_NOT_BUSY);
  }

  /* Issue Load Write Buffer Command Sequence */
  lld_WriteToBufferCmd(base_addr, offset);

  /* Write # of locations to program */
  wcount = (FLASHDATA)word_count - 1;
  wcount *= LLD_DEV_MULTIPLIER;

  FLASH_WR(base_addr, offset, wcount);

  /* Load Data into Buffer */
  while(current_offset <= end_offset)
  {
    /* Store last loaded address & data value (for polling) */
    last_loaded_addr = current_offset;
    write_data = *data_buf;

    /* Write Data */
    FLASH_WR(base_addr, current_offset++, *data_buf++);    
  }

  /* Issue Program Buffer to Flash command */
  lld_ProgramBufferToFlashCmd(base_addr, last_loaded_addr);
  
  status = lld_Poll(base_addr, last_loaded_addr, &write_data, 
                    &read_data, LLD_P_POLL_WRT_BUF_PGM);
  return(status);
}

Aaaand done. :art:

Uploaded the changes to github. Flashing a 32MB file takes ~12 minutes.

  • it now only erases the sectors needed according to the rom size instead of the whole flashrom
  • blankcheck is skipped since up until now erasing has never failed even once so why test it everytime
  • the code now uses the 16 word write buffer to flash the rom

sanni:
Aaaand done. :art:

Uploaded the changes to github. Flashing a 32MB file takes ~12 minutes.

  • it now only erases the sectors needed according to the rom size instead of the whole flashrom
  • blankcheck is skipped since up until now erasing has never failed even once so why test it everytime
  • the code now uses the 16 word write buffer to flash the rom

Wow that was fast! Congrats on the efficiency bump!

I've tested it out on mine here and it indeed works really fast now. I figure if verification was cut out then programming time itself would only be about 9 minutes on a 32MB game. Pretty nifty!

The newer N64 repro with the Intel chip can now be re-flashed too:

I cut out the wait times, in reality it took 5 minutes to flash the rom with the 8MB file. 64MB takes around 38 minutes.
In theory it should be possible to make it faster since there are two physical chips inside the Intel flashrom that could be addressed in parallel. This would half the erase time.
The write time also would be a little bit faster, not much though since there we are limited by the speed of SD card interface.

Hi sanni,

Aren't you going to develop famicom and nes adapter for snes connector?
It would be awesome if your Nintendo dumper supports dumping all Nintendo cartridge based console games.

There is the Kazzo cart dumper that also uses an Atmega chip and is open source so that could be used as a base for adding NES support to the Arduino Cart Reader. Might be worth a look but I fear it could be very complicated.

Thanks sanni!

I'm working on building kazzo right now on universal boards.
Got all necessary parts and all I've left to do is lot of soldering...

By the way, I just ordered Nintendo dumper PCB and Adapter board to Elecrow.

I ordered adapter using old adapter.zip file instead of latest small GB adapter only version.
Is it better if I order another one using latest Gerber file?

The only difference between the two GBA adapter versions is that the new version has holes for mounting the 3d printed case with screws and it also has a longer cart edge connector. Electronically they are the same and both versions work fine. So no need to order the new version.

OK! I don't have 3D printer so it doesn't mind then.
I need to go find nice case for dumper at dollar shop though.

Here are some updated pictures of everything the cart reader can right now:


Read N64 roms out of their cartridge. Read and write save games from and to the cartridge.


Test the buttons and give the exact values of the thumbstick of a N64 controller.


Read and write Controller Paks.


Re-write those $22 chinese N64 reproduction cartridges.


Read SNES roms out of their cartridge. Read and write save games from and to the cartridge.


Write flashroms for making custom SNES reproduction carts.


Read and write Nintendo Power SF Memory cartridges.


Read Game Boy (Color) roms out of their cartridge. Read and write save games from and to the cartridge.


Write custom made Game Boy flashcartridges.


Read Game Boy Advance roms out of their cartridge. Read and write save games from and to the cartridge.

Looks so nice!
I can't wait until I make my own cart reader.

By the way, did skaman say anything about adding satellaview 8M memory pack read/write feature?
I also got some GB memory cartridge but couldn't dump Flash using insidegadget's GBcartread. I will try some commands once I get cart reader working.

I got dumping GB memory carts working but it's not very reliable yet. See latest version of the code on my github.
I'll work on it again once I find the time, was distracted by the N64 repro :grinning:
The code itself is in the NP.ino file at the end.

You can also look at bennvenn's code from here: Downloads – BennVenn's Shop -> v3.18c -> JoeyJoebags3_18.py

def main_JPN_test():
    main_JPN_F2()
    main_JPN_F4()

    dev.write(0x01,[0x0A,0x00, 0x02  ,0x01,0x20,0x01 ,0x01,0x3F,0xA5] )
    USBbuffer = dev.read(0x81,64)
    dev.write(0x01,[0x0A,0x00, 0x02  ,0x01,0x20,0x02 ,0x01,0x3F,0xA5] )
    USBbuffer = dev.read(0x81,64)

    main_JPN_F5(0xA0) # or is it F5
    
    
    main_JPN_F5(0x60)


    main_JPN_F5(0xE0)
    
    dev.write(0x01,[0x0A,0x00, 0x02  ,0x01,0x20,0x02 ,0x01,0x3F,0xA5] )
    USBbuffer = dev.read(0x81,64)

    dev.write(0x01,[0x0A,0x00, 0x02  ,0x01,0x20,0x02 ,0x01,0x3F,0xA5] )
    USBbuffer = dev.read(0x81,64)

    for k in range (16):
        dev.write(0x01,[0x0A,0x00, 0x10  ,0x55,0x55,0x55 ,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55] )
        USBbuffer = dev.read(0x81,64)
    
    
def main_JPN_Unlock_ROM():
    main_JPN_F2()
    main_JPN_F4()
    main_JPN_F1(0x5555,0xAA)
    main_JPN_F1(0x2AAA,0x55)
    main_JPN_F1(0x5555,0x60)
    main_JPN_F1(0x5555,0xAA)
    main_JPN_F1(0x2AAA,0x55)
    main_JPN_F1(0x0000,0x40)    
    
def main_JPN_Burn_ROM():
    #Enable Flash Writing/Access
    main_JPN_F2()
    main_JPN_F4()
    main_JPN_EraseFlash()       
    #Flash Cart
    main_LoadROM()
    addold=0
    for address in range (0,ROMsize,32):

        AddHi=(address>>16)&0xFF
        AddMe=(address>>8)&0xFF
        AddLo=address&0xFF
        Data32Bytes=ROMbuffer[address:address+32]
        AddHi=AddHi.to_bytes(1,'little')
        AddMe=AddMe.to_bytes(1,'little')
        AddLo=AddLo.to_bytes(1,'little')

        FlashWriteCommand=b'\x22'+AddHi+AddMe+AddLo
        Data32Bytes=ROMbuffer[address:address+32]
        USBoutputPacket = FlashWriteCommand+Data32Bytes
        dev.write(0x01,USBoutputPacket)
        USBbuffer = dev.read(0x81,64)
        trying=0
        while (main_JPN_Read(0)&0x80)!=128:
            trying=trying+1
            if trying==100:
                print ("Failed writing to sector",address)
                break
        if addold != address >> 13:
            print (address,"bytes of",ROMsize)
        addold=address >> 13
        
            
    app.lowerLeftLabel.set(str(ROMsize)+' Bytes Written' )
    messagebox.showinfo('Operation Complete','Writing Complete.')

    main_JPN_F1(0x0000,0xF0)
    #Disable Flash Access
    main_JPN_F3()

def main_JPN_EraseFlash():
    #Erase Flash Cart - 64kbyte blocks, 0x00000, 0x10000, 0x20000 etc.... MBC 16kbytes/bank - 4 banks/block.
    main_JPN_F1(0x5555,0xAA)
    main_JPN_F1(0x2AAA,0x55)
    main_JPN_F1(0x5555,0x80)
    main_JPN_F1(0x5555,0xAA)
    main_JPN_F1(0x2AAA,0x55)
    main_JPN_F1(0x5555,0x10)
    while main_JPN_Read(0)!=128:
        pass
    
def main_JPN_F1(Address,Data):
    AddHi=Address>>8
    AddLo=Address&0xFF
    dev.write(0x01,[0x0A,0x00, 0x05  ,0x01,0x20,0x0F ,0x01,0x25,AddHi ,0x01,0x26,AddLo ,0x01,0x27,Data ,0x01,0x3F,0xA5] )
    USBbuffer = dev.read(0x81,64)
def main_JPN_F2():
    dev.write(0x01,[0x0A,0x00, 0x04  ,0x01,0x20,0x09 ,0x01,0x21,0xAA ,0x01,0x22,0x55 ,0x01,0x3F,0xA5] )
    USBbuffer = dev.read(0x81,64)
def main_JPN_F3():
    dev.write(0x01,[0x0A,0x00, 0x02  ,0x01,0x20,0x08 ,0x01,0x3F,0xA5] )
    USBbuffer = dev.read(0x81,64)
def main_JPN_F4():
    dev.write(0x01,[0x0A,0x00, 0x09  ,0x01,0x20,0x0A ,0x01,0x25,0x62 ,0x01,0x26,0x04 ,0x01,0x27,0x00 ,0x01,0x3F,0xA5 ,0x01,0x20,0x01 ,0x01,0x3F,0xA5, 0x01,0x20,0x02 ,0x01,0x3F,0xA5] )
    USBbuffer = dev.read(0x81,64)
def main_JPN_F5(inst):
    dev.write(0x01,[0x0A,0x00, 0x03  ,0x01,0x20,0x10 ,0x01,0x3F,0xA5 ,0x01,0x21,0x01 ] )
    USBbuffer = dev.read(0x81,64)
    main_JPN_F1(0x5555,0xAA)
    main_JPN_F1(0x2AAA,0x55)
    main_JPN_F1(0x5555,inst)
def main_JPN_F6(data):
    dev.write(0x01,[0x0A,0x00, 0x02  ,0x01,0x20,(0xC0&data) ,0x01,0x3F,0xA5] )
    USBbuffer = dev.read(0x81,64)
def main_JPN_F7(inst):
    main_JPN_F1(0x4555,0xAA)
    main_JPN_F1(0x42AA,0x55)
    main_JPN_F1(0x4555,inst)

def main_JPN_Read(Address):
    AddHi=Address>>8
    AddLo=Address&0xFF
    dev.write(0x01,[0x10,0x00,0x00,AddHi,AddLo])
    ROMbuffer= dev.read(0x81,64)
    return (ROMbuffer[0])

def JPN_Read(Address):
    AddHi=Address>>8
    AddLo=Address&0xFF
    dev.write(0x01,[0x10,0x00,0x00,AddHi,AddLo])
    ROMbuffer= dev.read(0x81,64)
    return (ROMbuffer)

Thank you sanni!
I'll try those.

I got all my parts except for clock gen module. I made my original clock gen module and it seems working great.

Successfully dumped all my N64 games and SA1 Snes games.
I also dumped a GB Nintendo power cartridge but I'm not sure if the dump is successful because it is in bin format.
I'll take a look at structure this week to extract a game from it.

One thing I noticed was that Derby Stallion 96(One with satellaview memory pack slot on top of the cart) only dumps first 2MB and other 2MB are all 0xFF.
Maybe special command is needed to dump all 4MB of this cart.

Special LoROM (like Derby Stallion 96) needs to read Banks 0x00-0x3F for the 1st/2nd MB and then Bank 0x80-9F for the 3rd MB.

Good Luck!

tamanegi_taro:
Successfully dumped all my N64 games and SA1 Snes games.

Nice 8)

Thank you!
Dumped my Derby Stallion 96 by adding following code in line 845 of SNES.ino V25C.


// Check if LoROM or HiROM...
//Dump Derby Stallion '96 (Japan) Actual Size is 24Mb
if ((romType == LO) && (numBanks == 128) && (strcmp("CC86", checksumStr) == 0)) {
// Read Banks 0x00-0x3F for the 1st/2nd MB
for (int currBank = 0; currBank < 64; currBank++) {
// Dump the bytes to SD 512B at a time
for (long currByte = 32768; currByte < 65536; currByte += 512) {
for (int c = 0; c < 512; c++) {
sdBuffer

 = readBank_SNES(currBank, currByte + c);
        }
        myFile.write(sdBuffer, 512);
      }
    }
    //Read Bank 0x80-9F for the 3rd MB
    for (int currBank = 128; currBank < 160; currBank++) {
      // Dump the bytes to SD 512B at a time
      for (long currByte = 32768; currByte < 65536; currByte += 512) {
        for (int c = 0; c < 512; c++) {
          sdBuffer[c] = readBank_SNES(currBank, currByte + c);
        }
        myFile.write(sdBuffer, 512);
      }
    }

  }
  //Dump Low-type ROM
  else if (romType == LO) {
**************************************

ROM information of this pack is

Name: DERBYSTALLION96
Type: LoROM FastROM
Rom Size: 32MBit
Banks: 128
SRAM Size: 256Kbit
ROM Version: 1.0
Checksum: CC86

So I used bank size and checksum to recognize its derby stallion.

Thanks 8)
Added it to the github repo.