📜 ⬆️ ⬇️

Remove images from cartridges for Dendy / Famicom / NES

It's no secret that now you can easily download the emulator of almost any 80s-90s game console and play classic games on your computer, phone, and many other platforms. In the network you can easily find the ROMs of these same games. Often people shake them and do not even think about how someone once read them from a cartridge. In this article I will try to tell you how this was done in the case of NES / Famicom, which we had more known as “Dandy”, and show you how to do it yourself.

image


')
I have to say right away that I was persuaded to be filmed on the whole multi-part show on how the game consoles work and work. Therefore, the publication today in two versions at once: in the form of a video and in the old manner as an article. Those who like it more, especially the target audience for each option is clearly different. In the article I will try to reveal more technical details when the video is more entertaining.

Video:



(Link: www.youtube.com/watch?v=gPSpk2gbAD4 )

Article:


So, how does the Famicom cartridge work? Many will immediately say that this is just a ROM-memory with parallel access, and there should be nothing difficult in its reading, but this is not quite so. First, there are two types of memory in the cartridge: with the code of the game, and with images from the game. Each of them is included directly on the console data bus. The first is in parallel with the RAM and the processor (CPU), and the second in parallel with the video memory and video chip (PPU). Thus, the cartridge is something like RAM, where the game is already loaded.

Consider the same pinout slot cartridge, and how it works.

image
View of the console above. Left - the front part.

→ CPU A0-A14 - contacts through which the address is set to read the CPU memory
CPU D0-D7 - contacts through which we transfer data to the CPU memory
→ PPU A0-A13 - contacts through which the address is set to read the PPU memory
PPU D0-D7 - contacts through which we transmit PPU memory data
→ M2 - local clock signal, takes a high level when accessing the CPU memory.
→ / ROMSEL - logical NAND between M2 and CPU A15, which is not directly accessible
→ CPU R / W - determines the type of operation: high - read, low - write
← / IRQ - allows the cartridge to generate an interrupt, inside the console will be pulled to + 5V
→ PPU / RD - takes a low level when the console reads PPU memory
→ PPU / WR - takes a low level when the console writes to PPU memory
→ PPU / A13 - simply the inverted signal from PPU A13
← CIRAM A10 - allows the cartridge to determine the principle of mirroring video memory in the console
← CIRAM / CE - at low level turns on the video memory inside the console
→ Sound (input) - here comes the sound from the audio chip
← Sound (input) - here comes a sound from the cartridge in the form in which we already hear it
* Earth and power - no comment, voltage 5 volts

Now more, some technical information.

The console's CPU memory lies in the range between 0 and $ FFFF (16 bits of addressing). The cartridge usually includes addresses $ 8000- $ FFFF . Please note that in this case we do not have a CPU A15 contact, which must respond to the most significant bit of the address. Instead, there is / ROMSEL , which accepts a low level only in the case when M2 and theoretical CPU A15 simultaneously accept a high level. Those. when the console reads or writes to addresses $ 8000- $ FFFF . Therefore, it can usually be directly connected to the / CE foot of the ROM memory. Reading or writing are selected through the CPU R / W. Why do I need to write to the cartridge? Yes, a lot of why, but more on that below.

PPU memory has addresses from 0 to $ 3FFF (14 bits of addressing), while the cartridge usually refers to 0- $ 1FFF . It is in this range that images are stored, and it can be both ROM and RAM, but the cartridge itself determines which addresses belong to it and which to the inside of the console, this is what CIRAM / CE is used for . Usually (almost always) it is closed directly to PPU / A13 , i.e. console memory is activated when A13 is equal to one - in the range from $ 2000 to $ 3FFF . Please note that inside Famicom and NES memory below $ 2000 and not at all, it must be in the cartridge. PPU uses separate pins for reading and writing: PPU / RD and PPU / WR . Separately, it should be said about the CIRAM A10 - this contact determines how the memory is mirrored in the range between $ 2000 and $ 2FFF inside the console. This is usually important to determine depending on how the game moves vertically or horizontally. In older games, this was hard set with a jumper on the board, in newer ones it can usually be changed programmatically during the game.

Yes, in the original Famikome there were also audio input and audio output, which allowed the cartridge to be an additional source of sound. It was rarely used, but it made music in games much nicer due to additional sound synthesizers. The NES did not have these contacts. In modern Chinese "Dandy" and other clones, they also do not solder. Of course, the sound chip from the cartridge can not be dumped.

At NES, the principle of operation does not differ, although there are already 72 contacts in the cartridges: several go directly to the socket at the bottom of the console (never used in any game), plus four go to the chip for protection against piracy.

Let's go to practice.

So, it seems there is nothing particularly complicated. You just need to somehow read all the data for all the addresses and save them in the NES file. For this, I decided to take two ATMEGA64 microcontrollers. Yes, it is very redundant, but I just need a huge number of legs - the cartridge still has 60 of them. Although the CPU and PPU memory do not need to be read simultaneously, and they could be connected to the same legs, but for the first experiment I decided to isolate them. Moreover, it is much easier to make a fee, I did not want to do two-sided.

image

You can buy a slot for cartridges, this is a standard edge connector for 60 legs, but for some reason it was everywhere only under the order, so I just dropped it from the cheap new-made dandy.

