--- title: "Disassembler" id: 51877 type: "computer_media" slug: "disassembler" url: "http://localhost/computer_media/disassembler/" markdown_url: "http://localhost/computer_media/disassembler.md" published_at: "2023-08-16T01:42:19+00:00" modified_at: "2026-03-31T07:43:03+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2019/02/20230809-041332.jpg" excerpt: "A fully menu-driven Z80 disassembler in pure BASIC that decodes all prefix groups, handles IX/IY indexing, and dumps memory as mnemonics, ASCII, or hex/decimal." 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/" genre: - name: "Machine Language" slug: "machine-language" taxonomy: "genre" url: "http://localhost/type/machine-language/" - name: "Programming" slug: "programming" taxonomy: "genre" url: "http://localhost/type/programming/" media_type: "Program" download_url: "https://archive.org/download/timex-sinclair-software-archive/Disassembler (198x)(TS2068)(Cassette).zip" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2023/08/SCR-20260331-eabp.png" media_type_tags: "Machine Language, Programming" --- 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: | 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*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. ## 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 ```