The Acorn DFS Osword commands - by - Gordon Horsington ---------------------------------------------------------- Module 7. Duplicating copy-protected single density discs --------------------------------------------------------- +----------------------------------------------------------+ | All the DFS modules in this series use programs which | | experiment with the format and contents of discs. These | | experiments may have disasterous effects if you use any | | of the programs on discs which store programs or data | | which you cannot afford to lose. You should first try | | out the programs using discs that have either been | | duplicated or, better still, have not been used at all. | +----------------------------------------------------------+ This module deals with duplicating copy-protected single density discs. As with the previous module, which dealt with producing this type of disc, you should use the information in this module as a starting point for your own program designs. The two disc duplication programs used to illustrate this module will copy most, but not all, single density discs. They have been designed simply to illustrate the techniques used to achieve this objective and, for that reason, they are not the fastest disc duplication programs available. The program COPYDFS is quite slow because it reads and writes a track at a time, but the program COPYALL is even slower because it reads and writes a sector at a time. Both programs require either 40 or 80 track dual disc drives because they copy from drive 0 to drive 1. One of the many techniques used to copy-protect single density discs is to include unformatted tracks on the disc. The programs which use this type of disc then look at a particular physical track and, if they find that the track has been formatted, they reject the disc as an illegal copy. If formatted discs are to be used for duplicating protected software it may be necessary to be able to de-format some tracks on the disc. This means that when an attempt is made to read from or write to the disc a 'Sector not found' error should be produced. This error will be generated if an attempt is made to read from or write to a track formatted with one sector of 2048 bytes. The program DEFORM can be used to demonstrate this idea. DEFORM must be used with great care because it removes track &00 from the disc in the current drive. Again, you have been warned! 10 REM: DEFORM 20 osword=&FFF1 30 DIM mcode &100 40 FORpass=0TO3STEP3 50 P%=mcode 60 [ OPT pass 70 LDA #&7F 80 LDX #block MOD 256 90 LDY #block DIV 256 100 JSR osword 110 LDA result 120 BNE error 130 RTS 140 .error 150 BRK 160 BRK 170 EQUS "De-format error" 180 BRK 190 .block 200 EQUB &FF \ current drive 210 EQUD buffer \ sector table 220 EQUB &05 \ 5 parameters 230 EQUB &63 \ format command 240 EQUB &00 \ physical track 250 EQUB &00 \ gap 3 260 EQUB &C1 \ sectors/size 270 EQUB &00 \ gap 5 280 EQUB &10 \ gap 1 290 .result 300 EQUB &00 \ result byte 310 .buffer 320 EQUD &04000000 330 ] 340 NEXT 350 CALL mcode The de-formatting technique illustrated in the program DEFORM is used in both COPYDFS and COPYALL to ensure that any unformatted tracks on the source disc are unformatted on the destination disc even if the destination disc has been previously formatted. Both the copy programs use a similar algorithm to duplicate copy-protected discs. These programs have been written to make them as easy to understand as possible. They are both well structured and well commented and you should make every effort to understand how they work. It is only when you fully understand how disc duplicators work that you can design a disc format which will defeat disc duplication programs. The program COPYDFS uses the following algorithm to duplicate each track: 1) Seek the physical track on the source disc. 2) Read one sector ID from the physical track. If a 'Sector not Found' error is generated the track has not been formatted and the destination track should be de-formatted. 3) If the source track has been formatted, extract the number of sectors on the track from the data read in step 2) and read all the sector IDs on the track. 4) Format the destination disc using the sector ID data from step 3). 5) Read every sector on the source track using the Read Data and Deleted Data command and the sector ID data from step 3). Check for deleted data on the track. 6) If deleted data is used on the source track then use the Write Deleted Data command to write the data onto the destination track, otherwise use the Write Data command to write the data onto the destination track. This simple algorithm will, somewhat surprisingly, duplicate many commercially protected discs but it is reletively easy to design a disc format which cannot be copied using this method. You might like to consider what would happen if, for example, you use a mixture of deleted and normal sectors on one track and use a !BOOT program which uses the appropriate command to read individual sectors rather than simply use the Read Data and Deleted Data command for all sectors. If you design a !BOOT program which does this on a copy-protected disc, then that disc could not be copied successfully using this algorithm. If you intend to take a serious interest in copy-protection then your first task should be to design a disc format which can not be copied by COPYDFS. 10 REM: COPYDFS 20 osnewl=&FFE7 30 oswrch=&FFEE 40 osword=&FFF1 50 osbyte=&FFF4 60 DIM table &50 70 DIM mcode &500 80 DIM buffer &1000 90 FOR pass=0 TO 2 STEP 2 100 P%=mcode 110 [ OPT pass 120 JSR osnewl 130 .mainloop 140 JSR escape \ check escape flag 150 JSR seek \ seek physical tracks 0 - 40 160 JSR firstsector \ read sector id first sector 170 BNE notformatted \ if error then track not formatted 180 JSR sectorids \ read all sector ids 190 JSR format \ format sector on drive 1 200 JSR copytrack \ read and write sector 210 JMP output 220 .notformatted 230 JSR deform \ de-format this track 240 .output 250 JSR printbyte \ print track number 260 INC physical \ increment physical track number 270 LDA physical \ load physical track number 280 CMP last \ all done? 290 BNE mainloop \ if not copy next track 300 JSR osnewl 310 RTS \ return to BASIC 320 .escape 330 LDA &FF \ escape flag 340 BMI pressed \ bit 7 set if pressed 350 RTS 360 .pressed 370 LDA #&7E 380 JSR osbyte \ acknowledge Escape 390 BRK 400 BRK 410 EQUS "Escape" 420 BRK 430 .seek 440 LDA physical \ physical track number 450 STA seekblock+7 460 LDA #&00 \ drive 0 470 STA seekblock \ store drive number 480 LDA #&7F 490 LDX #seekblock MOD 256 500 LDY #seekblock DIV 256 510 JSR osword 520 LDA seekblock+8 \ result 530 BNE seekerror \ = 0 if OK 540 LDA #&01 \ drive 1 550 STA seekblock \ store drive number 560 LDA #&7F 570 LDX #seekblock MOD 256 580 LDY #seekblock DIV 256 590 JSR osword 600 LDA seekblock+8 \ result 610 BNE seekerror \ = 0 if OK 620 RTS 630 .seekerror 640 BRK 650 BRK 660 EQUS "Seek error" 670 BRK 680 .format 690 LDA physical \ physical track number 700 STA formblock+7 \ store physical track 710 LDX table+3 \ data size code 720 LDA gap,X \ load gap 3 for these sectors 730 STA formblock+8 \ store for formatting 740 LDA #&7F 750 LDX #formblock MOD 256 760 LDY #formblock DIV 256 770 JSR osword 780 LDA formblock+12 \ result 790 BNE formerror \ = 0 if OK 800 RTS 810 .formerror 820 BRK 830 BRK 840 EQUS "Format error" 850 BRK 860 .deform 870 LDA physical \ load physical track number 880 STA deblock+7 \ store physical track 890 LDA #&7F 900 LDX #deblock MOD 256 910 LDY #deblock DIV 256 920 JSR osword \ de-format track 930 LDA deblock+12 \ result 940 BNE deerror \ = 0 if OK 950 RTS 960 .deerror 970 BRK 980 BRK 990 EQUS "De-format error" 1000 BRK 1010 .register 1020 STA regblock+8 \ value to put in register 1030 LDA #&00 \ drive 0 1040 STA regblock 1050 LDA #&12 \ write track register 0/2 1060 STA regblock+7 \ register number 1070 LDA #&7F 1080 LDX #regblock MOD 256 1090 LDY #regblock DIV 256 1100 JSR osword 1110 LDA regblock+9 \ result 1120 BNE regerror \ = 0 if OK 1130 LDA #&01 \ drive 1 1140 STA regblock 1150 LDA #&1A \ write track register 1/3 1160 STA regblock+7 \ register number 1170 LDA #&7F 1180 LDX #regblock MOD 256 1190 LDY #regblock DIV 256 1200 JSR osword 1210 LDA regblock+9 \ result 1220 BNE regerror \ = 0 if OK 1230 RTS 1240 .regerror 1250 BRK 1260 BRK 1270 EQUS "Special register error" 1280 BRK 1290 .firstsector 1300 LDA physical \ physical track number 1310 STA idsblock+7 \ store physical track 1320 LDA #&01 \ one sector 1330 STA idsblock+9 \ number of ids 1340 LDA #&7F 1350 LDX #idsblock MOD 256 1360 LDY #idsblock DIV 256 1370 JSR osword 1380 LDA idsblock+10 \ result 1390 AND #&1E \ = 0 if formatted 1400 RTS 1410 .sectorids 1420 LDX table+3 \ load data size code 1430 LDA sizes,X \ load number of sectors 1440 STA idsblock+9 \ store number of sectors 1450 ASL A \ *2 1460 ASL A \ *4 1470 SEC 1480 SBC #&04 \ sectors*4-4 1490 STA sectornumber \ store index on sectors 1500 TXA \ transfer data size code 1510 ASL A \ *2 1520 ASL A \ *4 1530 ASL A \ *8 1540 ASL A \ *16 1550 ASL A \ *32 1560 ORA idsblock+9 \ add number of sectors 1570 STA copyblock+9 \ sector size/number 1580 STA formblock+9 \ sector size/number 1590 LDA #&7F 1600 LDX #idsblock MOD 256 1610 LDY #idsblock DIV 256 1620 JSR osword 1630 LDA idsblock+10 \ result 1640 AND #&1E 1650 BNE idserror \ = 0 if OK 1660 RTS 1670 .idserror 1680 BRK 1690 BRK 1700 EQUS "Sector ID Error" 1710 BRK 1720 .copytrack 1730 LDX sectornumber \ load index on table 1740 LDA table+2,X \ load logical sector number 1750 STA copyblock+8 \ store for read sector 1760 .lowest 1770 DEX 1780 DEX 1790 DEX 1800 DEX 1810 BMI finished 1820 LDA table+2,X \ load logical sector number 1830 CMP copyblock+8 \ is it lower than the last one? 1840 BCS lowest \ branch if not lowest sector 1850 STA copyblock+8 \ store if it is lower 1860 BCC lowest \ look for lower sector number 1870 .finished 1880 LDA table \ load logical track number 1890 STA copyblock+7 \ and store for read 1900 JSR register \ write track register 1910 LDA #&00 \ drive 0 1920 STA copyblock 1930 LDA #&57 \ read sector command 1940 STA copyblock+6 1950 LDA #&7F 1960 LDX #copyblock MOD 256 1970 LDY #copyblock DIV 256 1980 JSR osword 1990 LDA copyblock+10 \ result 2000 BEQ notdel \ not deleted data 2010 CMP #&20 \ deleted data result 2020 BNE readerror \ error if not &20 2030 LDA #&4F \ write deleted data command 2040 BNE savecom 2050 .notdel 2060 LDA #&4B \ write data command 2070 .savecom 2080 STA copyblock+6 2090 LDA #&01 \ drive 1 2100 STA copyblock 2110 LDA #&7F 2120 LDX #copyblock MOD 256 2130 LDY #copyblock DIV 256 2140 JSR osword 2150 LDA copyblock+10 \ result 2160 BNE writeerror \ = 0 if OK 2170 LDA physical 2180 JSR register \ write track register 2190 RTS 2200 .readerror 2210 LDA physical 2220 JSR register 2230 BRK 2240 BRK 2250 EQUS "Read error" 2260 BRK 2270 .writeerror 2280 LDA physical 2290 JSR register 2300 BRK 2310 BRK 2320 EQUS "Write error" 2330 BRK 2340 .printbyte 2350 LDA physical \ print physical track number 2360 PHA 2370 LSR A 2380 LSR A 2390 LSR A 2400 LSR A 2410 JSR nybble \ print MS nybble 2420 PLA 2430 JSR nybble \ print LS nybble 2440 LDA #ASC(" ") 2450 JSR oswrch \ print space 2460 JMP oswrch \ print space and return 2470 .nybble 2480 AND #&0F 2490 SED 2500 CLC 2510 ADC #&90 2520 ADC #&40 2530 CLD 2540 JMP oswrch \ print nybble and return 2550 .seekblock 2560 EQUB &00 \ drive 0/1 2570 EQUD &00 \ does not matter 2580 EQUB &01 \ 1 parameter 2590 EQUB &69 \ seek command 2600 EQUB &00 \ physical track number 2610 EQUB &00 \ result byte 2620 .regblock 2630 EQUB &00 \ drive 0/1 2640 EQUD &00 \ does not matter 2650 EQUB &02 \ 2 parameters 2660 EQUB &7A \ write special register 2670 EQUB &00 \ register number 2680 EQUB &00 \ value to put in register 2690 EQUB &00 \ result byte 2700 .idsblock 2710 EQUB &00 \ drive 0 2720 EQUD table \ address of buffer 2730 EQUB &03 \ 3 parameters 2740 EQUB &5B \ read sector IDs command 2750 EQUB &00 \ physical track number 2760 EQUB &00 \ always &00 2770 EQUB &00 \ number of IDs to be read 2780 EQUB &00 \ result byte 2790 .copyblock 2800 EQUB &00 \ drive 0/1 2810 EQUD buffer \ address of buffer 2820 EQUB &03 \ 3 parameters 2830 EQUB &57 \ read data and deleted data 2840 EQUB &00 \ logical track number 2850 EQUB &00 \ logical sector number 2860 EQUB &00 \ sector size/number 2870 EQUB &00 \ result byte 2880 .formblock 2890 EQUB &01 \ drive 1 2900 EQUD table \ sector table 2910 EQUB &05 \ 5 parameters 2920 EQUB &63 \ format track command 2930 EQUB &00 \ physical track number 2940 EQUB &00 \ gap 3 size 2950 EQUB &00 \ sector size/number 2960 EQUB &00 \ gap 5 size 2970 EQUB &10 \ gap 1 size 2980 EQUB &00 \ result byte 2990 .deblock 3000 EQUB &01 \ drive 1 3010 EQUD detable \ sector table 3020 EQUB &05 \ 5 parameters 3030 EQUB &63 \ format track command 3040 EQUB &00 \ physical track number 3050 EQUB &00 \ gap 3 size 3060 EQUB &C1 \ sector size/number 3070 EQUB &00 \ gap 5 size 3080 EQUB &10 \ gap 1 size 3090 EQUB &00 \ result byte 3100 .detable 3110 EQUD &04000000 3120 .gap 3130 EQUB 11 \ Gap 3, 18 sectors 3140 EQUB 21 \ Gap 3, 10 sectors 3150 EQUB 74 \ Gap 3, 5 sectors 3160 EQUB 255 \ Gap 3, 2 sectors 3170 EQUB 0 \ Gap 3, 1 sector 3180 .sizes 3190 EQUB 18 3200 EQUB 10 3210 EQUB 5 3220 EQUB 2 3230 EQUB 1 3240 .physical 3250 EQUB &00 3260 .sectornumber 3270 EQUB &00 3280 .last 3290 EQUB &00 3300 ] 3310 NEXT 3320 INPUT'"Number of tracks (40/80) "tracks$ 3330 IF tracks$="40" ?last=40 ELSE ?last=80 3340 PRINT'"Insert ";?last;" track source disc in :0" 3350 PRINT"Insert ";?last;" track destination disc in :1" 3360 PRINT'"Press Spacebar to copy from :0 to :1" 3370 REPEAT 3380 UNTIL GET=32 3390 CALL mcode When you have designed a disc format and a data storage and retrievel system which cannot be copied with COPYDFS you should attempt to copy the disc using COPYALL. This program uses a similar algorithm to that used by COPYDFS but, instead of copying a track at a time, it attempts to copy the disc a sector at a time. This method of copying takes much longer but it does allow the program to check if deleted data is being used on an individual sector rather than on a track as a whole. This will give a better copy of the original disc but it is not a fool-proof method of duplicating 'difficult' discs. When you start to think about designing a disc format which can not be copied by COPYALL I suggest that you should think carefully about using unusual logical track and sector combinations in unusual or unexpected ways. I cannot tell you what to do because everyone else who reads this module will then know what you have done. You can not make a disc unhackable (if there is such a word) but it is possible to make a disc uncopyable by either of these programs or any of the commercial disc duplication programs available at the time of writing (November 1987). All the information you need to do it has been presented in these DFS modules. You will have to look carefully at the information provided and think hard about what a program would need to do to duplicate your disc. 10 REM: COPYALL 20 osnewl=&FFE7 30 oswrch=&FFEE 40 osword=&FFF1 50 osbyte=&FFF4 60 DIM table &50 70 DIM mcode &500 80 DIM buffer &1000 90 FOR pass=0 TO 2 STEP 2 100 P%=mcode 110 [ OPT pass 120 JSR osnewl 130 .mainloop 140 JSR escape \ check escape flag 150 JSR seek \ seek physical tracks 0 - 40 160 JSR firstsector \ read sector id first sector 170 BNE notformatted \ if error then track not formatted 180 JSR sectorids \ read all sector ids 190 JSR format \ format sector on drive 1 200 .loopsector 210 JSR escape \ check escape flag 220 JSR copysector \ read and write sector 230 BPL loopsector \ copy next sector 240 LDA physical \ physical track number 250 JSR register \ write track register 260 JMP output 270 .notformatted 280 JSR deform \ de-format this track 290 .output 300 JSR printbyte \ print track number 310 INC physical \ increment physical track number 320 LDA physical \ load physical track number 330 CMP last \ all done? 340 BNE mainloop \ if not copy next track 350 JSR osnewl 360 RTS \ return to BASIC 370 .escape 380 LDA &FF \ escape flag 390 BMI pressed \ bit 7 set if pressed 400 RTS 410 .pressed 420 LDA #&7E 430 JSR osbyte \ acknowledge Escape 440 BRK 450 BRK 460 EQUS "Escape" 470 BRK 480 .seek 490 LDA physical \ physical track number 500 STA seekblock+7 510 LDA #&00 \ drive 0 520 STA seekblock \ store drive number 530 LDA #&7F 540 LDX #seekblock MOD 256 550 LDY #seekblock DIV 256 560 JSR osword 570 LDA seekblock+8 \ result 580 BNE seekerror \ = 0 if OK 590 LDA #&01 \ drive 1 600 STA seekblock \ store drive number 610 LDA #&7F 620 LDX #seekblock MOD 256 630 LDY #seekblock DIV 256 640 JSR osword 650 LDA seekblock+8 \ result 660 BNE seekerror \ = 0 if OK 670 RTS 680 .seekerror 690 BRK 700 BRK 710 EQUS "Seek error" 720 BRK 730 .format 740 LDA physical \ physical track number 750 STA formblock+7 \ store physical track 760 LDA table+3 \ data size code 770 TAX \ used as index later 780 ASL A \ *2 790 ASL A \ *4 800 ASL A \ *8 810 ASL A \ *16 820 ASL A \ *32 830 STA formblock+9 \ store datacode*32 840 ORA #&01 \ add 1 850 STA copyblock+9 \ store datacode*32+1 860 LDA sizes,X \ load number of sectors 870 ORA formblock+9 \ add datacode*32 880 STA formblock+9 \ store datacode*32+numbersectors 890 LDA gap,X \ load gap 3 for these sectors 900 STA formblock+8 \ store for formatting 910 LDA #&7F 920 LDX #formblock MOD 256 930 LDY #formblock DIV 256 940 JSR osword 950 LDA formblock+12 \ result 960 BNE formerror \ = 0 if OK 970 LDX table+3 \ load data size code 980 LDA sizes,X \ load number of sectors 990 ASL A \ *2 1000 ASL A \ *4 1010 SEC 1020 SBC #&04 \ sectors*4-4 1030 STA sectornumber \ store index on sectors 1040 RTS 1050 .formerror 1060 BRK 1070 BRK 1080 EQUS "Format error" 1090 BRK 1100 .deform 1110 LDA physical \ load physical track number 1120 STA deblock+7 \ store physical track 1130 LDA #&7F 1140 LDX #deblock MOD 256 1150 LDY #deblock DIV 256 1160 JSR osword \ de-format track 1170 LDA deblock+12 \ result 1180 BNE deerror \ = 0 if OK 1190 RTS 1200 .deerror 1210 BRK 1220 BRK 1230 EQUS "De-format error" 1240 BRK 1250 .register 1260 STA regblock+8 \ value to put in register 1270 LDA #&00 \ drive 0 1280 STA regblock 1290 LDA #&12 \ write track register 0/2 1300 STA regblock+7 \ register number 1310 LDA #&7F 1320 LDX #regblock MOD 256 1330 LDY #regblock DIV 256 1340 JSR osword 1350 LDA regblock+9 \ result 1360 BNE regerror \ = 0 if OK 1370 LDA #&01 \ drive 1 1380 STA regblock 1390 LDA #&1A \ write track register 1/3 1400 STA regblock+7 \ register number 1410 LDA #&7F 1420 LDX #regblock MOD 256 1430 LDY #regblock DIV 256 1440 JSR osword 1450 LDA regblock+9 \ result 1460 BNE regerror \ = 0 if OK 1470 RTS 1480 .regerror 1490 BRK 1500 BRK 1510 EQUS "Special register error" 1520 BRK 1530 .firstsector 1540 LDA physical \ physical track number 1550 STA idsblock+7 \ store physical track 1560 LDA #&01 \ one sector 1570 STA idsblock+9 \ number of ids 1580 LDA #&7F 1590 LDX #idsblock MOD 256 1600 LDY #idsblock DIV 256 1610 JSR osword 1620 LDA idsblock+10 \ result 1630 AND #&1E \ = 0 if formatted 1640 RTS 1650 .sectorids 1660 LDX table+3 \ load data size code 1670 LDA sizes,X \ load number of sectors 1680 STA idsblock+9 \ store number of sectors 1690 LDA #&7F 1700 LDX #idsblock MOD 256 1710 LDY #idsblock DIV 256 1720 JSR osword 1730 LDA idsblock+10 \ result 1740 AND #&1E 1750 BNE idserror \ = 0 if OK 1760 RTS 1770 .idserror 1780 BRK 1790 BRK 1800 EQUS "Sector ID Error" 1810 BRK 1820 .copysector 1830 LDX sectornumber \ load index on table 1840 LDA table+2,X \ load logical sector number 1850 STA copyblock+8 \ store for read sector 1860 LDA table,X \ load logical track number 1870 STA copyblock+7 \ and store for read 1880 JSR register \ write track register 1890 LDA #&00 \ drive 0 1900 STA copyblock 1910 LDA #&57 \ read sector command 1920 STA copyblock+6 1930 LDA #&7F 1940 LDX #copyblock MOD 256 1950 LDY #copyblock DIV 256 1960 JSR osword 1970 LDA copyblock+10 1980 BEQ notdel \ not deleted data 1990 CMP #&20 \ deleted data result 2000 BNE readerror \ error if not &20 2010 LDA #&4F \ write deleted data command 2020 BNE savecom 2030 .notdel 2040 LDA #&4B \ write data command 2050 .savecom 2060 STA copyblock+6 2070 LDA #&01 \ drive 1 2080 STA copyblock 2090 LDA #&7F 2100 LDX #copyblock MOD 256 2110 LDY #copyblock DIV 256 2120 JSR osword 2130 LDA copyblock+10 \ result 2140 BNE writeerror \ = 0 if OK 2150 SEC 2160 LDA sectornumber \ sector index on table 2170 SBC #&04 2180 STA sectornumber \ index=index-4 2190 RTS 2200 .readerror 2210 LDA physical \ physical track number 2220 JSR register \ write track register 2230 BRK 2240 BRK 2250 EQUS "Read error" 2260 BRK 2270 .writeerror 2280 LDA physical \ physical track number 2290 JSR register \ write track register 2300 BRK 2310 BRK 2320 EQUS "Write error" 2330 BRK 2340 .printbyte 2350 LDA physical \ print physical track number 2360 PHA 2370 LSR A 2380 LSR A 2390 LSR A 2400 LSR A 2410 JSR nybble \ print MS nybble 2420 PLA 2430 JSR nybble \ print LS nybble 2440 LDA #ASC(" ") 2450 JSR oswrch \ print space 2460 JMP oswrch \ print space and return 2470 .nybble 2480 AND #&0F 2490 SED 2500 CLC 2510 ADC #&90 2520 ADC #&40 2530 CLD 2540 JMP oswrch \ print nybble and return 2550 .seekblock 2560 EQUB &00 \ drive 0/1 2570 EQUD &00 \ does not matter 2580 EQUB &01 \ 1 parameter 2582 EQUB &69 \ seek command 2584 EQUB &00 \ physical track number 2586 EQUB &00 \ result byte 2590 .regblock 2600 EQUB &00 \ drive 0/1 2610 EQUD &00 \ does not matter 2620 EQUB &02 \ 2 parameters 2622 EQUB &7A \ write special register 2624 EQUB &00 \ register number 2626 EQUB &00 \ value to put in register 2630 EQUB &00 \ result byte 2640 .idsblock 2650 EQUB &00 \ drive 0 2660 EQUD table \ address of buffer 2670 EQUB &03 \ 3 parameters 2672 EQUB &5B \ read sector IDs 2674 EQUB &00 \ physical track number 2676 EQUB &00 \ always &00 2678 EQUB &00 \ number of IDs to be read 2680 EQUB &00 \ result byte 2690 .copyblock 2700 EQUB &00 \ drive 0/1 2710 EQUD buffer \ address of buffer 2720 EQUB &03 \ 3 parameters 2722 EQUB &57 \ read data and deleted data 2724 EQUB &00 \ logical track number 2726 EQUB &00 \ logical sector number 2728 EQUB &00 \ sector size/number 2730 EQUB &00 \ result byte 2740 .formblock 2750 EQUB &01 \ drive 1 2760 EQUD table \ sector table 2770 EQUB &05 \ 5 parameters 2772 EQUB &63 \ format track command 2774 EQUB &00 \ physical track number 2776 EQUB &00 \ gap 3 size 2778 EQUB &00 \ sector size/number 2780 EQUB &00 \ gap 5 size 2782 EQUB &10 \ gap 1 size 2784 EQUB &00 \ result byte 2790 .deblock 2800 EQUB &01 \ drive 1 2810 EQUD detable \ sector table 2820 EQUB &05 \ 5 parameters 2822 EQUB &63 \ format track command 2824 EQUB &00 \ physical track number 2826 EQUB &00 \ gap 3 size 2828 EQUB &C1 \ sector size/number 2830 EQUB &00 \ gap 5 size 2832 EQUB &10 \ gap 1 size 2834 EQUB &00 \ result byte 2840 .detable 2850 EQUD &04000000 2860 .gap 2870 EQUB 11 \ Gap 3, 18 sectors 2880 EQUB 21 \ Gap 3, 10 sectors 2890 EQUB 74 \ Gap 3, 5 sectors 2900 EQUB 255 \ Gap 3, 2 sectors 2910 EQUB 0 \ Gap 3, 1 sector 2920 .sizes 2930 EQUB 18 2940 EQUB 10 2950 EQUB 5 2960 EQUB 2 2970 EQUB 1 2980 .physical 2990 EQUB &00 3000 .sectornumber 3010 EQUB &00 3020 .last 3030 EQUB &00 3040 ] 3050 NEXT 3060 INPUT'"Number of tracks (40/80) "tracks$ 3070 IF tracks$="40" ?last=40 ELSE ?last=80 3080 PRINT'"Insert ";?last;" track source disc in :0" 3090 PRINT"Insert ";?last;" track destination disc in :1" 3100 PRINT'"Press Spacebar to copy from :0 to :1" 3110 REPEAT 3120 UNTIL GET=32 3130 CALL mcode