This program is a Z80 disassembler written entirely in BASIC, capable of decoding standard, CB-prefix, ED-prefix, DD-prefix (IX), and FD-prefix (IY) instruction sets. It presents a menu-driven interface allowing the user to set start and end addresses, then display disassembled mnemonics, ASCII dumps, or numeric hex/decimal dumps of any memory range. The disassembler uses a lookup table array (O$) holding encoded opcode strings with mode bytes, and processes indexed addressing, indirect operands, and relative branch offsets at runtime. Output can be directed to both the screen and a printer via the LP flag, and addresses and byte values can be toggled between hexadecimal and decimal display using a base-switch command.
Program Analysis
Program Structure
The program is organized into clearly labeled functional blocks, with lines 5–195 handling constants and variable initialization, lines 200–420 implementing the main menu loop, lines 500–564 providing address input and hex/decimal conversion utilities, lines 595–654 managing the disassembly run loop, and lines 1000–1720 decoding Z80 opcodes. Lines 2000–3920 handle mnemonic text formatting by mode, lines 4000–4130 build the output line, lines 5000–5030 format byte and word values, and lines 7000–7565 provide ASCII and numeric memory dump modes.
Lines 80–120 use a distinctive idiom: they assign subroutine line numbers to variables with descriptive names (e.g., LET GET INSTRUCTION=2000), then call them with GO SUB GET INSTRUCTION. This is valid BASIC but relies on variable names containing spaces being accepted as multi-word identifiers, effectively creating named subroutine pointers.
Opcode Dispatch and Prefix Handling
The disassembly entry point is at line 1000. The first byte (I0) is read from LOC and dispatched on the four Z80 prefix bytes:
CBprefix (line 1010 → 1200): adds 256 to the second byte to form an index into the opcode table.EDprefix (line 1020 → 1300): maps valid ED opcodes into a range above 416 in the table; invalid codes are redirected to the undefined instruction entry at index 190.DDprefix (line 1040 → 1500): setsN$="IX"and falls through to indexed handling.FDprefix (line 1050 → 1520): setsN$="IY"and falls through to the same indexed handler.
Non-prefixed opcodes fall through to line 1060 and look up the instruction directly via GO SUB GET INSTRUCTION at line 2000. The opcode table is referenced as O$(I0+1), suggesting a string array indexed from 1.
Mnemonic Table and Mode Encoding
At line 2000, the mnemonic string for the current opcode is fetched from O$(I0+1). Trailing spaces are stripped (lines 2003–2003). The first character is interpreted as a mode digit (1–9); if present, it is consumed and the remainder is split into mnemonic (A$) and operand (B$) fields at the first space. A dispatch table jump is then performed:
| Mode | Target line | Meaning |
|---|---|---|
| 0 (none) | 3000 | No operands; NBYTES=1 |
| 1 | 3100 | Register from F$ array indexed by low 3 bits of opcode |
| 2 | 3200 | Immediate byte (NBYTES=2) |
| 3 | 3300 | Immediate word (NBYTES=3) |
| 4 | 3400 | Relative branch offset (NBYTES=2), computing absolute address |
| 5 | 3500 | Indirect byte address operand (NBYTES=2) |
| 6 | 3600 | Indirect word address operand (NBYTES=3) |
| 7 | 3700 | Indirect byte destination (NBYTES=2) |
| 8 | 3800 | Indirect word destination (NBYTES=3) |
| 9 | 3900 | Undefined / invalid (M$="?") |
The dispatch is implemented as GO TO 3000+MODE*100, a compact computed GO TO idiom common in BASIC disassemblers and assemblers.
Index Register Substitution (CHECK INDEX)
The subroutine at line 1800 scans the mnemonic string M$ for placeholder characters: # marks where N$ (IX or IY) should be substituted, and * marks where the indexed indirect operand S$ (e.g., (IX+05H)) should be substituted. The flags INDEX and INDIRECT are set accordingly and used to adjust NBYTES for the displacement byte in lines 1620–1625 and 1700.
Output Line Construction (MAKE TEXT)
Lines 4000–4130 build the output string L$. The current address is formatted, followed by up to four raw bytes (hex or decimal depending on DEC), padded to a fixed width, then the mnemonic string M$. LOC is advanced by NBYTES at line 4120.
Hex and Decimal Conversion
Two conversion routines handle numeric display. BYTE VALUE at line 5000 formats an 8-bit value: in decimal mode it uses STR$ C; in hex it extracts nibbles using H$ (the hex digit string "0123456789ABCDEF") via substring slicing with +.5 rounding for integer indexing. WORD VALUE at line 5020 does the same for 16-bit values, processing the high and low bytes separately.
Hex input is handled in lines 549–555 with a manual parser that handles both upper and lowercase hex digits and converts using the ASCII offset trick (X-7 for letters above '9'). The DEF FN H at line 20 provides a similar single-character hex digit value function, though its use within the main body is not explicit in this listing — it may serve lookup purposes in the opcode table initialization code (not shown).
Display and Scrolling Control
The run loop at lines 596–620 uses POKE 23560,0 to clear the scroll counter (system variable SCRCT) before printing, and POKE 23692,255 to suppress the “scroll?” prompt during output. Line 610 polls PEEK 23560, waiting for it to become 32 (space = user pressed Space to scroll) or 13 (Enter = return to menu). This provides a manual paging mechanism without any INPUT statement.
Dump Modes
The ASCII dump at line 7000 reads 16 bytes per line, masking the high bit (I0=I0-128 if >127), replacing control characters and pipe/tilde characters with ., and printing as a string. The numeric dump at lines 7500–7565 reads 8 bytes per line in hex mode or 5 bytes in decimal mode, with fixed-width formatting using padding loops.
Notable Techniques and Anomalies
- The use of multi-word variable names as subroutine address holders (e.g.,
GET INSTRUCTION,MAKE TEXT) is unusual and makes the code self-documenting but relies on the interpreter treating spaces within variable names as part of the identifier. - The
GO TO 3000+MODE*100computed branch is an efficient dispatch that avoids a chain of IF statements for nine modes. - Line 3130 special-cases opcode 118 (0x76 = HALT), which shares the mode-1 register dispatch with LD (HL),r instructions but must output “HALT” instead of a register load mnemonic.
- The relative branch calculation at line 3420 uses
LOC-254+I1; sinceLOCat that point still points to the instruction start and NBYTES=2, the effective address for a relative jump isLOC+2+(signed I1). The expressionLOC-254+I1is equivalent when I1 is treated as unsigned (0–255), with line 3430 adding 256 if I1<128 to handle the signed interpretation — this is a correct but slightly unconventional way to compute the branch target. - Lines 800–806 contain a standalone loop that LPRINTs the entire opcode table; this diagnostic utility is only reachable by direct GO TO and is not connected to the menu system.
- The printer toggle and base toggle both loop back to line 382 rather than 400, immediately refreshing the status display without redrawing the full menu.
Content
Source Code
5 REM DISASSEMBLER
6 REM DO NOT RUN. GO TO 1.
20 DEF FN H(H$)=CODE H$-48-7*(H$>"@")
50 BORDER 1: PAPER 1: INK 5
55 LET W$="": LET FROM=0: LET END=65535
60 LET LP=0
70 LET DEC=0
80 LET CHECK INDEX=1800
90 LET GET INSTRUCTION=2000
100 LET MAKE TEXT=4000
110 LET BYTE VALUE=5000
120 LET WORD VALUE=5020
195 PRINT '
200 LET H$="0123456789ABCDEF"
300 CLS : PRINT TAB 5;" DISASSEMBLER COMMANDS "
310 PRINT '"Q: QUIT"''"F: FROM "''"T: TO"
320 PRINT '"D: DISASSEMBLE "
330 PRINT '"N: NUMERIC DUMP"
340 PRINT '"A: ASCII DUMP"
350 PRINT '"P: PRINTER SWITCH -NOW "
360 PRINT '"B: BASE SWITCH -NOW"
370 PRINT ''"STACK END = ";PEEK 23653+256*PEEK 23654
380 LET d=from: GO SUB 560: LET g$=k$: LET d=end: GO SUB 560: LET t$=k$
381 PRINT AT 4,8;g$;"H (";from;")";TAB 30;AT 6,8;t$;"H (";end;")";TAB 30;
382 PRINT AT 14,23;"ON " AND LP;"OFF" AND NOT lp
383 PRINT AT 16,23;"DEC" AND dec;"HEX" AND NOT dec
400 PRINT #1;AT 1,0;"COMMAND: "; FLASH 1;"C"
401 IF W$=INKEY$ THEN GO TO 401
402 LET W$=INKEY$: IF W$="" THEN GO TO 402
403 BEEP .005,25: LET C$=w$: IF C$>"`" AND C$<"{" THEN LET C$=CHR$ (CODE C$-32)
404 IF C$="Q" THEN PRINT AT 21,0;"DO NOT RUN. TO RETURN, GO TO 200": STOP : GO TO 1
406 IF C$="F" THEN GO TO 500
408 IF C$="T" THEN GO TO 520
410 IF C$="D" THEN LET SUB=1000: GO TO 595
412 IF C$="A" THEN LET SUB=7000: GO TO 595
414 IF C$="N" THEN LET SUB=7500: GO TO 595
416 IF C$="B" THEN LET DEC=NOT DEC: GO TO 382
418 IF c$="P" THEN LET lp=NOT lp: GO TO 382
420 GO TO 400
500 INPUT "FROM: "; LINE K$: GO SUB 540: IF K$="" THEN GO TO 400
502 LET FROM=D: GO TO 380
520 INPUT "TO: "; LINE k$: GO SUB 540: IF K$="" THEN GO TO 400
522 LET END=D: GO TO 380
540 IF k$="" THEN RETURN
541 IF k$(LEN k$)="h" OR k$(LEN k$)="H" THEN LET k$=k$( TO LEN k$-1): GO SUB 550: RETURN
542 FOR i=1 TO LEN k$: IF k$(i)<"0" OR k$(i)>"9" THEN LET k$="": RETURN
543 LET d=VAL k$: IF d<0 OR d>65535 THEN LET k$="": RETURN
544 RETURN
549 REM HEX (K$) TO DEC(D)
550 LET D=0: FOR I=1 TO LEN K$: LET X=CODE K$(I): IF X<49 THEN GO TO 555
551 IF X>90 THEN LET X=X-32
552 IF X>57 THEN LET X=X-7
553 LET X=X-48: IF X>15 THEN LET K$="": RETURN
554 LET D=D+X*16^(LEN K$-I)
555 NEXT I: RETURN
559 REM DEC TO HEX
560 LET k$="": LET z=d
561 LET x=(48+z-INT (z/16)*16): IF x>57 THEN LET x=x+7
562 LET k$=CHR$ x+k$: LET z=INT (z/16): IF z>0 THEN GO TO 561
563 IF LEN k$<4 THEN LET k$="0"+k$: GO TO 563
564 RETURN
595 IF SUB=0 OR LOC>65534 THEN GO TO 400
596 CLS : LET LOC=FROM: POKE 23560,0
600 GO SUB SUB: POKE 23692,255: PRINT L$: IF LP THEN LPRINT L$
605 IF LOC>END THEN PRINT "**END**": POKE 23560,32: GO TO 610
610 IF PEEK 23560=32 THEN GO TO 610
615 IF PEEK 23560<>13 THEN GO TO 600
620 GO TO 200
650 CLS : PRINT AT 10,0;"TO RETURN TO DISASSEMBLER,"''"DO NOT RUN, BUT GO TO 50"
652 STOP
654 GO TO 50
800 FOR I=1 TO 608
802 LPRINT I;" ";O$(I),
804 NEXT I
806 STOP
1000 LET I0=PEEK LOC
1010 IF I0=203 THEN GO TO 1200
1020 IF I0=237 THEN GO TO 1300
1040 IF I0=221 THEN GO TO 1500
1050 IF I0=253 THEN GO TO 1520
1060 LET I1=PEEK (LOC+1)
1070 LET I2=PEEK (LOC+2)
1080 GO SUB GET INSTRUCTION
1090 LET N$="HL": LET S$="(HL)"
1100 GO SUB CHECK INDEX: GO SUB MAKE TEXT
1110 LET L$=L$+M$
1120 RETURN
1200 LET I0=PEEK (LOC+1)+256
1210 GO SUB GET INSTRUCTION
1220 IF M$="?" THEN GO SUB MAKE TEXT: GO TO 1110
1230 LET NBYTES=2
1240 GO TO 1090
1300 LET I0=PEEK (LOC+1)
1310 IF I0<64 OR (I0>127 AND I0<160) OR I0>191 THEN LET I0=190
1315 IF I0<128 THEN LET I0=I0+32
1320 LET I0=I0+416
1330 LET I1=PEEK (LOC+2): LET I2=PEEK (LOC+3)
1340 GO SUB GET INSTRUCTION
1350 IF M$="?" THEN GO SUB MAKE TEXT: GO TO 1110
1360 LET NBYTES=NBYTES+1
1370 GO TO 1090
1500 LET N$="IX"
1510 GO TO 1530
1520 LET N$="IY"
1530 LET C=PEEK (LOC+2)
1540 IF C=0 THEN LET S$="("+N$+")"
1550 IF C>0 AND C<128 THEN GO SUB BYTE VALUE: LET S$="("+N$+"+"+C$+")"
1555 IF C>127 THEN LET C=256-C: GO SUB BYTE VALUE: LET S$="("+N$+"-"+C$+")"
1560 IF PEEK (LOC+1)=203 THEN GO TO 1660
1570 LET I0=PEEK (LOC+1): LET I1=PEEK (LOC+2): LET I2=PEEK (LOC+3)
1580 IF I0=54 THEN LET I2=0: LET I1=PEEK (LOC+3)
1590 GO SUB GET INSTRUCTION
1600 LET INDEX=0: LET INDIRECT=0
1610 IF M$<>"?" THEN GO SUB CHECK INDEX
1620 LET NBYTES=NBYTES+INDIRECT+INDEX
1625 IF INDEX=0 THEN LET NBYTES=NBYTES+INDIRECT
1630 GO SUB MAKE TEXT
1640 GO TO 1110
1660 LET I0=PEEK (LOC+3)+256
1670 GO SUB GET INSTRUCTION
1680 LET INDEX=0: LET INDIRECT=0
1690 IF M$<>"?" THEN GO SUB CHECK INDEX
1700 LET NBYTES=NBYTES+3*INDIRECT
1710 GO SUB MAKE TEXT
1720 GO TO 1110
1800 LET INDIRECT=0: LET INDEX=0
1805 LET I=5
1810 LET I=I+1: IF I>LEN M$ THEN RETURN
1820 LET R$=M$(I TO I): IF R$<>"#" AND R$<>"*" THEN GO TO 1810
1830 IF R$="*" THEN GO TO 1880
1840 LET INDEX=1
1850 LET M$=M$(1 TO I-1)+N$+M$(I+1 TO LEN (M$))
1860 GO TO 1805
1880 LET INDIRECT=1
1890 LET M$=M$(1 TO I-1)+S$+M$(I+1 TO LEN (M$))
1900 GO TO 1805
2000 LET I$=O$(I0+1)
2003 IF I$(LEN I$)=" " THEN LET I$=I$(1 TO LEN I$-1): GO TO 2003
2005 LET MODE=CODE I$-48
2010 IF MODE<1 OR MODE>9 THEN LET MODE=0: GO TO 2020
2015 LET I$=I$(2 TO LEN I$)
2020 FOR I=1 TO LEN I$: IF I$(I TO I)=" " THEN GO TO 2045
2025 NEXT I
2030 LET A$=I$+Z$(1 TO 5-LEN I$)
2035 LET B$=""
2040 GO TO 2055
2045 LET A$=I$(1 TO I)+Z$(1 TO 5-I)
2050 LET B$=I$(I+1 TO LEN I$)
2055 GO TO 3000+MODE*100
3000 LET NBYTES=1
3010 LET M$=A$+B$
3020 RETURN
3100 LET NBYTES=1
3110 IF LEN (B$)<>0 THEN LET B$=B$+","
3115 LET K=I0-INT (I0/8)*8+1
3120 LET M$=A$+B$+F$(K)
3130 IF I0=118 THEN LET M$="HALT"
3140 RETURN
3200 LET NBYTES=2
3210 IF LEN (B$)<>0 THEN LET B$=B$+","
3220 LET C=I1
3230 GO SUB BYTE VALUE
3240 LET M$=A$+B$+C$
3250 RETURN
3300 LET NBYTES=3
3310 IF LEN (B$)<>0 THEN LET B$=B$+","
3320 LET C=256*I2+I1
3330 GO SUB WORD VALUE
3340 LET M$=A$+B$+C$
3350 RETURN
3400 LET NBYTES=2
3410 IF LEN (B$)<>0 THEN LET B$=B$+","
3420 LET C=LOC-254+I1
3430 IF I1<128 THEN LET C=C+256
3440 GO TO 3330
3500 LET NBYTES=2
3510 LET C=I1
3520 GO SUB BYTE VALUE
3530 GO TO 3630
3600 LET NBYTES=3
3610 LET C=256*I2+I1
3620 GO SUB WORD VALUE
3630 IF LEN (B$)<>0 THEN LET B$=B$+","
3640 LET M$=A$+B$+"("+C$+")"
3650 RETURN
3700 LET NBYTES=2
3710 LET C=I1
3720 GO SUB BYTE VALUE
3730 GO TO 3830
3800 LET NBYTES=3
3810 LET C=256*I2+I1
3820 GO SUB WORD VALUE
3830 IF LEN B$<>0 THEN LET B$=","+B$
3840 LET M$=A$+"("+C$+")"+B$
3850 RETURN
3900 LET NBYTES=1
3910 LET M$="?"
3920 RETURN
4000 LET C=LOC: GO SUB WORD VALUE
4010 IF DEC THEN LET L$=C$+" ": GO TO 4030
4020 LET L$=C$(1 TO 4)+" "
4030 LET D$="": FOR T=LOC TO LOC+NBYTES-1
4070 LET C=PEEK T
4075 IF DEC THEN LET DEC=0: GO SUB BYTE VALUE: LET DEC=1: GO TO 4090
4080 GO SUB BYTE VALUE
4090 LET D$=D$+C$(1 TO 2)
4100 NEXT T
4110 LET L$=L$+D$+Z$(1 TO 2*(4-NBYTES))+" "
4120 LET LOC=LOC+NBYTES
4130 RETURN
5000 IF DEC THEN LET C$=STR$ C: RETURN
5010 LET C$=H$(C/16+.5 TO C/16+.5): LET C=C-INT (C/16)*16+.5: LET C$=C$+H$(C TO C)+"H": RETURN
5020 IF DEC THEN LET C$=STR$ C: RETURN
5025 LET CT=C/256: LET C$=H$(CT/16+.5 TO CT/16+.5): LET CT=CT-INT (CT/16)*16+.5: LET C$=C$+H$(CT TO CT)
5030 LET CT=C-INT (C/256)*256: LET C$=C$+H$(CT/16+.5 TO CT/16+.5): LET CT=CT-INT (CT/16)*16+.5: LET C$=C$+H$(CT TO CT)+"H": RETURN
7000 LET C=LOC: GO SUB 5020: LET L$=C$+" ": FOR C=0 TO 15: IF C+LOC>65535 THEN LET I0=32: GO TO 7030
7010 LET I0=PEEK (C+LOC): IF I0>127 THEN LET I0=I0-128
7020 IF I0<32 OR I0=124 OR I0=126 THEN LET I0=46
7030 LET L$=L$+CHR$ I0
7040 NEXT C: LET LOC=LOC+16: RETURN
7500 IF DEC THEN GO TO 7550
7505 LET C=LOC: GO SUB WORD VALUE: LET L$=C$+" ": FOR I=0 TO 7: IF LOC+I>65535 THEN LET I0=0: GO TO 7520
7510 LET I0=PEEK (LOC+I)
7520 LET C=I0: GO SUB 5000: IF DEC THEN LET L$=L$+" "+C$
7525 IF DEC=0 THEN LET L$=L$+" "+C$(1 TO 2)
7530 NEXT I: LET LOC=LOC+8: RETURN
7550 LET L$=STR$ LOC+" "
7551 IF LEN L$<6 THEN LET L$=" "+L$: GO TO 7551
7552 FOR I=0 TO 4: IF LOC+I>65535 THEN RETURN
7553 LET C$=STR$ PEEK (LOC+I)
7555 IF LEN C$<5 THEN LET C$=" "+C$: GO TO 7555
7560 LET L$=L$+C$: NEXT I
7565 LET LOC=LOC+5: RETURN
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
