News:

11 March 2016 - Forum Rules

Main Menu

NES ROM Expansion Document

Started by Pennywise, December 21, 2014, 02:25:17 PM

Previous topic - Next topic

Pennywise

I had the idea to write up a document that attempts to explain and break down all the details that go into NES ROM expansion. Could I get some feedback on what I've written?

QuoteThe MadHacker once said that "NES rom expansion is so difficult and time consuming that you might as well think of it as impossible."

That was in a time when information was scarce and emulators weren't really any good. The fact is that NES Expansion isn't that complicated despite the myriad of different mappers that do things their own way. The goal of this document is to shed light on the technical details of ROM expansion and to share some common techniques that can be used to utilize the new space.

NES Hardware Basics

PRG-ROM and CHR-ROM

Most NES games have two ROMs inside a cartridge. The first ROM is PRG-ROM and the second is CHR-ROM. They stand for Program and Character respectively with the first usually containing all the code and data and the second containing the graphics. When we say ROM expansion, we are usually referring to the PRG-ROM. The PRG-ROM is split up into different sections of equal size in what are known as ROM banks. The bank size varies depending on the game and mapper. For example, the bank size is usually either 8kb or 16kb. You can find this information out by using the emulator FCEUX. Just go to Help -> Message Log.

The 6502 CPU Memory Map

The NES accesses the PRG-ROM by loading ROM banks into a specific address area of the CPU memory map. The banks are loaded into the address range of $8000-FFFF. This makes for a total size of 32kb the NES can access of the PRG-ROM at a time.

It's story time. Once upon a time when the NES first came out, PRG-ROM sizes were only 32kb, the maximum size the NES could access at a time. This worked fine until developers said, "32kb is way too small for me to fit my game into. I want to make bigger and more complex games. I need a bigger ROM." So Nintendo developed mappers, which is what you could call fancy boards, that usually handled ROM sizes of 128kb, 256kb and 512kb. Now the NES can only handle 32kb at a time, so these mappers could "swap" banks in and out as needed, thus being able to access the entire ROM, just not at once. Take this diagram to better picture it.


Game that uses 8kb banks
+---------------------------------------------------------------+
|      1       |        2       |       3      |       <4>      |
+-------------------------------+-------------------------------+

Game that uses 16kb banks
+---------------------------------------------------------------+
|             1                 |             <2>               |
+-------------------------------+-------------------------------+

Picture that the NES CPU has slots and pretend that banks from the ROM go in there. Then mix and match however you want and that's how the NES accesses the PRG-ROM in a nutshell. But there's one very important piece of information you need to know. The very last bank in the PRG-ROM is what you would call the "fixed bank" or "hard-wired bank". It's always loaded into the the last ROM bank slot in the CPU and can never be changed or moved. The <> is there just to denote the difference.

How to determine NES ROM expansion limits

Technically speaking, the NES could supports ROMs that were 10MB in size, but there isn't a board or mapper that hasn't been designed to support such a size yet. When we talk about ROM size limits, we are referring to the maximum size a game can be that was designed for a particular board or mapper. Maximum ROM sizes are determined from the number of PRG bits a board/mapper that sets the number of ROM banks available. This is all technical mumbo jumbo that's not really necessary to explain because NESDev has an excellent wiki that just lists the max ROM size for all the NES mappers.

Question: Let's say I have a ROM that I expanded to 256kb, but the mapper/board can only support 128kb. Is that even possible?

Answer: There are two sides to the coin for this. If you wanted to play your translation on a real NES, the original board used wouldn't be able to support the ROM. However, it becomes a moot point when you factor in that flashcarts could play the expanded ROM fine. You can consider a flashcart a sort of all-in-one mapper that can handle any game size which usually maxes out at 512kb. The NES doesn't care about ROM sizes and that's the important thing to remember. A good example is the Hebreke translation that expanded the ROM which the original mapper/board couldn't support, but that a flashcart could play fine.

How does the NES "swap" banks

First off, ROM banks are numbered starting from #$00 and going up the hex scale. You take the bank number and you write them to the PRG register which is something that basically tells the NES to do the bank swap. This is what the code looks like to swap banks.

LDA #$01
STA $8000

Translating this code into English would be something like load the bank #$01 and write it to the PRG register. Think of it like if I write #$01 o $8000 then bank 1 will go into the first PRG slot in the CPU.

That is generally how the NES swaps banks, but the register value is just a made up example and will vary by game and mapper. The mapper MMC1 does its bank swaps a little different and is not the same as the above example.

How to expand an NES ROM

Well, that's actually quite simple. KingMike made a simple program called nflate that can expand your ROM for you in a jiff. Every once in a while there will be special cases where a ROM won't work properly after expansion and you'd need to troubleshoot it and expand the ROM manually. But the vast majority of games work fine after expansion. Expanding the ROM is the easy part, but utilizing the expanded space is where things get tricky.

