Disassembler

Date: 198x
Type: Program
Platform(s): TS 2068

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:

  • CB prefix (line 1010 → 1200): adds 256 to the second byte to form an index into the opcode table.
  • ED prefix (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.
  • DD prefix (line 1040 → 1500): sets N$="IX" and falls through to indexed handling.
  • FD prefix (line 1050 → 1520): sets N$="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:

ModeTarget lineMeaning
0 (none)3000No operands; NBYTES=1
13100Register from F$ array indexed by low 3 bits of opcode
23200Immediate byte (NBYTES=2)
33300Immediate word (NBYTES=3)
43400Relative branch offset (NBYTES=2), computing absolute address
53500Indirect byte address operand (NBYTES=2)
63600Indirect word address operand (NBYTES=3)
73700Indirect byte destination (NBYTES=2)
83800Indirect word destination (NBYTES=3)
93900Undefined / 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*100 computed 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; since LOC at that point still points to the instruction start and NBYTES=2, the effective address for a relative jump is LOC+2+(signed I1). The expression LOC-254+I1 is 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

Appears On

Related Products

Related Articles

Related Content

Image Gallery

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.

People

No people associated with this content.

Scroll to Top