--- title: "Copycat" id: 54568 type: "computer_media" slug: "copycat" url: "http://localhost/computer_media/copycat/" markdown_url: "http://localhost/computer_media/copycat.md" published_at: "2024-06-02T17:40:15+00:00" modified_at: "2026-03-31T07:14:46+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2024/06/SCR-20240714-eszy.png" excerpt: "A tape copier and Z80 assembler in one package — copy tapes with or without headers, and assemble Z80 machine code with full instruction set support, symbol table, and forward reference resolution." 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: "Tape" slug: "tape" taxonomy: "genre" url: "http://localhost/type/tape/" - name: "Utility" slug: "utility" taxonomy: "genre" url: "http://localhost/type/utility/" media_contents: - id: 51214 title: "CATS Library Tape 8" type: "computer_media" url: "http://localhost/computer_media/cats-library-tape-8/" - id: 55923 title: "Timex Sinclair Public Domain Library Tape 2003" type: "computer_media" url: "http://localhost/computer_media/timex-sinclair-public-domain-library-tape-2003/" media_type: "Program" mediadate: "198x" images: - url: "http://localhost/wp-content/uploads/2024/06/SCR-20240714-eszy.png" media_type_tags: "Machine Language, Tape, Utility" --- # Copycat 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 ## Source Code ``` 1 REM 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)" ```