After assembling and printing the case, the device turned out like this:

image

I will not go into the details of the firmware, the principles of working with memory have already been outlined above, and the sources will be at the end of the article.

Is everything so simple? Alas, not really. The life span of NES and Famicom was quite long, and game developers very quickly (already in the 85th year) were faced with the fact that with this approach very little information could be crammed into the cartridge. And not at all because of its small size, but because the address space for the code was limited to these same $ 8000- $ FFFF, and this is only 32 kilobytes. Only the simplest games like Battle City, Ice Climber, Duck Hunt, Tetris, Lode Runner fit into this size. Simply put, all that we used to see on collections of the type "9999999 in 1" with repetitive games.

So mappers started to put in cartridges.

image

These are the microcircuits that are responsible for switching memory banks, as a result of which it became possible to significantly expand the address space. Imagine that the code for the first level of the game is stored at some address. You pass it, the mapper switches the memory bank, and as a result, the code of not the first level, but of the second level, is read at the very same address. Similarly with video memory.

It turns out that in order to dump a cartridge, you need to know in advance what the mapper is in it, and what commands you need to send it to switch memory banks. And all this would still be easy if all the cartridges had the same mapper, or if there were only a few of them. But there are hundreds of different mappers and ways to connect them. Sometimes they got along with a simple logic circuit, and sometimes they put on very sophisticated chips with a bunch of registers and additional functions. It was not uncommon that they took some popular mapper, but connected it in an unusual way, which radically changed the principles of interaction with it.

The pioneers had to dump the first memory bank, disassemble it and engage in reverse engineering in order to understand how to access the rest of the data. At the same time, the generally accepted number of the mapper is indicated in the header of the NES-file, and a full-fledged emulator must emulate not only the console itself, but also the entire zoo of iron that was put into cartridges. It turns out that theoretically there may be a cartridge that is not only difficult to dump, but which will not be emulated by any existing emulator. You don’t need to go far: inside the popular pirated multi-game cartridges that are just not worth it. And the Chinese are still releasing new games on their own hardware, which has become even harder to figure out.

By the way, in the cartridges that just was not. In addition to ROM-memory and mappers, additional RAM was installed there (sometimes with a battery to be able to remain in the game), all sorts of time counters, sound synthesizers described above and much more up to the modem. Alas, in our country in the ninetieth license cartridges it was not possible to find fire during the day, and the pirates did not bother much, and games with such bells and whistles were not sold here.

I decided to implement at least reading games on the most popular mappers. I write the client part to the dumper in C #, so I just described the IMapper interface and the class that corresponds to each mapper:

image

Each implemented methods for dumping data. Here is how the MMC3 mapper program memory reading method looks:
public void DumpPrg(FamicomDumperConnection dumper, List<byte> data, int size) { dumper.WritePrg(0xA001, 0); byte banks = (byte)(size / 0x2000); for (byte bank = 0; bank < banks-2; bank += 2) { Console.Write("Reading PRG banks #{0} and #{1}... ", bank, bank+1); dumper.WritePrg(0x8000, 6); dumper.WritePrg(0x8001, bank); dumper.WritePrg(0x8000, 7); dumper.WritePrg(0x8001, (byte)(bank | 1)); data.AddRange(dumper.ReadPrg(0x8000, 0x4000)); Console.WriteLine("OK"); } Console.Write("Reading PRG banks #{0} and #{1}... ", banks-2, banks-1); data.AddRange(dumper.ReadPrg(0xC000, 0x4000)); Console.WriteLine("OK"); } 

If anyone is interested, the description of this mapper can be read here: wiki.nesdev.com/w/index.php/MMC3

I decided to try to be at the place of the pioneers and dump the cartridge with such an unusual menu:



To do this, I first read the cartridge as if there was no mapper, I ran it on the emulator and started disassembling it. Soon I found the instructions I needed:



After that, I read the cartridge again, having previously made an entry at $ B600, and received a fully functional ROM . Of course, the games in it do not start, because for this you need to switch the memory banks again. And even if I follow what happens at the moment when the game is selected in the menu, and read the entire cartridge, the emulator most likely will not be able to start all this.

I also got a license cartridge from one of the most iconic games of those times - “The Legend of Zelda”. It works without problems with both the dumper and Famikom through a simple passive adapter. It makes no sense to dump this game, she interested me in others. This cartridge is worth additional RAM memory and battery, which allows you to remain in the game. This memory is in the range of $ 6000- $ 7FFF. I tried to read it and feed it to the emulator. He understood her without any problems. After that, for the sake of experiment, I decided to increase the number of hearts in it and write it back into the cartridge. It worked.



It turned out a fun opportunity to transfer save between the emulator and the real console.

Many will probably be asked why I even took this up when almost any ROM can be found on the net. Yes, out of curiosity and self-education. It was interesting to see what was going on inside these cartridges, and how it all works. In addition, they can both read and write cartridges. But more about that next time.

Links to sources:
github.com/ClusterM/famicom-dumper - the dumper itself (C source, board layout, 3D case models)
github.com/ClusterM/famicom-dumper-client - client in C #

Source: https://habr.com/ru/post/248459/


All Articles