Chris White - Emulation & Decompilation

Tuesday, September 29, 2009

Tools and Tiles

Wrote a quick tool, which takes an Outrun CPU 0 memory dump from the MAME debugger as input. As output it prints the jump table from that stage of the game that has been built and stored in RAM.

This is handy, because it formats the data and only shows the addresses which are currently enabled, and doesn't repeat addresses when they're called in succession. The table in RAM itself is 124 entries long, and updated at various points during runtime.

So here's the in-game output:

0. 0xB15E
1. 0x74E2
2. 0x3BEE
3. 0x4048 (16 times)
21. 0x4828 (15 times)
82. 0x4ADC
83. 0x5248 (5 times)
91. 0x9862
92. 0x9C84
93. 0xA568 (2 times)
95. 0x5EA8
103. 0xA816
104. 0xA7D2
105. 0xA816
106. 0xC5A4
123. 0x78B0
124. 0xE644


This has made it easier to figure out exactly where in code the program is updating this table.

What else? I've been looking into the tile handling code. Tilemaps are used in OutRun for the horizon graphics (two layers) , the text layer is a tilemap and obvious things like the music selection screen are a tilemap. Each tile entry in the map is a word. The compression format for the first tilemap I've analysed in ROM is as follows:

1/ If a word is not '0x0000', copy immediate word directly to tileram
2/ If a word is '0x0000' a long follows which details the compression.
The upper word of the long is the tilemap value to copy.
The lower word of the long is the number of times to copy that value.

And here's how OutRun manages to store your exact route history through the levels in just a byte.

1/ There are 5 stages, and a memory location increases by 0x10 for each stage you progress.
2/ Each stage forks twice, giving 15 stages in total. At each stage, this same memory location increments as follows when the left hand route is selected:

Stage 1 = +8 (1 << 3 - 0)
Stage 2 = +4 (1 << 3 - 1)
Stage 3 = +2 (1 << 3 - 2)
Stage 4 = +1 (1 << 3 - 3)
Stage 5 = Road doesn't split on this stage

3/ Later this indexes into a lookup table, which then makes it easy to update things like the stage map at the bottom right hand corner of the screen during gameplay.

Other than that, I'm thinking about how to represent the graphics once I convert the engine to C. Do I keep them in a similar format, and just render a final array of pixel data (similar to how an emulator would render it's display), or do I convert them to a native PNG or similar? This would make it easier for someone to replace them and change the game, but could run into problems with palette changes and so forth. Thoughts...

Anyway, this post might give you some idea of some of the things I'm doing at the moment. There's still a long way to go.

Labels:

Thursday, September 17, 2009

Quick progress report: Commented all the code relating to detecting when the car is off-road. Figured out the memory location that determined which wheel was off. Established the memory location that determined the width of the road, and handles splitting the road between number of lanes and individual roads. Commented some slightly less interesting code relating to lap times, output lamps and various other areas.

Amusing OutRun link: Unbelievable. I have found someone with a more insane project than my own. A real world OutRun cabinet containing a golf-cart... The mind boggles?

Labels:

Thursday, September 10, 2009

Debugging

I reached a point where it was time to switch to a competent interactive debugger, instead of working out of a series of text files that were becoming increasingly difficult to manage.

It took me a good few days to transfer my comments and knowledge to the new system. But the payoff is massive - I've immediately spotted mistakes, and identifying blocks of code has become a lot less painful.



In the screenshot you can see the commented block of code that transfers the in-game timer to the on-screen HUD.

Yesterday I also documented how the main branch table works in memory. In fact, I'm not even sure if branch table is the correct terminology. Figuring this out was important because actually determining the program flow in Outrun is tricky. The list of functions that are finally called is generated at runtime, and differs at different points of the game.

Firstly, there's a portion of ROM that contains a series of long addresses. Each of these addresses is a pointer to another portion of ROM. This second portion of ROM contains a set of properties related to the function, and the final address of the function itself. The final step is to compile these to a table within RAM. This table contains bits so that the functions can be enabled or disabled.

I presume this is some kind of optimisation, but it seems to add a lot of indirection and makes following the flow of code rather tough from the onset. Maybe some level of obfuscation was the idea? Perhaps someone reading who is a 68000 wiz will know.

Update: Figured this out. Some useful information in the comments regarding this from one of the blog readers and myself :)

Labels: