I’ve made good progress with my RetroChallenge to get an NES controller working on my RC2014 computer.
I wanted to be able to play a game with the controller. I had hoped to write my own, but with only a few days left, I didn’t think this was practical. Instead, I wondered about hacking an existing game.
While it’s not an RC2014, the Sinclair ZX Spectrum is also based on the Z80 processor. Colin Piggot makes a ZX Spectrum Bus Interface that allows RC2014 cards to be plugged into a Speccy.
As the RC2014 NES controller module can now be plugged into a Speccy, could I adapt one of my favourite games to work with it? Could Manic Miner be playable with an NES controller?

The first step was to try to disassemble Manic Miner to see how the existing Kempston joystick support worked. I was hoping it would be simple to just wire in NES controller support there.
I made a start on this myself, before thinking that for such an iconic game, someone must have done this before. A quick search revealed an excellent commented disassembly of Manic Miner by Richard Dymond.
Faking a Kempston joystick
The Kempston joystick interface works by using a Z80 in operation on port 31. I would need to fake this by calling a routine to read the NES controller and return data in the Kempston format.
The Kempston joysticker interface returns the following in a single byte.
| Bit | Value |
| 0 | Right |
| 1 | Left |
| 2 | Down |
| 3 | Up |
| 4 | Fire |
| 5 | N/A |
| 6 | N/A |
| 7 | N/A |
So, if the Fire button is being pressed, bit 4 will be 1.
I can modify the get_buttons routine I previously wrote to read the NES controller and return the data in this format.
To do this, before the pop bc instruction, I can add the following code.
; build the kempston joystick byte in register a
xor a ; clear a
.test_fire:
bit NES_A_BTN, c
jr nz, .test_up
set 4, a
.test_up:
bit NES_UP_BTN, c
jr nz, .test_down
set 3, a
.test_down:
bit NES_DOWN_BTN, c
jr nz, .test_left
set 2, a
.test_left:
bit NES_LEFT_BTN, c
jr nz, .test_right
set 1, a
.test_right:
bit NES_RIGHT_BTN, c
jr nz, .done
set 0, a
.done:
pop bc ; restore bc registers
Here I am clearing the a register so all bits have a default value of 0. Next, I am testing the bits in the byte I built to store the values from the NES controller. If the bits are set in the NES byte, then I set the relevant bit in the Kempston byte.
Hacking Manic Miner
My original idea was to load Manic Miner into a Spectrum, then use a Multiface to poke my changes into memory. Although this would work, I decided it would take too long and would be harder to save my work.
Instead, I downloaded a clean .tap file of Manic Miner from World of Spectrum and loaded this into the ZEsarUX emulator. ZEsarUX will let me poke my changes into the game and save the results as an SNA file. I can use the SNA file on a DivMMC connected to my Spectrum to load the game. This means I can make sure the game is still working before saving my changes.
I needed to find a place to store my get_buttons routine in Manic Miner. Upon examining the disassembly, remnants of the source code from when Matthew Smith was writing the game are visible at 0x934c. This space is unused in the game, so it’s safe for me to put my code there.
; A simple button reading program for the RC2014 Z80 computer running SCM
; This will pretend to be a Kempston joystick interface, and return bits like that interface.
; Robert Price - 24th October 2025
ORG $934c
; The Z80 port address to use for the controller interface.
NES_PORT EQU $01
; The bit masks for the controller interface lines.
CLOCK EQU $01
LATCH EQU $02
DATA EQU $01
NES_A_BTN EQU $00
NES_UP_BTN EQU $04
NES_DOWN_BTN EQU $05
NES_LEFT_BTN EQU $06
NES_RIGHT_BTN EQU $07
; subroutine to read the NES controller button states.
; returns the button states in register a.
; all other registers are preserved.
get_buttons:
push bc ; save bc registers
; pulse the LATCH line low to high and back to low again.
xor a ; set a to 0
out (NES_PORT), a
ld a, LATCH
out (NES_PORT), a
xor a ; set a to 0
out (NES_PORT), a
; setup the loop counter to read 8 buttons.
ld b, 8 ; 8 buttons to read. This will be decremented to 0.
; now we rebuild the byte into register c. As we are setting all 8 bits,
; we don't need to worry about clearing c first.
.loop:
; read the controller button states
in a, (NES_PORT) ; read the DATA line
; move the DATA bit into register c to rebuild the send byte
srl a ; shift right to get the DATA bit into carry
rr c ; rotate carry into bit 7 of c
; pulse the CLOCK line to read the next button.
ld a, CLOCK
out (NES_PORT), a
xor a ; set a to 0
out (NES_PORT), a
; loop 8 times to read all buttons.
djnz .loop
; build the kempston joystick byte in register a
xor a ; clear a
.test_fire:
bit NES_A_BTN, c
jr nz, .test_up
set 4, a
.test_up:
bit NES_UP_BTN, c
jr nz, .test_down
set 3, a
.test_down:
bit NES_DOWN_BTN, c
jr nz, .test_left
set 2, a
.test_left:
bit NES_LEFT_BTN, c
jr nz, .test_right
set 1, a
.test_right:
bit NES_RIGHT_BTN, c
jr nz, .done
set 0, a
.done:
pop bc ; restore bc registers
ret ; return to caller with button states in a
I compiled the code using the sjasmplus assembler on my Mac, and this gave me a 61-byte block of assembled code. Running this through hexdump gave me the following hex code to poke into memory.
9c4c c5 af d3 01 3e 02 d3 01 af d3 01 06 08 db 01 cb
9c5c 3f cb 19 3e 01 d3 01 af d3 01 10 f1 af cb 41 20
9c6c 02 cb e7 cb 61 20 02 cb df cb 69 20 02 cb d7 cb
9c7c 71 20 02 cb cf cb 79 20 02 cb c7 c1 c9
I used the Hexadecimal Editor in the Debug Menu of ZEsarUX to input this.
Now I needed to call this when the Kempston joystick was being called from the game.
The first place this is done is at address 0x8878. This code checks if the fire button is being pressed in demo mode.
8878 LD A,($8459) Pick up the Kempston joystick indicator from 8459
887B OR A Is there a joystick connected?
887C JR Z,$8884 Jump if not
887E IN A,($1F) Collect input from the joystick
In this case, I don’t need to check the joystick indicator, so all 4 instructions can be replaced with the following.
8878 CALL $934C
887B NOP
887C NOP
887D NOP
887E NOP
887F NOP
The NOP instructions tell the Z80 to do nothing. I am just using them to fill up the space that the 4 original instructions were using. The assembled code looks like this, and it was loaded using the hexadecimal editor.
8878 cd 4c 93 00 00 00 00 00
The next block of code looks for left or right being pressed at address 0x8C24
8C24 LD A,($8459) Collect the Kempston joystick indicator from 8459
8C27 OR A Is the joystick connected?
8C28 JR Z,$8C34 Jump if not
8C2A LD BC,$001F Collect input from the joystick
8C2D IN A,(C)
I can use the same code as before to drop in there, but I need an extra 3 NOP instructions to fill up the space. The replacement hex code looks like this.
8C24 cd 4c 93 00 00 00 00 00 00 00 00
There is similar code to check the fire button at address 0x8c6c. I can poke in the same hex code there.
8C6C cd 4c 93 00 00 00 00 00 00 00 00
Finally, there is a routine at 0x9337 that checks if the fire button is being pressed. This is a shorter version like the code at 0x8878, so I can poke in the same code there.
9337 cd 4c 93 00 00 00 00 00
I can now save a snapshot of the game. I do this using the Snapshot option in ZEsarUX and making sure I use a .SNA file extension. I called my file MMNES.SNA. It’s available to download for anyone following along.
Copying this to an SD card and placing it in the DivMMC allowed me to load the modified game on my Spectrum.

Conclusion
While I hadn’t planned to hack a Spectrum game as part of the RetroChallenge this year, it’s turned out to be a great diversion and learning experience. It’s still using the code and hardware developed on the RC2014.
The joypad works well, and although it is using a little more CPU time, it’s not noticeable when playing the game. It doesn’t have the nostalgia value of using the Spectrum’s rubber keys, but it is responsive and comfortable to use.
