The home computer was born in the early 1980’s and in England this came in the shape of the ZX Spectrum. Along with it came the birth of the British video game industry. Although I had a ZX81 the year before, it was the ZX Spectrum I received as an xmas gift in 1983 that got me hooked on games, in particular the classic Manic Miner; a simple, addictive, and deceptively difficult platformer.
A year and a half ago I found the Skoolkit disassembly toolkit and one of the examples was the disassembled source code for Manic Miner. As I’d recently started learning to program in C, I thought it’d be a great learning experience to port this classic Z80 game to the C language. After some months I had a working version of Manic Miner written in C and using SDL2.
Porting Manic Miner was a lot of fun and it’d given me a greater understanding of how these early assembly games were written. It had also piqued my interest for how these old Z80 games are disassembled so I decided to give it a go myself.
There are many speccy games to choose from, such as Jetpac, or the classic Knight Lore, but that had been disassembled by others already, so for my first disassembly I decided on Cyclone (the sequel to TTL - Tornado Low Level). Although TLL was the more popular of the two, I’d always preferred the less frantic Cyclone. I thought this would make an interesting project, especially due to its isometric game view.
As of writing, this project is not complete, but I wanted to share some of the tehniques I’ve discovered so far…
Note: if you’ve never encountered Z80 assembly code before then I strongly recommend you do some study first - the internet is your friend here. I learned a lot while developing my Manic Miner port.
For this we’re going to use the Skoolkit disassembly toolkit. Although there are other disassembly tools available, Skoolkit is by far the best suited for ZX Spectrum games.
You’ll want to make good use of the Skoolkit documentation for installation and usage instructions, which can be found on skoolkit.ca.
Although it is possible to start decompiling directly from a tape image (
.tzx) it’s actually more useful to use a Z80 snapshot, and the best way to get that is via an emulator. I’m running macOS so I’m a little more limited in choice, but whatever platform you’re on, the FUSE emulator is one of the more popular.
After loading the Cyclone tape image into FUSE you will be presented with the games’ main menu.
At this point you should export a Z80 snapshot file as
You could begin immediately by running this Z80 snapshot through Skoolkit, however, you’ll save yourself a great deal of time if you first create a code execution map (also known as a profile or trace) - plus you’ll have some fun playing the game!
At the core of the disassembly process is the Control File. We can have Skoolkit analyse the Z80 machine code and generate this file for us, and if you have a good execution map, Skoolkit will produce an even more accurate control file.
The better the map file, the better the control file.
In FUSE, start the profiler by selecting the
Machine > Profiler > Start menu item.
The trick to producing a good map file is to play the game from back-to-front and top-to-bottom. For Cyclone, this is visiting the instructions, map, and the game area - both North facing and South facing. Collect all the crates, pick up some people. You’ll also want to crash into things, run out of lives, run out of fuel, and run out of time. Win if you’re able!
Your aim for this process is to exercise every aspect of the game.
Once done, stop the profiler and save the map as
Due to a bug in Skoolkit, I’d started disassembling Cyclone without using a code execution map, but a recent update of Skoolkit had fixed this so I gave the map a try. Not only did it find all the code/data blocks I’d discovered manually, it also found additional code blocks that I’d marked as data. Needless to say, I would’ve saved myself a heck of a lot of time if I’d used the map in the first place – lesson learned!
A control file contains a list of start addresses of code and data blocks. This information can be used by
sna2skool.py to organise a skool file into corresponding code and data blocks. – excerpt from skoolkit.ca
With our code execution map ready, we can begin by generating a new
.ctl file. From the terminal, execute the following command inside the folder where your
.map files are located:
$ sna2skool.py -M cyclone.map -g cyclone.ctl cyclone.z80 > cyclone.skool
This will produce a
cyclone.ctl file and a
Eventually the control file will become redundant, with all work done directly on the skool file, but for now we’ll concentrate our efforts exclusively here.
Here’s how the
cyclone.ctl file looks at this stage:
b 16384 t 17872 b 17875 t 18033 b 18036 t 18128 b 18131 t 23229 b 23232 ; ... etc., etc., etc.
Each line starts with a control directive, followed by an address value. If you don’t know the significance of this first address I recommend you do some study on the ZX Spectrum’s memory layout.
As we can see, the first address is
16384, the start of the Spectrum’s display file, which at the time that we created the Z80 snapshot, was the Cyclone main menu.
It’s actually possible to tell Skoolkit to output all addresses as either
DECIMAL or as
HEXADECIMAL values. The Skoolkit examples use decimal, but often you’ll find examples of assembly code using hex. I’m not sure yet if working in hex is a better choice, so for the moment I’ll make my life easier by sticking with decimal.
If you know any good reasons for working with hex address values instead of decimal, please leave a comment below.
The process of disassembling a game with Skoolkit consists or two core steps:
You will analyse the skool file, trying to find game text, sprites, data blocks and code blocks, then annotate the control file with your new found understanding. You then generate a new skool file and iterate until the disassembly is complete.
When we generated the
ctl file we also generated a
skool file. From now on we need only re-generate the skool file. For this we need a slightly different command so that we don’t overwrite our edited control file. Let’s do that now:
$ sna2skool.py -c cyclone.ctl cyclone.z80 > cyclone.skool
Now, when you update the control file, you can run the above command to generate a new skool file.
Here’s a slice of our control file after adding some annotations:
; ... c 26112 Score routine b 26140 Scores GFX T 26141,1,1 SCORE label T 26142,7,7 SCORE value B 26149,1,1 T 26150,1,1 HISCORE label T 26151,7,7 HISCORE value b 26158 ; ... b 29956 Jet fighter sprite ID b 29957 b 29958 b 29959 b 29960 b 29961 when re-fuelling first byte changes b 29963 when re-fuelling first byte changes ; ...
There’s no magic source to understanding what the different parts of the assembly code do. You just need time and patience. However there are some techniques to aid in this process.
Skoolkit’s introduction provides some useful advice, which I strongly recommend you read. I’ll list the 6 core steps here:
The Skoolkit docs say you should make your edits directly to the
skool file. Richard has a lot more experience than I at this, but my recommendation is to continue working with the
ctl file. I find this much more intuitive and faster. I can play around with blocks (is it data, is it code?) more freely when working directly on the control file, and much less text twiddling.
Side note: if you use the SublimeText 3 editor then you might like my SkoolkitZ80 Syntax highlighting package to make viewing your
.skoolfiles a little more pleasant.
The first step above states, “Skim the code blocks for any code whose purpose is familiar or obvious”. At this point you might be saying to yourself, ‘I’ve never disassembled machine code before so there is neither familiar nor obvious code!’.
What do we do?
Here’s a few things I’ve learned while reverse engineering the Cyclone game.
This one is generally quite easy as Skoolkit does a good job of displaying all hard-coded text strings. For sure there’ll be many false-positives, but you can figure these out pretty quickly and annotate the control file appropriately.
Many games however don’t use hard-coded strings, they have their own custom font or text graphics. To find these you’ll need to extract sprites from bytes.
This is not so straight forward as you’ll need an external tool for extracting sprites and graphics.
There are various tools that can help to show what sprites exist in a bunch of bytes but none of the ones I found for macOS were that friendly; difficult to use/navigate, and the sprites were displayed at their original size, which on my 13-inch laptop made distinguishing sprites from random noise difficult indeed.
In the end I wrote a simple Ruby script to dump the whole
cyclone.z80 bytes to a file as pseudo pixels, which I then opened in a text editor to see what sprites I could find (see below for an example).
Rather rough-n-ready but it worked well enough. I made some further updates to the script to be able to output not only
8x8 sprites, but also
16x16, etc., which helped greatly for the larger sprites like the helicopter, and the main menu “CYCLONE” logo.
Here’s a cyclone sprite as displayed on the map overview screen:
; cyclone sprite b24333 DEFB 62,65,156,186,93,57,130,124
b24333 is an
8x8 sprite. Each decimal number expresses a row of
8 pixels, which means we need to convert each decimal number to a binary format.
62 expressed as binary becomes:
00111110. With 1’s and 0’s representing pixels and spaces. Therefore this whole sprite in binary would be:
62 : 00111110 65 : 01000001 156 : 10011100 186 : 10111010 93 : 01011101 57 : 00111001 130 : 10000010 124 : 01111100
And converted to something that resembles pixels:
█████ █ █ █ ███ █ ███ █ █ ███ █ ███ █ █ █ █████
It’s also possible to discover some variables and other data by comparing two different game snapshots. I’ve only tried this once so far, and I imagine it can get out of hand quite easily, but it may be worth considering.
For this experiment I tried to keep the scope of the two snapshots very close, so I looked at re-fuelling.
I slowed down the FUSE emulation and when the game reached the point where the helicopter was re-fuelling on the helipad, I made snapshot. Then when the fuel gauge was full, I made a second snapshot.
From these snapshots I generated two separate
.skool files, which I then
diff‘d to see all the places that looked like they could be fuel related variables, fuel gauge graphics, etc.
I think as an early stage experiment it can be useful, although it’s probably of limited use.
Reverse engineering old Z80 games is an interesting challenge. You not only become familiar with assembly code, but also the techniques for how these games had to be written for machines with limited CPU power and limited memory -
3.5 Mhz CPU and
48 KB RAM in the case of the ZX Spectrum - pretty good for 1982!
My progress on the Cyclone disassembly is ongoing, but it seems I’ve now reached the point where editing the
skool directly will be more useful than ping-pong-ing with the control file. I may well write up another post once I’ve finished the disassembly.
I hope you’ve found this short introduction to reverse engineering ZX Spectrum games useful. If you have any questions about the process, please leave a comment below and I will try to answer.
Hi, my name is Michael and this is my personal blog. Here I’ll be posting various coding thoughts and experiments; everything from writing blogs in Ruby, to Go tools and Z80 Assembly. This site is powered by Thunderaxe, a blogging platform I built using the Roda Ruby framework.