Reverse Engineering Prince of Persia on Apple II

I tried to reverse engineer the original code of Prince of Persia on Apple II, for some of it's parts, which was quiet interesting!

Reverse Engineering Prince of Persia on Apple II
Photo by Museums Victoria / Unsplash

I tried to reverse engineer the code of Prince of Persia for some of it's parts, which was quiet interesting!
I came along to dive deeper into this when I was in contact with Golo Roden from the native web, who ask me if I would be interested in presenting this code on a live stream on YouTube.

It was a fascinating experience to dive into the world of 6502 Assembly, that mythical game and the game development back in the 80s. Reading the source allowed me to unterstand how much work and knowledge went into it and it shows me how grateful we can be for the things we take for granted today.

Acknowledgment

First of all - I would like to thank Jordan Mechner for providing that amazing piece of history!
Many thanks to Roger Wagner which helped me with his book Assembly Lines: The Complete Book to understand the 6502. 
Also many thanks to Fabien Sanglard for providing a really good review of the PoP code, which helped me a lot to get into the code.

The 6502 CPU on the Apple II

The Apple II is powered by the MOS 6502 CPU, which can also be found on the Commodore C64 and the Nintendo NES (MOS was later bought by Commodore in 1976).

The success of the 6502 was mainly achieved because it was quite cheap. Back then, the 6502 only cost $25. In comparison Intel's 8080 has cost around 250$.

One reason that the 6502 was so inexpensive was that it needed only 3510 transistors. It’s less than half the number used in its key competitor the Zilog Z80 which used 8500 for comparable (or sometimes worse) performance.

One way that the 6502 managed to keep the transistor count low was its sparing use of registers. It had a single eight-bit accumulator, two eight-bit index registers an eight-bit stack pointer and a sixteen-bit program counter.

Compare that with the competing Zilog Z80 with 8 eight-bit registers and 4 sixteen-bit registers.

Why has the 6502 performed so well with so few registers?

It could quickly access the first 256 bytes of memory (called page zero as the first byte of the two-byte address would be zero) using instructions that only needed one byte to specify the memory location.

The same trick could be applied to the stack, which is located right above page zero in the so called page one as it is addressed from $100 to $1FF . This means that the stack pointer also only needed a single byte, to address this part of the memory.

The 6502 had only 56 instructions, which are also less than it's competitors. But it used a trick to compensate that, by using different addressing modes for multiple instructions which expand the possibilities, with so few registers.

πŸ™ƒ
Fun fact: 6502 is the processor of the robot Bender in the Futurama series

More about the 6502 can be found on this amazing blog post.

Connection to memory

The Apple II had by default a maximum memory of 64 KB RAM. Because the CPU only can address up to 64KB. Therefore the successors //e and //gs used another trick to address up to 128 KB of RAM by shifting RAM Banks.

The memory range from $C000 to $CFFF was reserved for soft switches, which allowed to switch between memory banks and different hardware, when set. Because this was reserved the missing 4k of RAM at this location could be switched with the memory at $D000 to $DFFF (Bank 0 and Bank 1).

The same switching was possible with the Auxiliary memory and with the ROM.
But it was not only a simple switching between memories. The soft switches could be set to enable read and/or write on a specific part of the memory.

Additional hardware was also accessed trough the memory as it was directly memory mapped. For example there are different video modes with different resolutions and colors, which could also be set using soft switches.

Overview of the Memory Map

APPLE //e RAM - (Memory allocation based on EQ.S and findings in code)

Now let's begin with the code from Prince of Persia.

How the files are structured

The Game Engine is made of numerous .S files. Back in the day this was one of the ways to structure code, but also you where limited somehow in the file size of such files, as of available RAM.

All these .S files contain a ORG directive which allows the Assembler to know where will the files be loaded into RAM.

There are some files which hold some references in form of constants. These are used to reference different parts of the program or the data.
Most of the files include such reference files at the beginning:

 lst
 put EQ
 lst
 put GAMEEQ
 lst
 put SOUNDNAMES
 lst off

They also include numerous jmp directives at the beginning right after the ORG directive. And this is important for referencing with multiple files. They are the direct reference to some label in the code right after.

