* Note * There is now an updated version of the slides available! Look for https://www.slideshare.net/StephenWylie3/process-of-arcade-rom-hacking-4232020-edition for much better descriptions of tools including how to poke around MAME source code for valuable information, how to reason through tons of other things that might be going on in your ROMs, and even a brief blurb on fixing crashes on Sega Genesis games. Hit the link above for better slides!
But if you wish to view these older slides...
Get familiarized with the tools and mindset required in order to add your own custom graphics to video arcade games and home console games. This slide deck describes exactly how to modify the graphics in Midway's "Tapper" game from 1983, running on the MCR-III hardware platform, so you can substitute the prominent branding throughout the game with something else.
Through a hex editor, you will see how game artwork tiles are laid out in the game's ROM, plus how color information in each tile is encoded. Plus, learn enough about the MAME debugger to step through the code and modify the checksum when you make significant changes to the ROMs, and discover quick tips in the GIMP photo manipulation tool that will help you scale down your art to fit beautifully into the game's tiny resolution and minimal color palette.
The concepts presented here apply (in varying degrees) to modifying games for other arcade and home console systems, particularly from the 1980s and 90s.
4. Tools
Everyone should have these
handy!
MAME emulator
Game ROMs
Editors (Hexplorer, SciTE Hex)
GIMP for new graphics creation
Pre-built tools for the game’s
platform
If not, I hope you can write code!
5. MAME Emulator
Download and unzip MAME
Put your ROM in the roms folder in its
own directory
MAME/roms/tapper/*.bin
Launch MAME
mame64.exe -window -verbose -
debug -nomax tapper
Press F5 to run in Debug mode
Press F4 for Tile Viewer
- Press Enter for more information
6. Around MAME and the ROMs
The Tile Viewer shows the available color
palettes, tile artwork, and tile map.
Examine the two background files; look
for patterns. What matches the Tile
Viewer? Discover the encoding and palette
scheme.
Practice making edits. See if the results
are what you expected. Refine and tweak
if it did not meet your expectations.
Organization of ROM Files
tapbg0
Background tile palette
tapbg1
Background tile artwork
tapfg[0, 7]
Foreground sprites
tappg[0, 3]
Game logic, level tile arrangements
tapsnda[7, 9], tapsda10
Getting Your Bearings
8. What we learned
tapbg0 (right) encodes the group of 4
colors (“minor palette”) for each pixel
tapbg1 (left) encodes the color within the
minor palette for each pixel
These are two-bit encodings per pixel
Each tile is 64 pixels
Each tile offset is 0x10
10. Where are the tiles?
1. Carefully study tiles in Tile Viewer
2. Match these to locations on-screen
3. Draw yourself a grid
4. Scroll through program ROM until
you find one or more tile addresses
11. Where are the tiles?
1. Carefully study tiles in Tile Viewer
2. Match these to locations on-screen
3. Draw yourself a grid
4. Scroll through program ROM until
you find one or more tile addresses
5. Sometimes you have to eliminate the
distractions, such as row wrapping
6. And the Tile Viewer might not be on
the correct major palette setting
12. Zilog Z80 machine language features opcodes where any byte
value from 0 to 255 seems equally likely. However, background
tile regions exhibit a pattern. Can you spot where the tiles begin?
14. Substantial
ROM Edits
For the checksum to make,
thou must giveth and take
● ROM checksum is calculated by
a loop in tappg3
● Checksum covers game program
ROMs, “D6”, and “D7”
● Two strategies to address this:
○ Balance out your changes
○ NOP the checksum routine
15. Just alter it:
Must calculate after each code change
Changing the programmed checksum
value won’t help
Use debugger to find the difference in
expected vs. actual
Pick a byte at the end to subtract this
offset from
What if the byte you picked gets used for
something?
Remove the checksum code:
Simple to overwrite the conditional
branch instruction with NOPs so the
processor ignores it
Shouldn’t do any harm on modern
computers and emulators
Maybe you would want it back before
writing to real EPROMs
Considering the Checksum
16. MAME Debugger
Use -debug at the command line
Set breakpoint to pause upon reaching a
piece of code:
bpset 0xBEEF
Set watchpoint to see when one byte of
memory is:
Read: wpset 0xBEEF,1,r
Written: wpset 0xBEEF,1,w
Either: wpset 0xBEEF,1,rw
18. Tile Codes At a Glance
Range: [0, 0x3FF]
Little endian
0x6F11 = Tile 16F, Palette 1
Aliased; higher-order bits affect various
settings as described at right
Specifics
Bits 15:14 - No apparent change
Bits 13:12 - Major palette
4 total groups of 16 colors
Bit 11 - Flip horizontal
Bit 10 - Flip vertical
Bits 9:0 - Tile address
Background Tile Codes
20. Tile Tweaking Tools
Plenty of tools at
http://www.romhacking.net
Vast majority geared toward home
consoles from VCS to PlayStation
Can’t find one? Write it and submit it!
21. Walkthrough
My Python Program For Automating Tile Art (MPPFATA: nice ring to it, eh?)
12.04.2016 (5844 2F70)SDthgkkjkj
23. Starting With the Logo
Use GIMP from
https://www.gimp.org/
GIMP is the ONE TOOL that we
need to make our Pixel Art.
Load the graphic to convert
Enable transparency; make the
background color transparent
Save as a PNG for now
24. Remove the Excess
Find the largest dimension of your image, then
find out the maximum size the image can
be along that dimension in the game.
To convert Tapper to Community, we must
constrain the width to 96 pixels, so we
need to remove extraneous details.
Steps:
Use the Magic tool; click
and delete the border
Use the Eraser to
clean up these lines
by going over all
removed material
Image > Autocrop Ink Well > Get the Blue
Remove
“Beer” with
the Paint Tool
25. Resize by Scale
Image > Resize
> Interpolator “cubic”
Select width of 96 pixels
Then,
Image > Mode > Indexed
For amount of colors, choose 4
(we will adjust the rest of
this manually)
28. Conclusion
With patience, intuition, the right set of tools, a good understanding of said tools,
and a versatile understanding of programming languages from assembly to scripting
languages, you too can make a ROM hack all by yourself.
But if superior tools already exist and you don’t have to worry about the checksum
or can handle it without having to know assembly, then writing code isn’t required.
Hexplorer for Windows is nice for reasons you will see later. (Hint: If you don’t get it, it’s that the Matrix-style color scheme lends well to pattern recognition.)
3. Why use the command line? If there’s an invalid checksum with your ROM compared to what MAME thinks is right, this will let you get around that. When you load the ROM through the menu with plain Jane MAME, it will stop on its own ROM checksum mismatch, considering it a fatal error, and won’t let you run your creation.
-window makes MAME windowed rather than full-screen. Otherwise you could not navigate to other applications without exiting the game. However, the window is still usually very large.
-verbose spews lots of other information on the command-line window such as CRC checksums of the ROMs, input & sound devices being loaded, status of initializing the ROMs, etc.
-debug launches the debugger.
-nomax makes the window the native size of the game, rather than the maximum size your monitor can fit.
In this demo, we were looking at the tapbg0 and tapbg1 files in the hex editor. I showed those in conjunction with the tile map and color palette to describe how I deduced the encoding scheme.
Each tile offset is simply the tile address shifted up 4 bits. Makes it easy to calculate! Just stick a “0” on it in hex and you’ve got it.
How did you know it was the program ROM? Process of elimination ;)
Depending on how wide your hex editor is, you might have one row of tile addresses on more than one line, so that the line is wrapped. In the picture at top right, every other row is actually a continuation of the first row of tiles, thus ignore it.
The “50” parts, while not really part of the tile address, is still important. We’ll discuss that later.
Now that the extraneous data has been blacked out, take a look at how the tiles line up between the hex code, the snippet of the game background, and the tile file.
And this tile arrangement is easy, because at least the word COMMUNITY is written straight across, even if it is split into the top half and bottom half. The original arrangement of the Budweiser logo made next to no sense at all. I’d imagine they were using some sort of Gray code to minimize the number of bits that had to be changed to load each sequential tile, but since MAME doesn’t run on hardware from the early ‘80s, we’re not concerned with squeezing out every last drop of performance from our graphics engine.
Think about this in terms of probability or from an entropy perspective. The Zilog Z80 instruction set is comprised of a great many opcodes, making big chunks of code look random and uniform (as in a uniform distribution, where all byte values from 0 to 255 seem equally likely) to someone inspecting the machine code in a hex editor. However, the background tiles will seem to take on much less of a uniform distribution, exhibiting tendencies to repeat the same or very similar numbers many times over. You might also start to see some of the tile addresses called out from the tile viewer. In fact, the “96” just a little bit down from the middle of the slide, in the first column of the 5th grouping of four, is the byte before the beginning of the tile map.
In this demo, we opened up tappg1 and navigated to address 0x2211 (relative to the file, not the program space) which is where the background tile order information begins. We changed that byte in the hex editor (any value between 00 and FF will do), then saved it, and watched the ROM blow up because later on in some other program code, the checksum was calculated to be invalid.
Changing programmed checksum value won’t help because if you changed something in ROM 1 to alter its checksum, then you have to change its checksum in ROM 3 where the checksum code lives. However, this in turn changes ROM 3’s checksum, which ends up creating a “chase your tail” kind of situation.
Beyond these two solutions, people in class offered additional alternatives:
“1b.” Make a program to alter the checksum for you after you make breaking edits.
3. Jim recalled that older versions of MAME for Windows seemed to let you edit the checksum value without having to rewrite the ROM or use the debugger. Then again, this recollection was about 15 or 16 years old.
0xBEEF is our hypothetical 16-bit memory address. You can watch a whole range of memory addresses for reads/writes by increasing the middle value from 1 to a larger number.
In this demo, we opened up tappg3 to look for the string “ROM ERROR”. Luckily it was there encoded in ASCII format. It’s in the file at 0xDDF, which translates to 0xCDDF in the program space, thus we set a watchpoint at 0xCDDF because this piece of memory would be copied on-screen to be shown. The first time the watchpoint is hit, we discover it’s in the midst of a loop where it’s adding up all the values of the ROM to calculate the checksum. The second time this address is hit, it’s being used to throw up the message. Instead of letting it get to the second time, let the loops run their course but put a breakpoint at 0xCD86. This statement compares the value of a memory address storing the expected checksum to the accumulator, which will raise the (Z)ero flag if the two numbers are equal. If the Z flag is not raised, it proceeds to continue along running instructions 0xCD89 through 0xCD8F in order to store the ROM’s index to a memory address so it can keep a list of which ROMs are bad. Because we don’t have physical ROMs going bad, we can simply use the hex editor to replace all the instructions from 0xCD89 through 0xCD8F with 00s (NOPs) so that when it finds invalid checksums, it simply doesn’t act on that information.
Little endian means the least significant byte of the two-byte number comes first when you look at it in the hex editor.
Consider using flips to save on tiles. Tapper doesn’t seem to do this very much.
However, despite bits 15:14 weren’t seen to do anything to the graphics, it should be noted that the vast majority of tile addresses seem to have bit 14 set. The earlier levels tend to use palette 1 throughout their screens, leading to the digit “5” before the tile address. For example, the complete address for tile 0x1F would be 0x501F, or in little endian, 1F 50. Later levels tend to use other palettes more extensively, so looking for these bits changing is a good visual cue for boundaries between screens. Nevertheless, it might be worth investigating the hardware schematics or more documentation to see if bits 15 & 14 are wired up to anything.
Top left: YY-CHR is a really popular tile editing tool that’s been around a long time and has a bunch of presets mostly for home console systems. Note here how it’s not handling the Tapper background ROMs very well; it seems to be showing them backwards and with a horizontal offset of 4 pixels. This makes it pretty darn difficult to use this tool for your editing purposes.
Nevertheless, romhacking.net might have something for you; there are a bunch of decent utilities to check out anyway. You can filter by console/system, your platform of choice (Win/Mac/Linux), level of difficulty for using the tool, etc.
The top right is a snippet of code from the Python program I whipped up to take 256-color bitmap files, convert them into the 2-bit encoding required for the background ROMs, and then write them right into the ROM at the desired spot. Someday this will make it up on romhacking.net too, but I’d like to give it a GUI before getting serious with it.
In this walkthrough, I showed the code for my Python program in its entirety and explained how it works. It takes a tile file, a color file, and a bitmap file. The tile file describes the address in the background ROM where the corresponding 8x8 section of the bitmap file should go. A blank address indicates to the program not to write that part of the bitmap anywhere. The color file maps each color in the bitmap image to the particular “minor palette” and color offset that it corresponds to in order to make the desired graphic. I then took the virgin Tapper ROM set, unzipped it into the ROMs folder in MAME, and turned my Python program loose with another image to replace the big Budweiser logo in levels 1 & 2. It did this with ease, but because the tile map used for Budweiser is not the same tile map as my other logo requires, I then exited MAME and edited part of the tile map quickly by hand. Upon rerunning MAME, it straightened out the new logo so it made sense.
Whatever you’re not going to use color-wise needs to be made transparent, especially if you want to put in a shape that is not rectangular. If you are using a rectangular graphic and want all the colors in your graphic in the game, then you do not need to worry about using transparency.
If your image is wide, then consider the widest spot it can go in the game and how many pixels it needs to be to fit in the desired spot. Then follow the steps as directed.
For the Community logo shown here, four colors is the number we picked in order to maximize use of the yellows and browns for the wheat plus the rounded rectangle box color while minimizing extraneous colors that wouldn’t belong to the palette we are targeting. Not everything will come out perfect, most likely, so be prepared to retouch the colors in the image manually.
Remember you only have use of the 16 colors in one specific “major palette”, i.e. rows 0, 0x10, 0x20, or 0x30 as pictured above. Recall that previously we described how the major palette gets set by bits 13:12 of the tile’s address in the game program ROM. In the Tapper game, palette 1 gets used almost exclusively.
You don’t necessarily have to make the image colors exactly the same as the palette colors if you’re using a tool that will map the color in the image to the desired color in the palette. This is especially true with the tool I designed to import bitmaps right into the ROM.
There are a variety of tools you can use. You could use a hex editor and manually overwrite the necessary tiles. The code snippet on the left comes from the tool I wrote that takes a 256-color bitmap and a couple different user-defined mappings. The first map links each color in the bitmap to the desired palette color in the game. The second map defines which 8x8 square of the image will be written to which tile address in the ROM. As of this writing, it has not been made available anywhere yet, but once it gets cleaned up with better documentation and doesn’t require you to edit the source code to choose your input files, it will wind up on romhacking.net.
Note the error in the command line output comes from some lines of comments that it doesn’t know how to parse, so it’s warning you it’s ignoring some input.
This might seem a little condescending, as it implies you can’t get very far without knowing programming. Well, as a programmer, I can vouch that practicing that skill gives you intuition and the ability to make your own tools to make this process go much easier if you choose to go about doing it yourself. Otherwise, you will find yourself leaning upon major crutches, suffering through documentation you can’t understand, finding tools that are either inflexible or have multiple ways to combine options you don’t understand, and/or waiting on others to give you answers before you can move on. (All those things still happen to me anyway, but a little less so.) Hopefully this presentation has facilitated hacking and decoding at least one more platform (namely the Midway MCR-III) for non-programmers.