(This article is also available via the short link tomrei.ch/nes)
As a kid I dreamed of diving into NES game code, in part to make my existing games easier, maybe by giving myself infinite lives, or disabling a countdown timer. Recently I revisited this dream and discovered how approachable it is using modern tools, including those written in .NET. Given the limited hardware capabilities of the NES, it is easy for a modern software engineer with a moderate understanding of hardware concepts and basic assembly to begin tracing through game code quickly and with relative ease. Although there are countless ways to get started, I thought I'd share my findings and methods.
To get started, it helps to know a little bit about the various parts of the NES and how the tools we'll be using relate to them.
This page does a great job of running down the NES hardware in greater detail, but I'll drop a quick tl;dr version here:
Don't worry too much about the details at this stage. For the purposes of this article, understanding the CPU is based on a 6502 architecture and that most game variables are stored in WRAM are key takeaways.
Now that we know a bit about the NES hardware, we can begin to talk about the software we'll be using to do our debugging. For this I have chosen Mesen. Mesen is not only an excellent emulator for the NES, it also supports SNES, Game Boy, and PC Engine. Perhaps more importantly for our purposes, it comes with an outstanding suite of debugging tools. And of course, it doesn't hurt that it's open source and written in .NET, so you can dig even further into how it works if you like. We'll just scratch the surface of what this amazing piece of software is capable of in this article.
Next up we'll need some actual games to mess with. To do this, you'll need a way to pull the code off your cartridges. There are a few devices available that are capable of this. I recommend trying the INLretro which is capable of creating ROM files from Famicom, Sega Genesis, N64, NES, SNES, Gameboy, and GBA games. Again the software is all free and open source, so depending on how deep you want to go, you'll be able to see how it all works.
Mesen is an excellent emulator in its own right, but its debugging tools really shine. Let's briefly walk through a few of the ones we will be using in this article.
Saving the best for first, the debugger is star of the show, allowing you to see a disassembled version of the 6502 code, set breakpoints, step through, view watch variables, and perform many other actions you would expect to see in a modern debugging tool like Visual Studio.
The Memory Search window assists in finding memory and is especially helpful for locating a particular memory address containing desired data given a set of search parameters. For each location, it lists the current value, previous value, a read/write count, as well as a counter since the last write.
The Tilemap Viewer provides a live view of the in-memory background layer as it appears to the player on the screen. Mousing over gives an index to each area in memory as well as the tile currently assigned to it.
Now that we're somewhat familiar with the debugging tools, let's walk through a quick example of how we might use them. Perhaps the most requested cheat in a platformer like Super Mario Bros is for infinite lives, and, given the tools we've seen, modifying the code to do this is relatively simple. Breaking it down into high-level steps, one way to do this would be:
The first step is to find where Mario's life counter is stored in SRAM. The easiest way to do this is
probably to use the Memory Search tool. Knowing that Mario begins the game with 3 lives, it seems likely
that, at the start of the game, there is a location somewhere in SRAM that contains either a 2, 3,
or 4, depending on how the developers chose to store the value. When Mario dies, this value will be
decremented. Knowing these two things, we can narrow it down a bit. Let's start by loading a new game
and searching for the unsigned value '2' in RAM. As we walk Mario forward, a few of these values will
change, and so we can rule them out. Eventually we're left with only a few potential spots before Mario
Now let's kill Mario. Don't worry, it's for a good cause.
Interesting. When Mario died, Address 075A got decremented, and the value seems sticky. Let's kill Mario again.
Yup, it decremented after he died yet again. Looks like we have a winner. Address 075A stores the life count. If you're still not convinced, feel free to grab an extra life and see what happens.
Now that we know where Mario's lives are stored, we can easily locate the code that gets run when
he dies. Before continuing, let's start with a fresh game and a fresh set of lives. Select Game > Reset,
then start a new game. Next, open the Debugger, right-click in the Breakpoints section, and select the Add
option. A dialog appears. Select the option to add a breakpoint on System RAM write, and enter the
address. The box should look like this when you're done:
Select the OK option, and you should see the breakpoint in the list:
Now let's kill Mario again.
...and the game paused! If we mouse over the call, we can see it paused on a Decrement, which is exactly what we would expect after Mario dies. So it appears location 91D9 contains a 3-byte instruction for the decrement call.
So now we know both where Mario's lives are stored and where the code does the decrement when
Mario dies. Next let's examine what happens after the decrement. The code states:
Rolling over BPL, it appears this is a branch condition. Further, if we step through the code, we can see that, when Mario has lives remaining, the branch condition hits and the code jumps to location $91E9. If Mario is out of lives, the code continues. So, following this logic, if we skip the Decrement call and just jump directly to location $91E9, the game should continue as expected and Mario's life count should remain unchanged.
Fortunately, Mesen makes this type of modification easy. Start by right-clicking the Decrement line and selecting "Edit Selected Code". The Assembler dialog appears:
Note that the number of bytes required for this instruction is 3, as we noted before. Let's try changing the call to just jump directly to $91E9:
Luckily this instruction is also 3 bytes! Clicking Apply updates the code in the Disassembler to reflect our change:
Now let's uncheck the breakpoint, press Play, and see what happens:
We still have 3 lives! Just for good measure, kill Mario a few more times. The life count remains at 3. It appears our modification has worked!
This quick tutorial just scratches the surface on NES game hacking, but hopefully serves as a useful starting place. In the next article I will demonstrate how to apply this change we have just made to a real game on actual NES hardware using period-appropriate equipment.