How to use the expanded space

In the scope of translation hacking, the goal of ROM expansion is to increase the amount of space you have available so you can fit all the translated text back in without cutting it down. Let's say you have an entire bank of text. There's no way in hell you can fit that all back in the original space. Maybe if you compressed it, but that's a different subject. So what you do is split the text between two different ROM banks effectively doubling the space you have available.

Question: How does one split the text between banks?

Answer: I use two different methods to accomplish this. To keep things simple, we'll only talk about one.

First off, you need to code a new routine that contains our bank swap code. The new code requires space in the ROM and most games usually have free space at the end of the fixed bank. That's where we put our code.

Question: But what if there's no free space in the fixed bank?

Answer: TBD

So what you do is JSR from the game's text pointer load routine. Your common game's text pointer load routine will look something like this:

LDA $8000,Y
STA $0000
LDA $8001,Y
STA $0001

Y is the index that selects which pointer from the table to load. Here's a simple breakdown of the pointer table addresses:

$8000 = 0
$8002 = 1
$8004 = 2
$8006 = 3
$8008 = 4
$800A = 5

The numbers on the right are what Y needs to be in order to load a particular text pointer.

Let's say we want to we want the text to be in another bank for string 5. Let's also assume that the PRG-ROM was 128kb and we expanded it to 256kb. The game uses 16kb banks, which with 128kb total, that makes for 8 banks. So the banks for the expanded space are going to start at #$09 and up. So here's what we do


;Load first pointer byte
LDA $8000,Y
;Write it to RAM
STA $0000
@Branch1:
LDA $8001,Y
STA $0001
;Compare if Y is less than or equal to 4
CPY #$05
;If so, skip this code and go here
BCS @Branch1
;load bank 9
LDA #$09
;swap bank 9 into PRG slot
STA $8000
@Branch1:
RTS

Bregalad

It's pretty good, and was needed. It is about time someone ends the "NES ROMs can't be expanded" fallacy.

I think you should add the following :
1) In some cases, it's really impossible to exand the ROM (for example MMC1 can't support more than 512kb PRG-ROM, or 256kb PRG if CHR-ROM is used).
2) You say most of the time CHR-ROM is used, but you could also mention than in ~40% of the time games use CHR-RAM instead, and thus it's normal no CHR-ROM data is stored in the iNES dump. (just so that they don't complain "hey, where is the CHR-ROM ?")
3) You should definitely mention that the size of the PRG-ROM *must* be a power of two, so if you expand it, you must double it's size. I think many people will try to be smart and expand just one bank or something.

On an unrelated note, the power of data compression should not be forgotten. I should update my CompressTools so they don't require Java anymore, and pehaps add better doccumentation to them, so people will be more likely to use them.

Dr. Floppy

Quote from: Bregalad on December 22, 2014, 05:24:14 AM
It's pretty good, and was needed. It is about time someone ends the "NES ROMs can't be expanded" fallacy.

^^^ THIS!!!!!!! ^^^

Srsly, expanding ROM's is mind-meltingly easy once you get the hang of it!  :woot!:

henke37

You really should discuss the troubles of when games already do their own bank switching. Having the wrong bank switched in can be disastrous.

STARWIN

From the last example, are you going to swap the old bank back if you happen to switch to bank 09?

Also, even if such a swap can be done transparently from the main bank switch routine, is there a risk of things breaking at NMI code? I mean, I could imagine that the main bank switch might do some bookkeeping for the NMI (instead of the NMI being safe and doing bookkeeping itself).

KingMike

You probably have to do something to make sure the NMI doesn't interrupt the bankswitch (I recall that was just one of the problems stopping Gideon's Megami Tensei translation).
In my Momotarou Densetsu translation, I had to wait for NMI to clear before trying to access the translation (as I had used 512K MMC1). I recall having to replace the original text seek function with pointers for every line because in one case the text-seek was STILL too long (the first enemy in the game, was stored as the final string in a block, right after the credits, which was one HUGE string).

I thought MMC1 could support 512KB PRG with CHR-ROM, but at the expense of halving the CHR limit (which would make it unusable for ROM hacking). Luckily the games I've done that on have used CHR-RAM.

Deep Dungeon III put the "system" text in the fixed bank. Didn't have free space otherwise, so I had to relocate the text to a non-fixed bank (which was not an easy task at all) but on the plus side that gave me tons of free space in the fixed bank. When doing 256->512KB MMC1 expansion, that introduces another problem: having two "fixed" banks. Code changes in the bank must be kept in sync or the game will break.
"My watch says 30 chickens" Google, 2018

STARWIN

Do games never use the first bank as fixed?

Would it be possible to use an existing bank switch routine from the game and just supply it with the new bank numbers, instead of writing your own?

