I frequently need to patch code or data in videogames. I've had to change an address because of a difference in hardware between 2 games (Defender). I've removed a copy protection check (Mayday). And I've patched out the check in Robotron for a maximum of 5 high scores. In each case, I want the ROM checksum to remain the same so that the game won't complain about a ROM error. This checksum routine is very simple- it just adds the bytes together (with carry), and discards any overflow. So the checksum itself consists of a single byte. There is a table of these bytes in the ROM that is compared to the calculated checksums when you power on or reset your game. For all the games except Defender, each 4K ROM bank is checksummed separately. (I think Defender is by 2K banks.) One method is to compute the new checksum and then patch that value into the checksum table. But then you've made another change, and now the checksum for the ROM that holds the table is incorrect. So I prefer to make changes that preserve the checksum. For instance, if there is a branch instruction that I want to remove, I can insert 2 NOP (no-operation) opcodes over it. Maybe there's a compare and a branch, and I never want the branch to be taken. The 2 NOPs mean that I'll always skip the branch. But changing 2 bytes to $12 (NOP's opcode), means I've just messed up the checksum (unless I was very lucky- there's a 1/256 chance that it stayed the same). I can add the original 2 bytes together and subtract 36 (double $12, in decimal) to find out the difference. Now I need to patch some other byte by that difference in order for the checksum to work out. But what byte? Most of the games have several ASCII text copyright messages included in them. It looks like these were just filler, and aren't used by the game. If there is a copyright message close by, I can patch 1 character of that. Or if my removal of the branch now makes some of the code above or below the branch unreachable, I can patch that code.
But here's an alternative- instead of "NOPping out" the branch, convert it to a BRN - branch never. This is the opposite of branch always. It effectively turns the branch into a NOP. What's the benefit? Well, the BRN takes an operand like any other branch opcode- the offset to the location that it will never branch to. The key is to make the sum of the BRN opcode and this offset equal to the original branch and offset. Since the BRN offset is never taken, we can make it whatever we want.
Here's an example. Say the code that you want to patch is this:
E9DB: 27 01 BEQ $E9DE
$27+$01=$28. BRN is $21. $21+$07=$28, so patching E9DB from 27 01 to 21 07 will remove the branch and keep the checksum happy.
Of course, removing branches isn't the only thing I want to do. Say I want to change a number from $14 to $04. Somewhere in that ROM I need to change another byte by +16 to compensate. Again, if there is some ASCII text close by, I can change 1 letter of it. Sometimes, there is another option- many of the hardware addresses used in the games aren't "fully decoded". For instance, Joust reads the controls by loading a byte from $C804. But checking the schematics reveals that the hardware only uses some of those 16 bits, so many different addresses would provide the same results. In this case, the hardware doesn't check bits 4, 5, 6 or 7, so $C814 is equivalent to $C804, as are 14 other addresses. So I can change a nearby LDA $C804 to LDA $C814 to fix the checksum, and the code will still read the controls as normal.
Any time you patch code you must be careful of side effects. This code was hand-tuned for maximum performance, and you don't have the source code (if you do, please write me!) That means it is often very confusing to figure out exactly what is going on, and what the ramifications of a change will be. One obvious issue is adding cycles. An example is my patch to Splat! to run on a Robotron machine. Robotron uses Special Chip 1s, and Splat! was written for Special Chip 2s. The major difference is that SC1s have a bug where you need to invert data bit 2 when setting up the width and height of the graphics to draw on the screen, but that was fixed with SC2s. So I wrote a small subroutine that XORs those bits, stores the new byte, then returns, and patched all the places in the code that wrote to those locations. The patch is short, but it is called every time an object is moved on the screen, which, of course, is all the time. Those cycles add up, and the game play suffers noticeably. There's not much to do about this one except change from a software patch to a hardware hack. Another issue is copy protection. Many of the games will check parts of the code and crash randomly if a change is found. I found this out the hard way with Robotron- I put my initials into the default high score table, and the game began to randomly crash. I thought it was a problem with my hardware and checked everything repeatedly. When I went back to the unpatched high scores, the problem went away. Changing logo graphics and copyright messages frequently triggers copy protection safeguards. Also note that MAME will detect your patches, since its checksum routines are more sophisticated.
back to Williams Info page
back to Arcade Game Info page
back to Home page