Copycat is a dual-purpose utility for the Timex Sinclair 2068 combining a tape copier with a Z80 assembler. The tape copier (lines 1-100) can load and save tape data with or without headers, allowing it to copy tapes that use non-standard or missing headers — useful for duplicating copy-protected software. The Z80 assembler (lines 1060-9922) is written entirely in BASIC and supports the full Z80 instruction set, including extended, bit manipulation, block transfer, and I/O instructions, along with pseudo-ops ORG, EQU, DEFB, and DEFW. It features a symbol table with forward reference resolution, operand classification for registers, register pairs, conditions, and indexed addressing (IX/IY+d), and hex-formatted output showing addresses, opcodes, and operands. Source code can be entered interactively, merged from tape, or auto-scanned from REM statements embedded in a BASIC program. Hard copy output to a printer is also supported. The program uses a Fairlight-style turbo loader for fast tape loading.
Part 1: Tape Copier (lines 1-100)
A simple tape copy utility with machine code routines (stored in line 1’s REM) accessed via USR calls:
- a – Load with header (USR 26740)
- b – Load without header (USR 26763)
- c – Save with header (USR 26814)
- d – Save without header (USR 26850)
The “without header” options let you copy tapes with non-standard or missing headers (i.e., copy-protected tapes).
Part 2: Z80 Assembler (lines 1060-9922)
A complete single-pass Z80 assembler written entirely in BASIC. Key features:
- Opcode encoding via DATA tables (lines 2000-2200) that encode every Z80 instruction as patterns of operand types +
opcode bytes - Symbol table with forward reference tracking (unresolved symbols get a second fixup pass at lines 5000-5100)
- Operand parsing (lines 7400-7490) that classifies operands as registers, register pairs, conditions, immediates, or
memory references, including IX/IY+displacement - Supports: LD, ADD, ADC, SUB, SBC, AND, XOR, OR, CP, INC, DEC, PUSH, POP, JP, JR, CALL, RET, DJNZ, RST, BIT, SET,
RES, rotate/shift ops, block transfer/search, I/O, plus pseudo-ops ORG, EQU, DEFB, DEFW - Auto-scan mode (lines 7700-7880) that reads source from REM statements in the BASIC program itself
- Merge from tape to load additional source
- Hard copy (printer) output option
- Hex converter (lines 9100-9140) for address/opcode display
Content
Source Code
1 REM <machine code: tape I/O routines at USR 26740/26763/26814/26850>
5 BORDER 0:INK 7:PAPER 0:CLS :INVERSE 1:PRINT AT 0,10;"Copycat":INVERSE 0
10 PRINT AT 7,0;"a to load with header"
20 PRINT AT 8,0;"b to load without header"
30 PRINT AT 9,0;"c to save with header"
40 PRINT AT 10,0;"d to save without header"
50 INPUT a$
60 IF a$="a"THEN RANDOMIZE USR 26740
70 IF a$="b"THEN RANDOMIZE USR 26763
80 IF a$="c"THEN RANDOMIZE USR 26814
90 IF a$="d"THEN RANDOMIZE USR 26850
100 GO TO 10
1060 DEF FN j()=(m$="JP"OR m$="CALL"OR m$="RET"OR m$="JR")
1070 DEF FN v(x$)= CODE x$(7)+256* CODE x$(8)
1080 DEF FN i(x)= PEEK x+256* PEEK (x+j)
1085 DEF FN r()=di-vdef*(add+2)
1090 DIM s$(ns+nu+j+j, VAL "8"):DIM L$(VAL "6"):DIM t$(VAL "2")
1095 LET s$(j)= CHR$ NOT j
1100 LET u= NOT j:LET er=u:LET ed= VAL "237"
1110 LET m$="HARD COPY?":GO SUB 9000:LET hc=j+j+(CHR$ CODE a$="Y")
1120 LET m$="Auto scan?":GO SUB 9000:LET auto=(CHR$ CODE a$="Y"):IF auto THEN LET m$="Merge source from tape?":GO SUB 9000:IF CHR$ CODE a$="Y"THEN INPUT "Enter tape name:"; LINE a$:CLS :PRINT AT 8,2;"START TAPE THEN PRESS A KEY":PAUSE 4e4:CLS :MERGE a$
1125 IF auto THEN GO SUB 7800
1130 LET add= FN i(VAL "23730")+ VAL "1":REM get default address
1140 CLS :PRINT #hc;"ADDR HEX OP OPERANDS "
1200 LET e= NOT j:LET vdef=j:LET ds=e:LET dd=e:LET ec=e:LET b=e:LET w=e
1210 LET h=add:GO SUB 9100:LET m$=h$:GO SUB 7700:IF end THEN GO TO VAL "5000"
1220 LET m$=""
1230 DIM t(2):DIM x(4,2):LET t= NOT j:LET p=j:LET t$="00"
1240 FOR x=j TO LEN a$:REM now scan input line
1245 IF a$(x)=";"THEN GO TO 1300:REM ignore comments
1250 IF t <> (a$(x) <> " "AND a$(x) <> ",") THEN LET x(p,t+j)=x-j:LET p=p+t:LET t= NOT t:IF p>4 THEN GO TO 1310:REM mark word ends
1260 NEXT x
1300 IF x(p,j) OR p=j THEN LET x(p,j+j)=x-j
1310 LET bx=(FN s$(j)(LEN FN s$(j))=":"):REM check for symbol
1320 LET m$= FN s$(j+bx):REM get instruction
1400 LET jump= FN j():REM is this a JP/JR/CALL?
1410 FOR t=j TO j+j:LET x$= FN s$(j+t+bx):GO SUB 7400:NEXT t:REM determine op classes
1500 LET n$=t$+m$:REM create scanning code
1510 LET f1=(LEN FN s$(bx+2) <> 0):LET f2=(LEN FN s$(bx+3) <> 0):IF f1 THEN GO TO 1600:REM find how many ops
1520 IF m$=""THEN GO TO 1900:REM no instruction
1530 FOR b=j TO j+j:LET i= VAL "2010+b*10":GO SUB 8300:IF NOT x THEN NEXT b:REM vet no op type
1550 GO TO 1630
1600 LET i= VAL "2060+f2*20":LET b= NOT j:REM vet 1/2 op types
1610 GO SUB 8300:IF NOT x+f2 THEN LET i=2070:GO SUB 8300
1615 IF x THEN GO TO 1650
1620 RESTORE VAL "2000+40*(f2=0)":GO SUB 8400:IF x THEN GO TO 1800
1630 IF NOT x THEN LET ec= VAL "9":REM invalid instruction
1640 GO TO 1800
1650 LET n=x-j:RESTORE i+j:LET n$=t$:GO SUB 8400:IF NOT x THEN LET ec= VAL "9"
1800 IF ec THEN GO TO 1960:REM don't output if error
1820 IF m$="ORG"THEN LET add=di:LET h=di:GO SUB 9100:REM check for pseudo-op
1830 IF e THEN POKE add,dd:IF e=j+j THEN POKE add+j+j,dis:REM echeck if IX/IY class output displacement
1840 FOR x=0 TO b-j:READ x$:POKE add+x+(e <> 0)+x*(e=2), VAL x$:NEXT x:REM output machine code
1900 IF NOT vdef THEN GO SUB 7200:REM undefined symbol?
1910 IF NOT bx OR ec THEN GO TO 1960:REM check for label
1920 LET L$= FN s$(bx)(TO LEN FN s$(bx)-j):GO SUB 7600:IF v THEN LET ec=2:GO TO 1960:REM does it already exist?
1925 IF s=ns+j THEN LET ec= VAL "99":GO TO 1960:REM full table
1930 LET v=add:IF m$="EQU"THEN LET v=di:REM re-equate
1940 LET s$(s)=L$:LET s$(s,7 TO )= CHR$ FN L(v)+ CHR$ FN h(v)
1950 LET s$(j)= CHR$(s-j):REM update symbol count
1960 GO SUB 7300:IF ec THEN LET er=er+j:GO TO VAL "1200":REM print line
1970 LET add=add+b+e:REM update address
1990 GO TO 1200:REM await new input
2000 DATA "11LD","1","1","64+t(1)*8+t(2)"
2001 DATA "13LD","vdef","2","t(1)*8+6","ds"
2002 DATA "14LD","t(1)=7","3","58","ds","dh"
2003 DATA "15LD","(t(1)=7)*(t(2)<2)","1","t(2)*16+10"
2004 DATA "23LD","t(1)[C9]6","3","t(1)*16+1","ds","dh"
2005 DATA "24LD","t(1)=2","3","42","ds","dh"
2006 DATA "24LD","t(1)<4","4","ed","75+t(1)*16","ds","dh"
2007 DATA "41LD","t(2)=7","3","50","ds","dh"
2008 DATA "42LD","t(2)=2","3","34","ds","dh"
2009 DATA "42LD","t(2)<4","4","ed","67+t(2)*16","ds","dh"
2010 DATA "51LD","(t(2)=7)*(t(1)<2)","1","t(1)*16+2"
2011 DATA "99","","2100"
2020 DATA "RET","201","NOP","0","RLCA","7","RRCA","15","RLA","23","RRA","31","DAA","39","CPL","47","SCF","55","CCF","63","HALT","118","EXX","217","DI","243","EI","251","99",""
2030 DATA "NEG","ED","68","RETN","ED","69","RETI","ED","77","RRD","ED","103","RLD","ED","111","LDI","ED","160","LDIR","ED","176","LDD","ED","168","LDDR","ED","184","CPD","ED","169","CPDR","ED","185","CPI","ED","161","CPIR","ED","177"
2031 DATA "INI","ED","162","INIR","ED","178","IND","ED","170","INDR","ED","186","OUTI","ED","163","OTIR","ED","179","OUTD","ED","171","OTDR","ED","187","99","",""
2040 DATA "30JR","[BD][A8]r()<128","2","24","[A8]r()"
2041 DATA "10INC","1","1","4+t(1)*8"
2042 DATA "20INC","t(1)[C9]4","1","3+t(1)*16"
2043 DATA "10DEC","1","1","5+t(1)*8"
2044 DATA "20DEC","t(1)[C9]4","1","11+t(1)*16"
2045 DATA "30DJNZ","[BD][A8]r()<128","2","16","[A8]r()"
2046 DATA "60RET","1","1","192+t(1)*8"
2047 DATA "20POP","t(1)[C9]3","1","193+t(1)*16-16*(t(1)=4)"
2048 DATA "20PUSH","t(1)[C9]3","1","197+t(1)*16-16*(t(1)=4)"
2049 DATA "30RST","vdef[C6]ds[C7]56","1","199+[BA](ds/8)*8"
2050 DATA "10JP","t(1)=6","1","233"
2051 DATA "30IM","vdef*(ds<3)*(ds[C8]0)","2","ed","70+16*(ds=1)+24*(ds=2)"
2052 DATA "30JP","1","3","195","ds","dh"
2053 DATA "30CALL","1","3","205","ds","dh"
2054 DATA "30DEFB","vdef","1","ds"
2055 DATA "30DEFW","vdef","2","ds","dh"
2056 DATA "30ORG","vdef","0"
2057 DATA "30EQU","vdef[C6]bx","0"
2059 DATA "99","","0"
2060 [10][00] DATA "","","SUB","","AND","XOR","OR","CP","99"[10][05]
2061 [10][00] DATA "10","1","1","128+n*8+t(1)"
2062 [10][00] DATA "30","vdef","2","198+n*8","ds"
2063 DATA "99","","0"
2070 DATA "RLC","RRC","RL","RR","SLA","SRA","","SRL","99"
2071 DATA "10","1","2","203","n*8+t(1)"
2072 DATA "99","","0"
2080 DATA "ADD","ADC","","SBC","99"
2081 DATA "11","t(1)=7","1","128+n*8+t(2)"
2082 DATA "13","t(1)=7","2","198+n*8","ds"
2083 DATA "22","(t(1)=2)*(t(2)[C9]4)*(n=0)","1","9+t(2)*16"
2084 DATA "22","(t(1)=2)*(t(2)[C9]4)*(n[C9]0)","2","ed","78-n*4+t(2)*16"
2085 DATA "99","","0"
2100 DATA "63JP","1","3","194+t(1)*8","ds","dh"
2101 DATA "63JR","t(1)<4[C6][BD][A8]r()<128","2","32+t(1)*8","[A8]r()"
2102 DATA "22EX","(t(1)=4)*(t(2)=4)","1","8"
2103 DATA "22EX","(t(1)=1)*(t(2)=2)","1","235"
2104 DATA "52EX","(t(1)=3)*(t(2)=2)","1","227"
2112 DATA "31BIT","vdef[C6]ds<8","2","203","64+ds*8+t(2)"
2113 DATA "31RES","vdef[C6]ds<8","2","203","128+ds*8+t(2)"
2114 DATA "31SET","vdef[C6]ds<8","2","203","192+ds*8+t(2)"
2117 DATA "63CALL","1","3","196+t(1)*8","ds","dh"
2120 DATA "14IN","vdef[C6]t(1)=7","2","219","ds"
2121 DATA "41OUT","vdef[C6]t(2)=7","2","211","ds"
2122 DATA "17IN","t(1)[C9]6","2","ed","64+t(1)*8"
2123 DATA "71OUT","t(2)[C9]6","2","ed","65+t(2)*8"
2200 DATA "99","","0"
5000 IF CODE s$(j) THEN PRINT #hc''"Symbols:"':FOR y= VAL "2"TO VAL "[AF]s$(1)+1":LET h= FN v(s$(y)):GO SUB 9100:PRINT #hc; TAB VAL "((y-2)*16+1)";s$(y, TO 6);" ";h$;:NEXT y:REM list symbol table
5010 LET n= NOT j:REM scan unresolved table
5020 FOR t=ns+3 TO ns+2+u:LET add= FN v(s$(t)):LET c= PEEK add:LET L$=s$(t):GO SUB 7600:IF v THEN GO TO 5050:REM does it exist?
5030 IF NOT n THEN PRINT #hc''"Unresolved:"
5040 LET n=n+j:LET h=add:GO SUB 9100:PRINT #hc;" ";L$;" ";h$:GO TO 5100:REM no-print it
5050 RESTORE 9920:LET di= FN i(add+j):LET ds= FN L(di):LET di= VAL "di-65536*(di>32767)":REM determine type
5055 LET ds= VAL "ds-256*(ds>127)"
5060 READ c$,a$,x$:IF NOT VAL c$ THEN FOR x=j TO VAL x$:READ x$:NEXT x:GO TO 5060
5070 FOR x=j TO VAL a$:READ y$:POKE FN v(s$(t))+ VAL x$+x-j, VAL y$:NEXT x:REM relocate output
5100 NEXT t
5200 IF er+n THEN PRINT #hc'er+n;"Error(s)":REM any errors?
5210 GO TO 9999
7200 REM undefined
7210 LET u=u+j:IF u>nu THEN LET ec= VAL "98":RETURN
7220 LET s$(ns+u+j+j)=s$(ns+j+j, TO 6)+ CHR$ FN L(add)+ CHR$ FN h(add)
7230 RETURN
7300 REM print detail line
7310 IF ec THEN PRINT #hc;h$;" "; INVERSE j;"ERROR ";ec; INVERSE NOT j; TAB 14;a$:RETURN
7320 IF bx THEN PRINT #hc;h$; TAB VAL "13"; FN s$(j)
7330 IF m$=""THEN RETURN :REM no mnemonic entered
7340 PRINT #hc;h$; INVERSE j;"*"AND (NOT vdef); INVERSE 0; TAB 5;:FOR y=0 TO b+e-j:LET h= PEEK (add+y):GO SUB 9120:PRINT #hc;h$;:NEXT y
7350 PRINT #hc; TAB 14;m$; TAB 19; FN s$(j+j+bx);","AND f2; FN s$(3+bx)
7360 RETURN
7400 REM check type classify operands
7405 LET x= LEN x$:IF NOT x THEN RETURN
7410 LET ix=(x$(j)="("):IF ix THEN LET x$=x$(2 TO LEN x$-j)
7415 GO SUB 7500
7420 RESTORE 9900+jump:READ n:FOR x=0 TO n-j:READ c$:IF x$=c$ THEN GO TO 7450
7425 NEXT x
7430 LET t$(t)=("3"AND (NOT ix))+("4"AND ix)
7440 GO SUB 8500:LET di=a+w:LET dh= FN h(di):LET ds= FN L(di):RETURN
7450 LET t(t)=x:IF jump AND (NOT ix) THEN LET t$(t)="6":RETURN
7460 LET t$(t)="111111112222222070000005515011"(ix*15+x+j)
7470 IF x >= 13 THEN LET e=j+ix-jump:LET dd= VAL "dd+(dd=0)*(221+32*(x=14))":LET dis=w:LET x=10
7475 IF x>7 THEN LET t(t)=x-8
7480 IF ix AND t$(t)="1"THEN LET t(t)=6
7490 RETURN
7500 FOR x=j+j TO LEN x$:IF x$(x)="+"OR x$(x)="-"THEN GO TO 7550:REM calculate offset
7510 NEXT x
7520 LET w= NOT j:RETURN
7550 LET z$=x$(TO x-j):LET x$=x$(x TO ):GO SUB 8900:LET x$=z$
7560 LET w=x
7570 RETURN
7600 REM find symbol
7610 LET v=j:REM set as found
7620 FOR s=2 TO CODE s$(j)+j:IF L$=s$(s, TO 6) THEN LET a= FN v(s$(s)):RETURN
7630 NEXT s:LET v=0:REM not found
7640 RETURN
7700 IF NOT auto THEN GO TO 9000
7710 LET end=0:IF FN i(auto-1)=10730 THEN LET end=1:RETURN
7720 LET a$=""
7730 IF PEEK auto=13 THEN GO TO 7760
7735 IF PEEK auto=33 THEN LET auto=auto+1:GO TO 7770
7740 LET a$=a$+ CHR$ PEEK auto
7750 LET auto=auto+1:GO TO 7730
7760 LET auto=auto+6
7770 IF NOT LEN a$ THEN GO TO 7710
7780 GO TO 9020
7800 REM Auto start
7810 PRINT AT 10,10;"Searching..."
7820 LET x= FN i(23635):LET y= FN i(23627)
7830 IF x >= y THEN LET auto=0:RETURN
7840 IF PEEK (x+4)=234 THEN GO TO 7860
7850 LET x=x+4+ FN i(x+2):GO TO 7830
7860 IF PEEK (x+5) <> 40 THEN GO TO 7850
7870 LET auto=x+6
7880 RETURN
7900 REM This allows reruns
7910 RETURN
8300 REM find op (1)
8310 RESTORE i:LET x=0
8320 READ x$:IF x$="99"THEN LET x=0:RETURN :REM scan tables
8330 LET x=x+j:IF m$=x$ THEN RETURN :REM return if found
8340 FOR y=j TO b:READ x$:NEXT y:REM skip to next item
8350 GO TO 8320
8400 REM find op (2)
8410 READ x$,c$,y$:LET z= VAL y$:IF x$="99"THEN GO TO 8450:REM scan tables
8420 IF x$=n$ THEN LET x= VAL c$:IF x THEN LET b=z:RETURN :REM return if found
8430 FOR y=j TO z:READ x$:NEXT y:REM skip to next item
8440 GO TO 8410
8450 LET x=0:IF NOT z THEN RETURN
8460 RESTORE z:GO TO 8410
8500 REM convert type determine address
8505 LET a=0
8510 GO SUB 8900:IF v THEN LET a=x:LET ec= VAL "6*(x>65535[C5]x<-32768)":RETURN :REM check if decimal
8520 IF FN n(x$(j)) AND x$(LEN x$)="H"THEN FOR x=j TO LEN x$-j:LET a= VAL "a*16+[AF]x$(x)-48-7*(x$(x)>""9"")":NEXT x:RETURN :REM check if hex
8530 LET L$=x$:GO SUB 7600:IF v THEN RETURN :REM scan tables
8560 LET ec= VAL "6*([A8]a(x$(1))=0)":IF ec THEN RETURN
8570 LET s$(ns+2)=x$:LET vdef= NOT j:REM flag unresolved
8580 RETURN
8900 REM vet numeric
8910 LET z=j+ VAL "((x$(1)=""+"")[C5](x$(1)=""-""))[C6][B1]x$>1"
8920 FOR n=z TO LEN x$:IF FN n(x$(n)) THEN NEXT n
8930 LET v=(n> LEN x$):IF v THEN LET x= VAL x$(TO n-j)
8940 RETURN
9000 REM kybrd
9010 INPUT (m$+" "); LINE a$:LET end= NOT LEN a$
9020 FOR x=j TO LEN a$:LET a$(x)= CHR$(CODE a$(x)-32*(a$(x) >= "a")):NEXT x
9030 RETURN
9100 REM h$=hex$(h)
9110 LET h$=" ":GO TO 9130:REM 4 spaces
9120 LET h$=" ":REM 2 spaces
9130 LET h1=h:FOR x= LEN h$ TO j STEP -j:LET x1=h1- INT (h1/16)*16:LET h$(x)= CHR$(x1+ CODE "0"+7*(x1>9)):LET h1= INT (h1/16):NEXT x
9140 RETURN
9900 DATA 15,"B","C","D","E","H","L","M","A","BC","DE","HL","SP","AF","IX","IY"
9901 DATA 15,"NZ","Z","NC","C","PO","PE","P","M","","","HL","","","IX","IY"
9920 DATA "(c-[BA](c/8)*8)+[BA](c/64)=0","1","1","ds+[A8]L(a-add-2)"
9921 DATA "c=ed[C5]c=221[C5]c=253","2","2","[A8]L(a+di)","[A8]h(a+di)"
9922 DATA "1","2","1","[A8]L(a+di)","[A8]h(a+di)"
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