Quote from: KingMike on December 22, 2014, 09:50:28 AM
When doing 256->512KB MMC1 expansion, that introduces another problem: having two "fixed" banks. Code changes in the bank must be kept in sync or the game will break.

I can understand that the new fixed bank has to contain what the old fixed bank did, but in which cases are there then references to the old fixed bank that would break? Do games load the fixed bank to the variable slots or something else?

aishsha

Quote from: STARWIN on December 22, 2014, 11:55:24 AM
Do games never use the first bank as fixed?

Would it be possible to use an existing bank switch routine from the game and just supply it with the new bank numbers, instead of writing your own?

I can understand that the new fixed bank has to contain what the old fixed bank did, but in which cases are there then references to the old fixed bank that would break? Do games load the fixed bank to the variable slots or something else?
It's always mapper-dependent. Or rather case-dependent. I've had both cases in my practice. You'll have to debug to see it every time.

Pennywise

Thanks for the feedback. I was actually going to use one of the various games I've expanded the ROM for as an example. There's at least one example that shows just how easy it to do ROM expansion and make use of the expanded space.

snarfblam

Quote from: Pennywise on December 22, 2014, 03:09:48 PM
I was actually going to use one of the various games I've expanded the ROM for as an example.
Excellent idea. I was going to recommend providing a concrete example.

Quote from: henke37 on December 22, 2014, 07:11:52 AM
You really should discuss the troubles of when games already do their own bank switching. Having the wrong bank switched in can be disastrous.
Quote from: KingMike on December 22, 2014, 09:50:28 AM
You probably have to do something to make sure the NMI doesn't interrupt the bankswitch

Yes, this is important. When expanding a game, you may need to modify it's bank swapping paradigm, which introduces opportunities for problems. When I expanded Metroid, I knew I had to protect the NMI from having the wrong bank loaded, but it took me a while to realize the very rare random resets I was getting were from NMI corrupting main thread's MMC1 register writes. You may want to provide a brief explanation of what is necessary to safe-guard against this with common mappers.


KingMike

Quote from: STARWIN on December 22, 2014, 11:55:24 AM
Do games never use the first bank as fixed?

Would it be possible to use an existing bank switch routine from the game and just supply it with the new bank numbers, instead of writing your own?

I can understand that the new fixed bank has to contain what the old fixed bank did, but in which cases are there then references to the old fixed bank that would break? Do games load the fixed bank to the variable slots or something else?

The reason the last PRG-ROM bank in an NES ROM is usually fixed is because of vectors (NMI, Reset, IRQ). The CPU expects them at $FFFA-FFFF, which is mapped to ROM. By doing that, they only need to have those routines coded once in the game. Otherwise it would have to be coded in each bank.
The 512k variant of MMC1 is a special case. It basically acts as 2 256K ROMs (the limit of a standard MMC1 board) glued together, with one of the mapper bits (normally used for CHR-ROM) instead functioning as a switch between them. (such as I understand, FF1+2 effectively was 2 256KB MMC1 games stitched together, with some code to switch between them, though that was a further variant since it needed to glue the SRAMs together too)
"My watch says 30 chickens" Google, 2018

Bregalad

QuoteI thought MMC1 could support 512KB PRG with CHR-ROM, but at the expense of halving the CHR limit (which would make it unusable for ROM hacking). Luckily the games I've done that on have used CHR-RAM.
Technically possible, yet. But I don't think any emu supports that. Normally when the ROM is expanded to 512kb, the top CHR bit is used for 256kb page selector and the bottom CHR bits for SRAM banking.

frsj8112

Nice write up, would it be possible with some examples? :-)

Dr. Floppy

Might want to add a brief paragraph distinguishing ROM Expansion from Mapper Conversion, perhaps as an addition to the "ROM = 256kb; Mapper max = 128kb" Q & A.

Pennywise

Well, I'm gonna have to revisit this sometime in the future and rewrite it and provide some examples. I'll probably do an MMC1 SUROM example and a few other examples from a few different mappers that also highlights the similarities.

Dr. Floppy

You can reference my tutorial on expanding Zelda II if you like:
http://www.romhacking.net/documents/659/

Trax

I think the technicality of expanding a ROM is not fringe knowledge anymore. However, I think the hardest part is about how to adapt the game itself to the new potential content. If your game has saving ability, then the saved data must reflect the new levels, items, etc., that you might want to expand. And Zelda II is a good example of that...

I studied Zelda II quite a lot. Many types of things need to be remembered between resets. One example that comes to mind is the state of collectable objects. Every area uses 4 bits to store the presence (or absence) of a potential object in each of the 4 screens of that area. This data is stored in one of the 3 saved game slots. If you expand your world, you need to keep that new information somewhere, and everything that is related to keeping track of this information...