Hacking ZX Spectrum Games Like It's 1989

Tales From The Dork Web 0x20

Like many I saw the reports of Sir Clive Sinclair's passing last month. The rubber-keyed 48k ZX Spectrum was my first ever "proper" computer. Without the humble Speccy my career would’ve been very different. I don’t want to diminish the loss from his passing but my attachment was always to the Speccy, not the man. Instead of an obiturary I'll walk through hacking Spectrum games as it was done in the 80s. I’ll also highlight some Speccy things you might've missed. I’ve structured this as a weekend afternoon project for you to try. Read along, enjoy the links and come back when you’re ready.

If you enjoyed this issue, check out some of the back issues. Get future issues in your inbox by subscribing below or add the site to your RSS reader.

If you want to learn the history behind Sir Clive Sinclair, Sequentia Soft’s Visual Tale series for the ZX Spectrum is the best way to experience it. The second best way is to watch the brilliant Micro Men drama above.

A Pig in a POKE

The Spectrum wasn't Sinclair's first computer. That was the MK-14. The ZX80 was Sinclair's first "proper" computer. It was terrible. I restored one this year. Every corner that could be cut was cut. How these didn’t melt through floors is beyond me.

The more practical ZX81 followed. It had a stable black and white screen and something almost keyboard-like. Unlike the ZX80, the ZX81 was just on the right side of functional, especially if you had a 16k RAM pack and blu-tack.

1982’s ZX Spectrum was a major upgrade from the ZX81. This was partly due to the built-in 16 or 48k of RAM and the Ferranti Uncommitted Logic Array (ULA) chip. The ULA brought two shades of 8 colours to the screen and fixed many faults in the ZX80 and ZX81. A Piezo buzzer provided the cheapest possible audio. Sinclair Research upgraded the keyboard to what journalists called "Dead flesh".

Nobody outside of school playgrounds pretended Sinclair’s machines were brilliant, or even good. They were underpowered, buggy and with expansions, often unreliable. But they were cheap and easy to clone. The Spectrum was a hit across the UK, Europe, South America and the Soviet Union. RMC’s video provides a PEEK into the Soviet Speccy Scene.

Sneaky PEEKs

Sinclair era computers had direct hardware access. Users could interrogate RAM, hardware, anything, with the amusingly named PEEK, POKE, IN and OUT (oo-er! Ed.).

PEEK and POKE were the gateway to machine code. Type-in BASIC programme listings were already popular in magazines. A BASIC "hex loader" POKEing data brought fancy effects to drab BASIC listings. Your Sinclair's Program Pitstop sometimes had huge listings full of hex strings. It made more sense to put these on the cover tape included with each issue but that wasn't the point.

POKE let users enter machine code and change system settings. It could also overwrite existing machine code before jumping to it. To combat this, software companies used machine-code based loaders that disabled BASIC. This mostly worked until Romantic Robot released their Multiface in 1986.

Enter The Multiface

Today Romantic Robot creates and distributes classical music. Back in the day they made some of the best commercial reverse engineering Spectrum tools. With the press of a magic red button the Multiface sprung into action. Abusing the Z80's Non-Maskable Interrupt it'd interrupt running code. After saving state it'd jump to a ROM menu. This let users inspect memory, or back it up to tape. Piracy ensued. The Multiface wasn’t just a “backup” tool though. It also provided the ability to PEEK and POKE addresses while machine code was running.

Spectrum games were often distributed on cassette tape. There was a strong tape-to-tape copying community in British schools. If like me you often couldn't afford to buy full price games to share, you risked being locked out of access to new games. I got my grubby mits on a Multiface One and never looked back.

The Multiface provides a hex viewer and editor. For a while I saved games to tape, patched the Multiface loader and added a BASIC frontend. Code was hand-disassembled using the Spectrum Manual’s Z80 opcodes list. This worked for a while but become more painful as games became more complex. I should’ve saved up for Romantic Robot’s Genie software but never did.

