AKA The Process of Making a ROMhack (it’s not that hard)

(In my last post, I talked about how a lot of NES games suffered from having bad control schemes. In this post, I’ll explain the technical details of hacking a game to improve its control scheme.)

A few days ago somebody told me it would be too hard to hack the Castlevania 3 ROM in order to improve its control scheme and let Trevor turn around in mid-air – a feature that, in my opinion, is sorely lacking in the original game. So to prove them wrong I learned NES assembly and downloaded FCEUX, a tool for playing, debugging, and editing NES ROMs, and I got to work. As I learned, hacking uncommented assembly code turns out not to be nearly so hard as I thought it would be. (At least, it wasn’t that hard for this NES game.)

FCEUX makes it easy to search through memory for useful values, and it provides a fairly workable debugger that let me step through the assembly code and try to piece together how the game worked. It would have been a lot easier to do this for a documented ROM, but unfortunately, nobody has done much work on documenting the Castlevania 3 ROM. I did manage to find this list of ROM and RAM offsets that one generous person made; it is quite extensive but unfortunately didn’t help all that much for my purposes.

As my goal was to make it possible for the player to change direction in mid-air, the first step was to figure out which of the 2048 bytes of read-write memory related to the direction that the player moved in. FCEUX provides a utility specifically for this; it lets you filter out memory values based on whether they change or stay the same from frame to frame. I knew the direction variable should stay constant most of the time and only change when Trevor turns around. Here’s a GIF of me filtering RAM values:

Filtering Memory

We arrive at three different memory addresses: $00A2, $04A8, and $04F2. Figuring out which of these is Trevor’s direction was fairly simple; FCEUX provides a hex editor to edit the RAM values while the game is running. I paused mid-jump and edited each of these values:

00A2

00A2 (above): No discernable effect

04A8

04A8 (above): Trevor faces the opposite direction, but continues with the same velocity

04A8

04F2: Trevor faces the same direction but continues with opposite velocity.

From this, it’s not clear what $00A2 relates to, but it’s clear that $04A8 is the direction that Trevor is facing (and is 0 for right and 1 for left) whereas $04F2 is the direction that Trevor is moving (and is 0 for stalled, 1 for right, and FF (i.e. -1) for right).

At this point, I knew what variables needed to be edited, but what still remained was editing the code for this. So I placed a memory watch point to see what code caused the variables $04A8 and $04F2 to be changed (which only ocurred while Trevor is on the ground, of course). Looking around this code, and manually forcing it to skip various lines, it wasn’t hard to figure out what lines of assembly did what. I could even deduce that the memory storing what controller buttons were held down was in $002A (Which is not $00A2 mentioned above, just to be clear; I never did figure out what $00A2 was for).

Letting the code run until it hit a return statement (aka “stepping out”) revealed what function called the function handling movement on the ground (which I dubbed ground-movement). There was some hooliganism going on there, because the function that was called didn’t seem to be the function that was running; after some investigating, it turned out that the ground-movement code was actually reached via an indirect jump from another function, which I later named Trevor-dispatch; this was a very helpful observation because putting a breakpoint in Trevor-dispatch while Trevor was jumping led me to realize that the aforementioned indirect jump was on a jump-table listing all the functions that handled every different type of thing that Trevor could do (walk, stand, climb stairs, fall, attack, crouch, etc.).

Unfortunately, the code was packed too densely to edit the jump code in-place in order to add the check for the keyboard to turn around; I’d have to evict something else useful in the jump code, and that just wouldn’t do. We wouldn’t want to remove the code that let Trevor land, would we?

At this point I was worried, but I scrolled around randomly through the code until I found a suspicious number of invalid opcodes in a row starting at address $BFE3. These were all set to the value FF, so I imagine that it was some extra space left over (surprising, given how little room there was on the ROM. The whole thing was 385 kb, mostly image and sound data.) Here were 398 bytes in a row of unused ROM.

398 FFs, or 796 Fs

Great! I could write my code here and just modify the jump table to point to my malicious improved code instead. Then I just had to learn ASM for the 6502 processor. This was actually very easy; thanks to my cs class I already knew how to write assembly and the 6502 opcodes can all be found online.

I read in $002A with a LDA command to detect controller input, then bit-shifted with LSR and branched with BCC to code that sets $04A8 (facing) and $04F2 (horizontal velocity) with the STX command. Finally, after setting these things, I made the code jump (JMP) to the original jump code (literally Trevor jumping, not ASM jumping) in order to carry out Trevor’s jump correctly (landing is important!). This actually just… worked. Surprisingly. Against all odds. No glitches or nothing so far as I can tell.

Jumping and changing direction in mid-air

Whee! Jumping is fun again.

Then I went on and fixed a bunch of other things too like letting you stop your ascent mid-jump and letting you regain control quickly after being knocked back. Adjusting the attack code to let you change direction in mid-air had to be done separately (I also made it only modify velocity, not facing), and I did some other minor things like letting you change direction while crouching and letting you jump off stairs. Overall it works very well and I finally am having fun while playing Castlevania 3 for the first time ever. Of course, a lot of people say that the fun and challenge of the game derives from the stiff controls, but I at least found them too frustrating.

There are still some improvements left to be made, in my opinion; stairs are still very clunky (holding up instead of sideways to climb them? Seriously?) and lives probably shouldn’t drain below the starting number of 2 if one isn’t at a midway checkpoint. I will leave improvements these for another day, or another person. But honestly the game is already a lot less frustrating.

Altogether, it turned out to be quite easy. It took me less than 24 hours to complete the first hack (direction control in mid-air), and then only a few hours after that to do the rest of the changes. I highly recommend trying out ROMhacking if this sounds fun to you – it really isn’t very hard at all. It just requires some patience when debugging other people’s uncommented assembly code.

You can download my ROMhack from ROMhacking.net. You can apply the patch to a ROM if you have one using Lunar IPS and with the help of Google you can easily attain a ROM of Castlevania 3 or Akumajou Densetsu as it’s called in Japan. (The Japanese version has better music and you can translate it with another hack; I definitely recommend the Japanese version). Have fun!