This program is a Z80 disassembler that decodes machine code bytes in memory and outputs human-readable Z80 assembly mnemonics. It handles the full Z80 instruction set including the CB, DD, ED, and FD prefix bytes, correctly resolving IX and IY indexed addressing. A lookup system using RESTORE and READ with computed DATA-line addresses decodes each opcode’s mnemonic, source, and destination fields from compact string tables. Optional features include byte-sequence searching, jump-following with optional address relocation, and output routing to screen, printer, or serial (TTY) streams via OPEN #3.
Program Structure
The program is organized into several functional regions:
- Lines 10–24: Byte-sequence locator — scans memory for a user-specified byte pattern.
- Lines 100–180: Opcode fetch and prefix handling — reads the next byte, classifies DD/FD/CB/ED prefixes.
- Lines 200–280: Opcode decoding — computes a DATA line address from the opcode fields and reads the mnemonic.
- Lines 300–730: Output formatting — wraps operands in parentheses, prints address, mnemonic, and optional raw bytes.
- Lines 800–997: Subroutine for argument substitution (
GOSub 900) — handles relative offsets (u), immediate bytes (v), 16-bit words (w), register-pair substitution (y), and address relocation. - Lines 1001–4078: DATA tables — all opcode mnemonic/operand/flag triples.
- Lines 4500–4560: Interactive menu — output routing, byte display toggle, locate, relocate, and jump-follow controls.
- Lines 4600–4630: Byte-sequence search configuration.
- Lines 4900–6000: Initialization — string tables for registers, conditions, ALU ops, rotation ops, and block instructions.
Opcode Decoding Technique
The heart of the disassembler is the computed RESTORE trick at line 200. The Z80 opcode byte q is decomposed into three octal fields:
a= bits 7–6 (the “x” field in standard Z80 notation), range 1–4b= bits 5–3 (the “y” field), range 1–8c= bits 2–0 (the “z” field), range 1–8d=INT(b/2)+1, the register-pair indexe=b - 2*d + 3, the odd/even parity ofb
These fields are combined into a DATA line number: k*1000 + a*100 + c*10 + e - 111, where k is 1 for unprefixed, 2 for CB-prefixed, and 3 for ED-prefixed instructions. The program then does RESTORE to that line and reads the mnemonic. If the DATA entry starts with ">", a secondary redirect reads two further values to construct a new RESTORE target, allowing irregular opcode encodings to be handled without a full table entry for every combination.
Argument Substitution (GOSub 900)
Operand placeholders in the DATA entries are single lowercase letters read from b$ and c$. The subroutine at line 900 substitutes them as follows:
| Code | Meaning |
|---|---|
u | Signed 8-bit relative offset (for JR, DJNZ) — computes absolute target from p |
v | Unsigned 8-bit immediate |
w | 16-bit little-endian word — also triggers address relocation if in range |
y | Current index register (HL, IX, or IY) |
↑ | Undefined/invalid opcode sentinel — triggers error output at line 850 |
The flag variable i is set to 1 whenever a y substitution occurs; this is used at line 280 to suppress DD/FD-prefixed instructions that do not actually reference the index register (falling back to the HL form).
String Tables
All register and condition-code strings are stored as fixed-width string arrays initialized at lines 5000–5080. Substrings are extracted by index arithmetic (2*z-1 TO 2*z or 3*z-2 TO 3*z, etc.):
r$: 8-character string for 8-bit registers B, C, D, E, H, L, (HL), A — withxas a placeholder for the indexed form.s$(z): 16-bit register pairs BC, DE, HL/IY/IX, SP.t$(z): Stack register pairs BC, DE, HL/IY/IX, AF.q$(z): Condition codes NZ, Z, NC, C, PO, PE, P, M.x$(z): ALU operations ADD, ADC, SUB, SBC, AND, XOR, OR, CP.w$(z): Rotation/shift operations RLC, RRC, RL, RR, SLA, SRA, undefined, SRL.v$(z): Miscellaneous accumulator operations RLCA, RRCA, RLA, RRA, DAA, CPL, SCF, CCF.y$(z): Block instruction suffixes for LDI/LDD/LDIR/LDDR and related.u$(z): Block instruction prefixes LD, CP, IN.p$: Interrupt mode values 0, ↑, 1, 2 (with invalid entries marked).o$: Used as a dummy second operand for ED-prefix instructions that have invalid variants.
Output and I/O Routing
All disassembly output goes to stream #3. At line 105, CLOSE #3 followed by OPEN #3,f$(1) sets the output channel. The menu at line 4500 allows switching f$ between "Scr/Pri/Tty", "Pri/Tty/Scr", and "Tty/Scr/Pri", effectively cycling the primary output target among screen, printer, and serial TTY. The FORMAT command at line 4512 sets the TTY baud rate to 300.
Line 700 formats each instruction as a fixed-width line: address left-padded in a 6-character field (l$(7 TO) for the mnemonic), followed by operands. When the byte-display flag s is set, raw decimal byte values are printed alongside, with a printable-character column using CHR$(PEEK z AND (g<16 OR g>23)) — the AND expression masks non-printable control character ranges to zero.
Line 730 includes POKE 23692,255 to reset the scroll counter (preventing the “scroll?” prompt) each time a line is printed.
Jump Following and Address Relocation
When jump is enabled (toggled via the menu), the program pauses at JP, JR, CALL, and RET instructions and asks the user whether to follow. An empty response causes the program counter p to be set to the branch target decoded from c$ or b$.
When shift is non-zero, every 16-bit word operand (w placeholder) that falls within the range [start, end] is adjusted by adding shift and the modified bytes are written back into memory via two POKE calls. The adjusted value is also appended to the disassembly output with a >= annotation. This is a relocation patching feature intended for moving code blocks.
Byte-Sequence Locator
Lines 10–24 implement a simple memory scanner. The user specifies up to b bytes to locate, stored in string h$. Line 20 walks address a forward comparing PEEK a against CODE h$ (the first byte). Line 22 then verifies the remaining bytes with a FOR loop. Matches are printed to stream #3. Pressing Space interrupts and returns to the menu. The INKEY$ check at line 20 inside the scanning loop doubles as a fast interrupt mechanism.
Error Handling and Invalid Opcodes
Invalid or undefined Z80 opcodes are represented in the DATA tables by the sentinel string "↑". When detected at line 240 or in the argument subroutine at line 945, the program jumps to line 850, which sets the mnemonic to n$ (a flashing inverse ? character defined at line 4990 using CHR$ 18 + CHR$ 1 + "?"), sounds a short beep, sets s=1 to enable byte display, and continues output.
Notable Bugs and Anomalies
- Line 200 uses
LET a=...for the opcode field, overwriting the variableaused in the byte locator (lines 10–24). These two features cannot be used simultaneously without state corruption, though in practice they are separate operational modes. - The
r$entry for register index 7 (the(HL)form) is"x", which is the same placeholder character used for DD/FD indexing. The logic at line 280 uses theiflag to disambiguate, but relies on the argument substitution subroutine having been called. - Line 4074 uses a substring extraction from a literal string
"OUTIOUTDOTIROTDR"keyed onb, covering the four block output/input instructions. The computation4*b-3 TO 4*bextracts 4-character mnemonics, which is valid for OUTI (b=1), OUTD (b=2), OTIR (b=3), OTDR (b=4). - The
NOT PIexpression evaluates to 0 (since PI is non-zero, NOT PI = 0), and is used as thegflag throughout the DATA tables to indicate “no parentheses wrapping,” making it a semantic zero constant rather than a meaningful flag value.
Source Code
1 REM © Aug 82 David Hawkins
10 CLS :GO TO 4900
20 IF INKEY$ <>" " THEN LET a=a+1:PRINT AT 21,0;a:IF PEEK a <> CODE h$ THEN GO TO 20
21 IF INKEY$=" " THEN GO TO 4500
22 FOR x=1 TO b-1:IF PEEK (a+x)= CODE h$(x+1) THEN NEXT x:PRINT #3;a
24 GO TO 20
100 INPUT " Start address ? ";p
105 CLOSE #3:OPEN #3,f$(1)
110 LET p1=p:LET q= PEEK p:LET p=p+1:LET k=1:LET i$="HL"
120 IF q=118 THEN LET a$="HALT":LET b$="":LET c$="":GO TO 700
130 IF q=203 OR q=237 THEN LET k=2+(q=237):LET q= PEEK p:LET p=p+1:GO TO 200
150 IF q=221 THEN LET i$="IX"
160 IF q=253 THEN LET i$="IY"
170 IF i$ <>"HL" AND PEEK p=118 THEN GO TO 850
180 IF i$ <>"HL" THEN LET q= PEEK p:LET p=p+1:IF q=203 THEN LET k=2:LET f= PEEK p:LET p=p+1:LET q= PEEK p:LET p=p+1
200 LET a=(INT (q/64))+1:LET b=(INT (q/8-a*8))+1:LET c=(q-b*8-a*64)+1:LET d= INT (b/2)+1:LET e=b-2*d+3:RESTORE k*1000+a*100+c*10+e-111:READ a$
230 IF a$=">" THEN READ l,m:RESTORE l+m:READ a$
240 IF a$(1)="↑" OR a$(LEN a$)="↑" THEN GO TO 850
250 READ b$,c$,g
260 LET i=0:IF CODE b$>90 THEN LET d$=b$(1):LET m=1:GO SUB 900:LET b$=d$
270 IF CODE c$>90 THEN LET d$=c$(1):LET m=2:GO SUB 900:LET c$=d$
280 IF i$ <>"HL" AND NOT i THEN GO TO 850
300 IF g=1 THEN LET b$="("+b$+")"
310 IF g=2 THEN LET c$="("+c$+")"
700 DIM l$(11):LET l$= STR$ p1:LET l$(7 TO )=a$:PRINT #3;l$;b$;:IF c$ <>"" THEN PRINT #3;",";c$;" ";
720 IF s THEN FOR z=p1 TO p-1:LET g= PEEK z:PRINT #3;" "; TAB 21;g;" "; TAB 25; CHR$ (PEEK z AND (g<16 OR g>23));:NEXT z
730 PRINT #3:POKE 23692,255
740 IF INKEY$=" " THEN GO TO 4500
741 IF jump AND NOT shift AND (a$="JP" OR a$="JR" OR a$="CALL" OR a$+b$="RET") THEN INPUT "Jump? ";a$:IF a$="" THEN PRINT #3;">>>>":LET d$="0"+c$:LET p= VAL d$:IF c$="" THEN LET p= VAL b$
742 IF CODE a$= CODE "STOP " THEN GO TO 4500
800 GO TO 110
850 LET a$=n$:LET b$="":LET c$="":BEEP .1,0:LET s=1:GO TO 700
900 REM edit arguments
920 IF d$="u" THEN LET q= PEEK p:LET p=p+1:LET d$= STR$ (p+q-256*(q>127)):RETURN
930 IF d$="v" THEN LET q= PEEK p:LET p=p+1:LET d$= STR$ q:RETURN
940 IF d$="w" THEN LET q= PEEK p:LET d$= STR$ (q+256* PEEK (p+1)):LET p=p+2:GO TO 995
945 IF d$="↑" THEN LET d$=n$:BEEP .1,10:LET s=1:RETURN
950 IF i$ <>"HL" THEN LET i=1
955 IF d$="y" THEN LET d$=i$:RETURN
960 LET g=m:IF i$="HL" THEN LET d$="HL":RETURN
970 IF k=1 THEN LET f= PEEK p:LET p=p+1
990 LET f=f-256*(f>127):LET z$="+" AND f >=0:LET d$=i$+z$+ STR$ f:RETURN
995 IF NOT shift THEN RETURN
996 IF VAL d$ >=start AND VAL d$ <=end THEN LET new= VAL d$+shift:POKE p-1, INT (new/256):POKE p-2,new-256* PEEK (p-1):LET d$=d$+">="+ STR$ new
997 RETURN
1001 DATA ">",4000,b
1010 DATA "LD",s$(d),"w", NOT PI
1011 DATA "ADD","y",s$(d), NOT PI
1021 DATA ">",4010,b
1030 DATA "INC",s$(d),"", NOT PI
1031 DATA "DEC",s$(d),"", NOT PI
1041 DATA "INC",r$(b),"", NOT PI
1051 DATA "DEC",r$(b),"", NOT PI
1061 DATA "LD",r$(b),"v", NOT PI
1071 DATA v$(b),"","", NOT PI
1171 DATA "LD",r$(b),r$(c), NOT PI
1271 DATA ">",4050,b-2*(b=4)
1301 DATA "RET",q$(b),"", NOT PI
1310 DATA "POP",t$(d),"", NOT PI
1311 DATA ">",4020,d
1321 DATA "JP",q$(b),"w", NOT PI
1331 DATA ">",4030,b
1341 DATA "CALL",q$(b),"w", NOT PI
1350 DATA "PUSH",t$(d),"", NOT PI
1351 DATA "CALL","w","", NOT PI
1361 DATA ">",4060,b-2*(b=4)
1371 DATA "RST", STR$ (b*8-8),"", NOT PI
2071 DATA w$(b),r$(c),"", NOT PI
2171 DATA "BIT", STR$ (b-1),r$(c), NOT PI
2271 DATA "RES", STR$ (b-1),r$(c), NOT PI
2371 DATA "SET", STR$ (b-1),r$(c), NOT PI
3071 DATA "↑"
3101 DATA "IN",r$(b),"C",2
3111 DATA "OUT","C",r$(b),1
3120 DATA "SBC","HL",s$(d), NOT PI
3121 DATA "ADC","HL",s$(d), NOT PI
3130 DATA "LD","w",s$(d),1
3131 DATA "LD",s$(d),"w",2
3140 DATA "NEG",o$(d),"", NOT PI
3141 DATA "↑"
3150 DATA "RETN",o$(d),"", NOT PI
3151 DATA "RETI",o$(d),"", NOT PI
3161 DATA "IM",p$(b),"", NOT PI
3171 DATA ">",4040,b
3231 DATA ">",4070,c
3371 DATA "↑"
4001 DATA "NOP","","", NOT PI
4002 DATA "EX","AF","AF'", NOT PI
4003 DATA "DJNZ","u","", NOT PI
4004 DATA "JR","u","", NOT PI
4008 DATA "JR",q$(b-4),"u", NOT PI
4011 DATA "LD","BC","A",1
4012 DATA "LD","A","BC",2
4013 DATA "LD","DE","A",1
4014 DATA "LD","A","DE",2
4015 DATA "LD","w","y",1
4016 DATA "LD","y","w",2
4017 DATA "LD","w","A",1
4018 DATA "LD","A","w",2
4021 DATA "RET","","", NOT PI
4022 DATA "EXX","","", NOT PI
4023 DATA "JP","y","",1
4024 DATA "LD","SP","y", NOT PI
4028 DATA "↑"
4031 DATA "JP","w","", NOT PI
4032 DATA "↑"
4033 DATA "OUT","v","A",1
4034 DATA "IN","A","v",2
4035 DATA "EX","SP","y",1
4036 DATA "EX","DE","HL", NOT PI
4037 DATA "DI","","", NOT PI
4038 DATA "EI","","", NOT PI
4041 DATA "LD","I","A", NOT PI
4042 DATA "LD","R","A", NOT PI
4043 DATA "LD","A","I", NOT PI
4044 DATA "LD","A","R", NOT PI
4045 DATA "RRD","","", NOT PI
4046 DATA "RLD","","", NOT PI
4048 DATA "↑"
4052 DATA x$(b),"A",r$(c), NOT PI
4058 DATA x$(b),r$(c),"", NOT PI
4062 DATA x$(b),"A","v", NOT PI
4068 DATA x$(b),"v","", NOT PI
4073 DATA u$(c)+y$(b),"","", NOT PI
4074 DATA "OUTIOUTDOTIROTDR"(4*b-3 TO 4*b),"","", NOT PI
4078 DATA "↑"
4500 INPUT AT 0,0;(g$),(f$)'"Address Cont Locate R"; FLASH NOT NOT shift;"elocate "; FLASH 0;"J"; FLASH NOT NOT jump;"ump"; FLASH 0;" "; LINE a$
4510 IF a$="P" THEN LET f$="Pri/Tty/Scr"
4512 IF a$="T" THEN LET f$="Tty/Scr/Pri":FORMAT "T",300
4515 IF a$="S" THEN LET f$="Scr/Pri/Tty"
4520 IF a$="A" THEN PRINT :GO TO 100
4530 IF a$="N" THEN LET s=0:LET g$="Nobytes/Bytes"
4535 IF a$="B" THEN LET s=1:LET g$="Bytes/Nobytes"
4540 IF a$="L" THEN GO TO 4600
4550 IF a$="C" THEN GO TO 105
4552 IF a$="R" THEN INPUT "Shift ";shift
4553 IF a$="R" AND shift THEN INPUT "Start ";start," Finish ";end:LET jump=0
4555 IF a$="J" THEN LET jump= NOT jump:LET shift=shift*(NOT jump)
4560 GO TO 4500
4600 OPEN #3,f$(1)
4610 INPUT "No of bytes to locate ";b:IF b>0 THEN DIM h$(b):FOR x=1 TO b:INPUT "Byte";(x);" ";y:LET y=y-256* INT (y/256):PRINT #3;x;" ";y:LET h$(x)= CHR$ y:NEXT x:INPUT "OK ?";a$:IF a$ <>"Y" THEN GO TO 4600
4620 IF b <=0 THEN GO TO 4500
4630 INPUT "Address to start ";a:LET a=a-1:PRINT #3:GO TO 20
4900 REM Initialize
4901 PRINT AT 5,5;"SPECTRUM DISASSEMBLER"; AT 12,7;"© David Hawkins":POKE 23658,8
4905 LET END=0:LET start=end:LET shift=end:LET jump=end
4910 LET p=0:LET s=p:LET f$="Scr/Pri/Tty":LET g$="Nobytes/Bytes"
4990 LET n$= CHR$ 18+ CHR$ 1+"?"
4995 LET o$=" ↑↑↑"
5000 LET r$="BCDEHLxA"
5002 LET p$="0↑12↑↑↑↑"
5005 DIM s$(4,2):DIM t$(4,2):DIM u$(4,2)
5010 FOR z=1 TO 4
5015 LET s$(z)="BCDEy SP"(2*z-1 TO 2*z)
5020 LET t$(z)="BCDEy AF"(2*z-1 TO 2*z)
5030 LET u$(z)="LDCPIN↑↑"(2*z-1 TO 2*z)
5040 NEXT z
5050 DIM q$(8,2):DIM x$(8,3):DIM v$(8,4):DIM w$(8,3):DIM y$(8,2)
5055 FOR z=1 TO 8
5060 LET q$(z)="NZZ NCC POPEP M "(2*z-1 TO 2*z)
5065 LET x$(z)="ADDADCSUBSBCANDXOROR CP "(3*z-2 TO 3*z)
5070 LET w$(z)="RLCRRCRL RR SLASRA↑↑↑SRL"(3*z-2 TO 3*z)
5075 LET v$(z)="RLCARRCARLA RRA DAA CPL SCF CCF "(4*z-3 TO 4*z)
5076 LET y$(z)="↑↑↑↑↑↑↑↑I D IRDR"(2*z-1 TO 2*z)
5080 NEXT z
6000 GO TO 4500
9998 SAVE "Disass" LINE 1:VERIFY "Disass"Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
