So, I've gone quiet for a couple of months. That's because I've moved off one Z80-based project onto another. Specifically, I've started reverse-engineering the classic ZX Spectrum isometric game "Head Over Heels".
I've a few reasons for doing this. I've intended to do more reading of others' code for a while. Reverse-engineering is perhaps an extreme way of doing this, rather than just reading source code, but it's one way to go about it. I've been intrigued by how a quite complicated game like this gets implemented on a simple machine, and the whole thing has a huge nostalgia value. So, it seemed like a great project.
It looks like I'm not the first to do something like this, given there's a free-but-not-open-source reimplementation for modern computers. On the other hand, it doesn't seem a perfect copy, and, well, I haven't seen them publish reverse-engineered assembly, so it's still something of a mystery.
So far, it's been an interesting reverse-engineering exercise. There's somewhere around 8 thousand lines of assembly in there, and I think I've now identified about half of that (some very loosely). The entry points have been the literal entry point to the code, interrupt vector routines, and those routines that perform I/O - keyboard/joystick reading, sound output, interrupt triggering, writing video to the screen etc.
From there, I can start to identify and reverse the routines for writing text and sprites to the screen, constructing menus, how user controls are passed in, etc. By taking the memory dump and appropriately twiddling it into an image file, the sprites are easily found, and references to them can then be located.
Having built up a set of low-level routines, how the higher-level routines use them can be found. For example, the screen drawing routines make it possible to find the locations where lives and inventory are stored. The keyboard routines allow me to find the code that handle the "pick up object" case, which then provides me with a reference to the data structures representing the objects in the room. Each abstraction provides a crack that can be forced open to provide insights into the next chunk of code, which previously looked completely impenetrable.
So, that's where I am right now. While I'm halfway-through, or so, that's the easy half done, of simple, low-level routines. The rest may be something more challenging.
It's also reminded me that standard software-engineering techniques really helps. I'd had a previous pass at doing this before, but got nowhere near as far. This time, two things have been really helpful. The first thing is that I got the disassembly assembling back into the original image. By doing this, I can test that the change I'm making, improving the labelling, pulling out image files, etc., don't actually affect the meaning of the code, and what I'm reversing is true to the original image.
The other thing is to use source control. The ability to checkpoint the work I'm doing means I can undo silly mistakes, and see exactly what I've done since the last checkpoint. It's about the dumbest use of source control possible, but it's really brought home to me how useful it can be in contexts outside developing software or writing a paper.
All round, it's been a fantastic learning experience so far, in terms of picking up reverse-engineering techniques, but also in terms of learning about Z80 coding and writing games for the Spectrum. Wonderfully educational!
Posted 2014-01-24.