I’m no good at obituaries, but I’m not terrible at hacking. So by way of a tribute to Sir Clive who hated the game machine I loved, lets hack a Spectrum game the old fashioned way.

Oh Do The Hokey POKEy

As a kid I concentrated on infinite time/energy and ammo pokes. Once you've done a few they're fairly easy to spot, although decades on I've forgotten more than I knew. No Z80 or ZX Spectrum internals knowledge are required. Expect to see hexadecimal though. I’ve chosen Hungry Horace as it's simple and well documented. Play along when you have a couple of hours to spare.

Hungry Horace is a Pacman-style game set in a park. Horace must eat the flowers across 4 levels to complete the game. If the park guards catch him he'll lose a pass. If he loses all his passes they'll throw him out of the park.

Horace knows neither good nor bad, only hunger. He’s trapped in a system built solely to destroy him. Lets give him infinite passes. Solidaridad Horace. La tierra es vuestra!

We need music to hack to. Bring your own or press play and read on.

The following items are needed for this quest:

  1. An Emulator (I recommend FUSE)

  2. Genie v1.03 TZX and Genie.txt (newer versions are 128k only)

  3. The documented Hungry Horace disassembly

  4. A copy of Hungry Horace

The documented disassembly uses decimal values but it's easier to work in hexadecimal (hex). I’ll use hex by default and note the decimal address the first time per paragraph. Hex addresses are prefixed with 0x - e.g. 0x5454 to distinguish from decimal (21588). Genie doesn’t support 0x prefixes, so don’t type the 0x part into the Genie interface - e.g. type in 5454, not 0x5454.

A notepad is very handy for those not used to hex. As you inspect addresses, write the hex and decimal values one per line. Add a summary note to each address. If you become disoriented refer to your notes first then the documented disassembly.

Check your emulator’s documentation for configuration details. In FUSE, set the machine to Spectrum 48k. Add Multiface One support under the peripherals menu. FUSE triggers the Multiface via a Multiface Red Button menu option. You’ll need an mf1.rom file. Put it wherever the other Spectrum ROMs live (search for 48.rom).

Once the Multiface is working load the Genie tape. Press I to install and Symbol-shift and A to do a soft reset. Next load the TZX or TAP version of Hungry Horace.

The intro screen looks and sounds bad but this was mid-end stuff for 1982. The screen has the text, “PRESS ANY KEY TO START PLAY”. The game starts when a key is pressed. The number of passes is probably set after the keypress check.

After a while a DEMO MODE appears and the game plays itself. Various noises and colour fade effects are shown. On keypress the maze fades down and the player is taken back to the title screen.

Getting To Grips With Genie

The Genie.txt file contains the official manual. Have a quick skim and play around a little. If you mess up, hard reset your emulator, reinstall Genie and reload the game.

Trigger the Multiface and the Genie interface should appear. Press Z to check the current Z80 register values. The H key toggles between hexadecimal and decimal numbering systems. Keep a note of the following keys:

  • D - Enter DISassembler

  • T - View memory as text

  • N - View memory as decimal or hexadecimal numbers

  • Z - Inspect Z80 registers

  • F - Search for a set of values

  • R - Return to the game

  • A - Alter a value (from within D, T, N or Z modes)

  • H - Toggle Decimal (blue border) and Hexadecimal (light blue border) numbering modes

  • Space - Return to the main menu

Lets pretend Z80 CPU Registers are local 8-bit variables. Some can be combined to make 16-bit (two-byte) variables called register pairs. Some, such as Stack Pointer (SP) and Program Counter (PC) are natively 16-bit. The PC register contains the address of the instruction being executed. Genie stores PC's value at the point the Multiface was triggered. When I triggered the Multiface from the title screen (not the demo), PC pointed to address 0x6A8D (27277). What’s in your PC register?

It’s ok when your register values differ from my screenshots. Press space to return to the menu. The D command disassembles an area of memory. Press D then enter 6A8D. From left to right, each line in the disassembly view lists an address, the raw machine code in hex, the relevant instruction and values supplied to that instruction.