A JuMP instruction needs 3 bytes in memory, which means that the next jmp is located right after that in memory.  Example how it look like in memory in comparison to the ASM code itself (right):

                1           org $400
0400: 4C 0C 04	2           jmp GR
0403: 4C 0E 04	3           jmp DRAWALL
0406: 4C 10 04	4           jmp CONTRL
0409: 4C 12 04	5           jmp VERSION
040C: EA        6   GR      NOP
040D: EA        7           NOP
040E: EA        8   DRAWALL NOP
040F: EA        9           NOP
0410: EA        10  CONTRL  NOP
0411: EA        11          NOP
0412: EA        12  VERSION	NOP
0413: EA        11          NOP
0414: EA        11          RTS

The ORG directive instructs the Assembler to start at memory location $400. In this code there are multiple labels like GR on line $040C.
The JuMP directive is 4C as HEX in memory followed by the line number (2 bytes - Little Endian order) which it is referring to. For example the first jmp is referring to GR on line $040C.

The advantage of such jump blocks at the start is, that they always keep the same address in code even if there are changes in this file. So it will be easy to jump from another file to a function that has been programmed in another file, by simply refering to a specific fixed place in memory.

And there comes files like the EQ.S into the game. This is some part of it:

    grafix = $400

    dum grafix          => Instructs the Assembler to start with label values at a given index of $400

    gr ds 3             => Therefore the label `gr` starts at $400 and reservse 3 bytes of memory
    drawall ds 3        => then `drawall` must be at $403 and reservse 3 bytes of memory
    controller ds 3     => $406 + 3 bytes
    ds 3                => $409 + 3 bytes
    saveblue ds 3       => $40C + 3 bytes

If you then referring in a different file to gr with a Jump to it, the Assembler would know, based on a file like EQ.S that the jump must go to memory address $400 and on $400 is another jump that finally brings us to the function of that specific file.
There are also jmp's that directely point to a specific location whithout the use of labels.
So if you want to know where a Jump is pointing to, you might need check multiple files. But most likely (today) you also can find the function by using a search function over multiple files in your editor/IDE or by using something like grep in your terminal.

Boot - First boot up from disk

The Apple II starts to load the fist sectors from the disk into the RAM Register $0800
This part is located in the file BOOT.S

**APPLE II RAM**
----------------------
$FFFF - |
        |
        |
        |
        |
        |
        |
        |
        |
        |
        |
        |
        |------------
        | BOOT.S - 
$0800 - |------------
        |
        |
        |
        |
        |
$0000 - |

This is established by outputting $01 to the instruction stream (line:21). This will trigger the Apple firmware to automatically load one sector from track:0 to the RAM at $800 and branch there.

The boot is first setting some essential soft switches (e.g. clear display, display text mode, setup RWTS16) at line:31. And then loads the rest of BOOT.S and RWTS18 into the RAM. Therefore it uses the ROM routine RWTS16 of the Apple II to load a bunch of sectors to RAM using skewing table table (line:79).
The skewing table is used to access the sequential sectors of the disk with a technique called Interleaving.

From Wikipedia about Interleaving:
In block storage devices such as hard disk drives, interleaving is a technique used to improve slow system performance by putting data accessed sequentially into non-sequential blocks, typically sectors.

Interleaving was used to arrange the sectors most efficiently, so that after reading a sector, time is allowed for processing, and then the next sector in sequence is ready to be read just as the computer is ready to do so.

Hardware Check

Prince of Persia requires the full amount of 128 KB of RAM to run. Therefore it checks right at the beginning if the Auxiliary Memory exists, which is usually the case on the Apple //e and the //gs (e.g. line:113 and line:167). This is achieved by writing, reading and comparing the data from the main and aux memory. If the aux memory does not exists then the data failed on the comparison.

Read Write Routine RWTS18

At the startup the BOOT.S is also loading a custom disk read/write routine which is called RWTS18. This routine is later be used to load the other sectors from the disk. The RWTS18 routine uses larger sectors/tracks as the original routine of the Apple II, which helps to manage and store a bigger amount of data, as there are less unused space left with this approach.

πŸ’‘
A blog post with more detail about this routine, can be found at Fabien Sanglard really good review of the PoP code.

