A disassembly of a game for the Fairchild Video Entertainment System.
Build Instructions
dasm dodge_it.asm -f3 -ododge_it.bin
Current status of the project: ~98%
Every chunk of code has been deciphered and documented. Very few questions about the game's code remain, some of which may be unanswerable. What remains is a little bit of documentation and clean-up work (though that could continue indefinitely).
There are four unused characters in the character set which are, in order, "F", "A", "S", and "T". There does not appear to be any place in the code where they could be displayed, though hacking the timer to $CDEF just before it's drawn would get it to be displayed.
When initializing the game, there is a table that is referenced and an index is calculated for it, but it is never read from. I suspect that this table was somehow related to enemy speed in an earlier revision of the game.
There is an unused function that flashes the screen. Given how it reads from the same register that the game over procedure uses to determine who died, I suspect it was used for game overs before the iconic multicolored spiral effect was written.
After the last line of code in the game, there is an unused byte (0xB2). I'm not sure what it means, but it does mirror the second byte of the game's header (0x2B). Coincidence?
The F8 has a 64 byte "scratchpad" of registers that functions similarly to RAM. The processor has a register called the ISAR, or Indirect Scratchpad Address Register, that allows the scratchpad to be accessed arbitrarily.
There are several opcodes that can read and write what the ISAR is pointing to, and read and write the ISAR itself. Of note are the instructions LISU and LISL -- one loads the upper octal nybble of the ISAR, and the other loads the lower octal nybble. Given this fact, it is convenient to map out the scratchpad in an 8 by 8 grid, as shown below:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
---|---|---|---|---|---|---|---|---|
0 | Locals, arguments, and returns | |||||||
1 | Locals (cont.) |
J (flag storage) |
Game Settings |
Current Ball |
K (call "stack") | Q (for jump table) | ||
2 | xpos | |||||||
3 | xpos (cont.) | ypos | ||||||
4 | ypos (cont.) | speed | ||||||
5 | speed (cont.) | P1 Hi Score | Num Balls |
Delay Num |
||||
6 | R Wall (Enemy) |
R Wall (Player) |
L Wall | S Wall (Enemy) |
S Wall (Player) |
N Wall | Timer | |
7 | temp 1 | temp 2 | Explosion Flag |
P2 Hi Score | Game Mode |
RNG |
The registers "temp 1" and "temp 2" are used, depending on the context, to store the controller inputs, some arguments of doBall(), or a local variable for collision().
The registers used for local variables differ in their usage depending on the function. Most commonly r1 is used to hold an x position and r2 to hold a y position, but that is not always the case. Also, in some functions r9 ("J") is used as a local instead of for storing the processor flags.
The F8 processor does not have support for a hardware call stack to save return addresses when calling functions. Instead, it has a main program counter (PC0) and a secondary program counter (PC1). When calling a function using the opcode "PI", the return address is pushed from PC0 to PC1. When returning from a function using "POP", the return address is popped from PC1 back into PC0.
Fortunately, the F8 has the ability to save and write to the secondary program counter (PC1). Using "LR K,P" saves it to the "K register" (scratchpad registers 12 and 13), and using "LR P,K" does the opposite. Also, using "PK" allows us to jump to wherever the "K register" points.
The simplest calling convention the hardware permits allows us to go two layers deep, with each function being in a fixed layer of the call graph. This is the calling convention that Dodge It uses. With this knowledge, we can create a call graph of the game, like so:
Here is an explanation of the terminology I made for the graph:
- Top-Level Procedures - The procedures that make up the core of the game. They can be jumped between, and other functions can jump to them (though in that case they can't be returned from).
- Mid-Level Functions - Functions called from the top level that can call a lower level function. These functions need to be bookended by "LR K,P" and "PK" in order to work.
- Leaf Function - Called such because they form the leaves of the call tree, being called either from the top or mid level. Since they do not call any other functions, they can be exited with a simple "POP".
Note that more sophisticated calling conventions are possible. For instance, the BIOS provides functions to push and pull return addresses from a software stack on the scratchpad. However, given that the scratchpad is so small, this is not practical in many cases (such as with this game).