Press return till you can see address 6A99 at the bottom. 0x6A91 (27281) holds an OUT instruction used to generate sound. At 0x6A9D (27293) there’s a RET instruction. Think of RET as the RETURN following a BASIC GOSUB command. The Multiface interrupted a sound generation routine. I had hoped to land in the main intro sequence but it calls the sound routine often. The code should return to the main intro sequence. The first stumbling block is finding the return address.

Return addresses are stored on a data structure called a stack. On the Spectrum the stack stores references (or pointers) to 16-bit addresses containing data. New notes are PUSHed on top of old notes. Notes are POPped off the stack top two bytes at a time.

The stack’s address is stored in a register called the Stack Pointer (SP). From the main menu Press Z to see the address next to the text SP. In my case SP is at 0x5F99 (24473).

When the Spectrum encounters a CALL instruction several things happen. The address immediately following the call is pushed onto the stack. In this case the address is 0x60AD (24749). The values are byte-swapped due to the way Z80 stores numbers. In this case 0x60AD is stored as AD 60 in memory. The Program Counter (PC) is then set to the CALL's address and execution continues.

When the Spectrum encounters a RET instruction the inverse process happens. The return address on top of the stack moves into the Program Counter (PC). Execution now continues from that address.

If you thought strange things might happen if stack values are modified you’d be correct. But that’s for another issue. An earlier screenshot showed an SP address of 0x5F99 (24473). The sound generation routine’s return address will be in the stack.

From the main menu enter numerical mode with the N key. Enter your SP value and a string of hex digits should appear. Each pair of digits refers to an address in memory. The only address close to addresses known so far is 60AD. Return to the main menu and disassemble 0x60AD (24749). Press G to jump back a few instructions. This provides a view leading up to the return address. Scroll forward to 0x60AA (24746) with enter.

At 0x60AA (24746) the code issues a CALL to 0x6A77 (27255). This is a jump to the sound generation routine encountered earlier. The Multiface was triggered at 0x6A8D (27277) partway through that routine. When it returns, it’ll continue execution from address 0x60AD (24749). 0x60AD isn’t in the sound routine. So where is it?

Remember the screen text mentioned pressing a key to start the game? If 0x60AD (24749) is in the title sequence the code will do one of three things:

  1. Check for a keypress using the IN instruction

  2. Jump to the beginning of the loop

  3. Call a subroutine to check for the keypress

Disassemble from 0x60AD (24749). Keep pressing return to move forward until around 0x60B8 (24760). From 0x60B8 the following can be seen:

The code from 0x60B8 (24760) through 0x60C0 (24768) checks to see if a key has been pressed. If not, the program jumps to address 0x6086 (24710) and the title sequence loops round. If a key has been pressed the game starts. Keep following the code through to address 0x60D3 (24787). Around 60C9 pattern of LD A, NN and LD (NNNN), A instructions emerges. These load A with a value of NN, and set the contents of NNNN to that value. Make a note of addresses and values set this way.

From 0x60D3 (24787) the game loads A with 03, then sets 0x7C78’s (31864) value to 03. Although the game starts with 4 passes the game documentation refers to 3 lives. There’s a bug adding an extra pass during game initialisation. The easiest way to figure out what’s going on is to play the game and compare the value at 0x7C78 to the number of passes, lose a pass and repeat until the game ends. If the pass values match throughout it’s a pass counter.

Exit the disassembler and press R to return to the game. Start the game and trigger the Multiface again. Make sure hexadecimal mode is active (light blue border). Enter Numerical mode and the address 7C78. The first byte value should match the number of “PASSES” shown in the game. Return to the game, lose a pass and repeat the check until the game is lost. If the value of 0x7C78 (31864) matched the number of passes left throughout it’s the pass counter.