After the routine is set up it will load several data into the RAM and finally it jumps out of BOOT.S to RAM address $EE00 (line:138).
This address can be found at the start of the file HIRES.S.
HIRES.S
directly jumps to to $F880 at beginning (line:13) which again is the start of MASTER.S

Overview of the RAM at this stage

Master - Main routine that handles the start of the game

We are now at the start of MASTER.S were we directly jump at the beginning to the Firstboot routine at line:186.

From there on it get's complicated as the program will load a lot of necessary data for the later game. And it is not easy to understand of what is exactly loaded without reverse engineering the disk sectors. But that is not trivial because it will become clearer what some of this data will be, later on.

So several data fragments are loaded:

  • Hires tables/code into RAM from $E000 to $ED00 from disk via RWTS18 (line:186)
  • Permanent code & data from disk via RWTS18 (Loads as much of Stage 3 as it can keep)
    • into AUX RAM from $0400 to $2700 which is GRAFIX.S & TOPCTRL.S (and maybe more?) (MASTER:line:207)
    • some parts into MAIN RAM (MASTER:line:1095)
    • Load aux l.c. stuff (tracks 19-21 & 34) (includes music set 1) into MAIN RAM and moves it then in AUX RAM (MISC:line:152)

After the loading is finished the disk drive will be turned off. And the program checks if the Hardware is a //gs which has specific display settings for Super Hi-res images (MASTER:line:215 & GRAFIX:line:2015).

The game has two modes. One is the builder which builds the levels of the game and the other mode is the game play itself. Some parts of the program are only present in memory while one of these modes are active.

The game then initializes some parts of the Hardware, like for example that it centers the Joystick (GRAFIX:line:1232).
And some basic variables (which are basically memory addresses) (TOPCTRL:line:223).

The sound will then be activated (even though it will not yet play any sound) and we are jumping directly into the so called Attract mode.

The game finally starts

The Attract mode is a self playing start of the game which is like a demonstration mode intended to attract potential players (MASTER:line:687).

First of all the music is turned on by storing a 1 in a register above $300. But it will not play music yet as this register is not mapped to some sort of hardware. But it might be interesting to know that registers around this address might have later on something to do with music or configuration.

The game will then setup the screen for double hi-res by setting some Soft switches (UNPACK:line:627) and loads lots of the data for the first stage of the game (MASTER:line:1168), as well as music data (MASTER:line:1180).

It then moves data to the area were the double hi-res screen is mapped to to finally show as this image:

Publisher Credits "Broderbund Software Presents"

Also the music finally starts playing in a interruptible way, which is interesting!
It reads the previous loaded flags which hat activated the music/sound (MASTER:line:1389)

  • While music is playing, it checks for a keypresses (MASTER:line:1389)
    • Esc to pause, Ctrl-S to turn sound off (which also toggles the previous flags for the music)
    • Return A = ASCII value (FF for button)
    • It uses a sound library called Music System II by Kyle Freeman to play sounds (MSYS II)

After that the screen get's cleared again (MASTER:line:764

πŸ’‘
If the player presses a key, than we jump out of the attrack mode (which is basically the prolog and a demo scene) into the gameplay.

Now it will shows Author Credits "A Game by Jordan Mechner" and basically the same happens as in the previous step:

Author Credits "A Game by Jordan Mechner"

The same happens again for the next image:

Shows Title Screen "Prince of Persia"

After the credists are shown, it starts to play the prolog 1

It does seem to decide to load Hi-Res images based on memory locations (MASTER:line:100), same for music (MASTER:line:116)

Which starts with the Princess Scene:

Princess Scene - Princess's room: Vizier starts hourglass

After that the Prolog Part 2 starts - which works similar as Prolog 1. Followed by showing the title again but without additional music.

Finally a demo game play can be seen. And all this will now loop over and over until a key is pressed to interrupt the demo, which then will start the game.

In my opinion, from this point on it's no longer particularly exciting to dig deeper, because from now on the game will always load new data and react to user input, etc. You can do this if you want to understand more, but it will be a very time-consuming undertaking. You have to know that the development of this game took around 5 years - which basically means that the reverse engineering can take much longer.

Code