--- title: "Spectrum Disassembler" id: 71084 type: "computer_media" slug: "spectrum-disassembler" url: "http://localhost/computer_media/spectrum-disassembler/" markdown_url: "http://localhost/computer_media/spectrum-disassembler.md" published_at: "2026-08-27T20:57:51+00:00" modified_at: "2026-08-27T20:59:12+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2026/08/Spectrum-Disassembler.png" alt: "Spectrum Disassembler screen" excerpt: "A full Z80 disassembler in BASIC decodes all opcode prefixes, resolves IX/IY indexing, and can follow jumps or relocate addresses — all from within memory." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 2068" slug: "ts2068" taxonomy: "post_tag" url: "http://localhost/tag/ts2068/" model: - name: "Timex/Sinclair 2068" slug: "ts-2068" taxonomy: "model" url: "http://localhost/model/ts-2068/" indiv: - name: "David Hawkins" slug: "david-hawkins" taxonomy: "indiv" url: "http://localhost/indiv/david-hawkins/" genre: - name: "Machine Language" slug: "machine-language" taxonomy: "genre" url: "http://localhost/type/machine-language/" - name: "Utility" slug: "utility" taxonomy: "genre" url: "http://localhost/type/utility/" media_type: "Program" programmers: - name: "David Hawkins" slug: "david-hawkins" taxonomy: "indiv" url: "http://localhost/indiv/david-hawkins/" download_url: "https://archive.org/download/timex-sinclair-software-archive/Spectrum%20Disassembler%20%281982%29%28Hawkins%2C%20David%29%28TS2068%29%28US%29%28Program%29.zip" mediadate: "1982" images: - url: "http://localhost/wp-content/uploads/2026/08/Spectrum-Disassembler.png" alt: "Spectrum Disassembler screen" media_type_tags: "Machine Language, Utility" --- # Spectrum Disassembler 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–4 - `b` = bits 5–3 (the “y” field), range 1–8 - `c` = bits 2–0 (the “z” field), range 1–8 - `d` = `INT(b/2)+1`, the register-pair index - `e` = `b - 2*d + 3`, the odd/even parity of `b` 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 — with `x` as 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 variable `a` used 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 the `i` flag to disambiguate, but relies on the argument substitution subroutine having been called. - Line 4074 uses a substring extraction from a literal string `"OUTIOUTDOTIROTDR"` keyed on `b`, covering the four block output/input instructions. The computation `4*b-3 TO 4*b` extracts 4-character mnemonics, which is valid for OUTI (b=1), OUTD (b=2), OTIR (b=3), OTDR (b=4). - The `NOT PI` expression evaluates to 0 (since PI is non-zero, NOT PI = 0), and is used as the `g` flag 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" ```