Start a new game and trigger the Multiface. Enter numerical mode at address 7C78, enter alter mode with the A key and press enter. Enter 8 to change 0x7C78’s (31864) value to 8. Press space and the value will update on screen. Press space and R to return, then lose a pass. The player is reborn with 7 passes. Any value from 0 to 255 will do, but a bug occurs if the pass counter is set to higher than 10.

The game stores pass values in address 0x7C78 (31864). The code that loses a pass should be structured similarly to the initialisation code seen earlier. Somewhere in memory is code that should look something like:

LD A, (7C78) ; Loads a register with the contents of 0x7C78.
DEC A        ; Decrements the value.
LD (7C78), A ; Loads 0x7C78 with the contents of the register.

In RAM, a reference to address 0x7C78 (31864) would be stored as 787C. Searching through RAM, two closely located hits for 787C indicate behaviour like the code shown above. Open the main menu and press the F key. Enter 787C. Press return to go through the list, noting down each address. The first entry 60D6 (24790) matches the 3 pass initialisation code found earlier. The following 9 addresses should appear:

1. 0x60D6 (24790)
2. 0x619C (24988)
3. 0x65EC (26092)
4. 0x65F0 (26096)
5. 0x65F9 (27105)
6. 0x673B (26427)
7. 0x673F (26431)
8. 0x674F (26447)
9. 0x67D6 (26582)

Each address listed is a reference to 0x7C78 (31864) stored in RAM. Z80 instructions using 16-bit addresses (like the ones above) should be 3 bytes long. To get an accurate disassembly, subtract 1 from each address. For example, to check address 0x60D6 (24790), start the disassembly at 0x60D5 (24789).

Disassemble each address minus 1. Press G to go back and work forward with enter until a CALL, jump of some sort or DEC instruction is found.

The code starting at 0x673A (26426) matches the expected pattern perfectly. The code at 0x67D5 (26581) jumps to 0x67E9 (26601) if the number of passes matches zero. The code at 0x67E9 handles the end of demo mode fade and jumps to the title, signifying the end of the game.

The DEC A instruction (0x3D) at 0x673D (26429) is one byte long. If it was swapped for an INC A (0x3C) instruction, Horace would be given a pass whenever caught by a guard. A NOP (0x00), or “No-Operation” instruction leaves the pass value unchanged.

Disassemble 0x673D (26429) and enter alter mode. Change the value of 0x673D from 3D (DEC A) to 0 (NOP) and press return. Press space to return to the menu and R to return to the game with a well-earned infinite lives. Liberty for Horace!

The final Multiface poke is:

POKE 26429,0

It’s too late for an original Your Sinclair Tip Shop submission. There’s still more to explore in the game's code. See if you can find and fix the 4 pass maze initialisation bug. Can you modify the maze structure? Why not try some of the other suggested pokes to see if there’s anything that might grab your interest.

Things You May Have Missed

If you liked the Hungry Horace disassembly there’s more thanks to Skoolkit.

You might think Spectrum games are things of the past but you’d be dead wrong. Modern ZX Retro Gaming’s video above shows off some of the best games from the first half of this year. Titles like Escape from M.O.N.J.A.S., Travel Through Time Vol 1: Northern Lights and The Dark, Redux push the Speccy to it’s limits. There’s a constant stream of excellent game and demo releases.

Mark Pesce writes on The Joy of Disobeying Your Phone. Gordon Brander writes about notes as conversations across time. Andy Matuschak writes about how books fail at helping people learn.

Molly Drake recorded songs, released only after her death. It feels weird considering Nick Drake’s mum an outsider musician. I Remember is a beautiful piece to finish this issue with, recorded at home in the 1950s by her husband Rodney. I thought I’d end with this quote by Ray Bradbury:

We are cups, constantly and quietly being filled. The trick is, knowing how to tip ourselves over and let the beautiful stuff out.

If you’ve enjoyed reading Tales From The Dork Web, please share an issue with someone else who might enjoy it. If you haven’t subscribed, you can do so below. I’ll be back next month with more Tales From The Dork Web.