Dumping SNES Zelda ROM
A couple years ago, I was sitting at a cafe inside of Google NYC with a few friends. We were talking about things we missed from the 90’s, and I expressed how much I enjoyed 16-bit gaming when I was a kid. A friend of mine happened to have two SNESs that he was not using in his garage, and said I could have one of them. A few days later I found an SNES with a Zelda game cartridge sitting on my desk.
I didn’t have a monitor, so when I got home, I tried attaching it to my roommate’s projector. The game console turned on, but did not display anything. I later found out this was likely caused by the game cartridge not being read correctly because of dust, or dirty contacts. I tried blowing on the cartridge, but that didn’t help, so it sat in my living room in Queens for about a year, and when I moved to Brooklyn it came with me and found a spot in my kitchen hidden away above the cupboard collecting even more dust.
One day, at the Recurse Center, my friend Phoebe gave a presentation on hardware hacking. She mentioned resources for hardware vulnerabilities and a search engine for security. She also mentioned Bus Pirate, and how it could be used to snoop serial communication on a circuit board. I wanted to get some interesting data off of something using it.
After becoming interested in dumping data from devices, the SNES found a possible new use. My friend Cameron found the schematics for it and mentioned it seemed to have a lot of parallel busses. Parallel interfaces convey multiple bits simultaneously and each bit has a single wire devoted to it. Serial interfaces convey a single bit at a time; the bits are conveyed as a series of pulses. Because the SNES uses parallel communication, it might not be the best candidate for Bus Pirate explorations.
Nonetheless, I wanted to learn more about the innards of the SNES, so I brought it to the Recurse Center and began to take it apart. A special screwdriver was needed to open up the game cartridge, and the console itself. I found a video online about how you could create a makeshift screwdriver by melting the tip of a plastic pen and then molding it to be the shape of the screwdriver by pressing it again the screw, but decided it would be easier to buy the screwdriver.
Again, after plugging in the SNES and turning it on, the black screen appeared. Another friend, Avery, decided to wipe off the cartridge reader with a towel. When we plugged in the power, and connected the SNES to a projector, this time it worked. A few days later, the screw driver came in the mail and I was good to continue breaking down the SNES. There was a spring holding the cartridge release button. I removed the game cartridge reader and the sound card. The cartridge reader was not soldered in, nor was it screwed in. So I took it out, and put it back in.
We began sniffing the parallel bus using Saleae logic analyzer while the game was running. We did this by hooking the probe clips directly to the Zelda ROM chip leads.
After reading up on ROM cartridges and SNES, we learned it was not necessary to have the cartridge attached to the console in order to dump the ROM. By studying the SNES cartridge ROM pinout, we learned we could power the ROM and communicate with it as though we were the SNES. If parallel communication was sent to the ROM by using the address and output enable pins, the ROM would output data via the data pins. So we hooked up an Arduino to the ROM, and wrote some C code that iterates through all possible memory locations by address, requesting the data. Then we hooked the Saleae logic analyzer to the 8 digital output pins and collected the ROM dump. We effectively used the output enable pin as a clock, so we didn’t need a separate clock signal.
White spikes represent high signal, a binary 1 value. The purple channel is the clock, used to determine when samples are considered valid.
There were some caveats, of course it was not *that* simple.
First, 21 pins are needed to request all addresses (20 address pins and 1 output enable pin), but the Arduino only has 20 pins that can be used as digital output. Cameron found an MC14015B chip sitting around, which is a serial to parallel converter, so it enabled us to use two pins on the Arduino to talk to 8 pins on the Zelda chip. We did this by writing to the data pin 8 times, and using a clock pin to shift the data. Because MC14015B has two 4 bit shift registers and we wanted to use 8 registers, we used a jumper to make it act as an 8 bit register.
Second, we didn’t have enough test clips to hook to each of the ROM pins. We needed to attach to all 32 ROM pins, and we only had 8 or so clips, so Cameron kindly soldered a bunch of wires onto the chip via the backside of the Zelda game cartridge PCB.
Third, the Saleae logic analyzer could only take in data from 8 pins at a time, but in order to take the samples at the correct time, we needed 9 pins; we needed 8 digital output pins and 1 clock pin to tell the analyzer when to consider samples valid. So we took two readings, changing the pin we were scoping, and then stitched them together with a Python script. The LED on the breadboard helped with debugging; we added some longer blinks so we would know when the data was done being collected.
After we dumped the cartridge as two `.txt` files, stitched them together, and saved the combined file as a binary, we ran strings on it and found the title of the game, in capital letters!
This is not the first time an SNES game cartridge has been dumped. In fact, it’s possible to find almost any SNES game binary online. These binaries are used to play the game on emulators. To verify we were dumping the correct data, I downloaded one of the binaries. Comparing our ROM dump to the downloaded ROM dump, we found some noise; roughly 3 percent of values did not agree. You can see an example of this by looking at image output. You’ll notice the image generated from our rom dump has an extra pixel.
To try to troubleshoot what might be causing that pixel, it’s important to understand Saleae logic analyzer settings.
One theory about why our dump was noisy was that we weren’t sampling often enough. We used Nyquist’s law to determine the noise was likely not related to our sample rate. Following Nyquist, a sufficient sample-rate is anything larger than 2B samples per second. At 1MS/s we should reliably capture signals up to 500kHz, which is a safe margin over our max bus speed of ~1/(10us) = 100kHz, where 10us is delay_time.
Another theory of why our initial ROM dump was noisy, is we were collecting data too close to the falling edge of the output enable (/OE) signal. So we thought changing the Arduino code to send an additional clock pin a pulse a microsecond after the data goes low could help eliminate noise. We did not test this theory. “Data is valid on clock falling edge” means that Saleae outputs a sample every time a clock bit (or in our case output enable) goes from high to low. We changed the analyzer setting to sample data on rising edge instead of falling edge and the noise disappeared.
To extract the sprites from the binary data, we needed to understand how sprites are stored in SNES game cartridge ROM. While portable pixel map files are stored with RGB values together for each pixel, SNES sprites are interlaced, and the colors are stored using palettes. So we set the header of the `.pbm` file to render only one color, black or white, and then set the width to 8 pixels, the width of an SNES tile. Cameron wrote a line of bash, to save the data as separate image files, because it would not open as one file.
After that, we could make out some of the sprites used in the game!
We also managed to spot the opening logo sprite used in the first seconds of the game starting, in pieces, and pasted it together.
Thank you Cameron for pairing with me on this, and for being my hacker buddy. Your Python, Bash and Linux skills are super impressive, and I’m grateful to have learned so much from you in this endeavor!
Thank you Avery for being a super awesome person, for sharing your hardware expertise and your game console wisdom!
Thank you Phoebe for your wonderful talk that inspired this exploration!
Thank you Recurse Center for providing the space and tools to make this happen!