The Acorn DFS Osword commands - by - Gordon Horsington ---------------------------------------------------------- Module 6. Creating 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. | +----------------------------------------------------------+ In this module I will describe in detail one method of producing copy- protected single density discs. This type of disc is designed to prevent other people seeing how your programs work and to stop them duplicating the disc. Meeting the first objective is relatively easy. Meeting the second objective is virtually impossible. It is easy to prevent amateur piracy using duplicating programs but you will never be able to stop a determined hacker undoing all your efforts to prevent duplication. I have not yet found a commercially produced copy-protected disc for the BBC range of computers which can not have all the protection removed so that the disc can be duplicated with the *BACKUP command. If you choose to copy-protect your discs you will at least indicate to the users of your software that you are unwilling to have the disc duplicated. To stand the best chance of defeating a hacker you should use the method described in this module as a starting point for developing your own ideas about protection. Remember that everyone reading this module will know how to undo the protection by just reversing the steps in the procedure. You should at least aim to produce a disc which cannot be duplicated by either of the two disc duplicators described in the next module. This will not be easy but all the information you need to do it has been or will be presented in this series. You might like to consider using Bad Tracks, Unusual logical sector numbers and logical track numbers in unexpected combinations, encrypted machine code programs and multiple files which use unusual methods of writing to and reading from the disc. What you do is up to you but you must try to think like a hacker if you want to prevent your software being hacked. The method used to introduce copy-protection will use a single density disc formatted with 5 sectors per track instead of the usual 10 sectors per track. The data will be stored on the disc using the Write Deleted Data command as an extra protection. A disc cannot be formatted with 5 sectors per track using the DFS formatter and so the first step in this procedure will be to write a special disc formatting program. The program SECTOR5 can be used to produce an object code file which, in turn, can be used to produce a single density disc with a standard track &00 and the rest of the disc formatted with 5 sectors on every track. Each of the non-standard tracks is capable of storing 2.5k of data in 5 sectors, ie. 512 bytes per sector. The object code file produced by SECTOR5 is exactly 1k long and can be stored in two of the 512 byte sectors. I will use this object code file as an example for producing the required copy-protected disc. Run the program SECTOR5 and, when prompted, give a suitable filename for the object code file. In the rest of this module I will refer to the object code file produced by SECTOR5 as FORM5. 10 REM: SECTOR5 20 DIM mcode &500 30 zeropage=&70 40 oswrch=&FFEE 50 osword=&FFF1 60 osbyte=&FFF4 70 osnewl=&FFE7 80 oswrch=&FFEE 90 osrdch=&FFE0 100 osasci=&FFE3 110 FORpass=4 TO 6 STEP 2 120 O%=mcode 130 P%=PAGE+&100 140 [ OPT pass 150 .firstbyte 160 EQUW &FF0D \ NEW the BASIC program 170 .start 180 LDA #&0F 190 JSR osasci \ scroll mode 200 LDX #title MOD 256 210 LDY #title DIV 256 220 JSR print \ print title 230 .getdata 240 LDX #drivenum MOD 256 250 LDY #drivenum DIV 256 260 JSR print \ which drive? 270 .whichdrive 280 JSR osrdch 290 BCS escape 300 SEC 310 SBC #ASC("0") 320 BMI whichdrive \ drive 0-3 330 CMP #&04 340 BCS whichdrive \ drive 0-3 350 STA block \ format parameter block 360 STA catblock \ catalogue parameter block 370 ADC #ASC("0") 380 JSR osasci \ print 0, 1, 2 or 3 390 LDX #tracknum MOD 256 400 LDY #tracknum DIV 256 410 JSR print \ 40 or 80 tracks? 420 .whichtrack 430 JSR osrdch 440 BCS escape 450 LDX #&27 \ 40 tracks 460 CMP #ASC("4") 470 BEQ continue 480 CMP #ASC("8") 490 BNE whichtrack 500 LDX #&4F \ 80 tracks 510 .continue 520 STX finish \ store number of tracks 530 JSR osasci \ print "8" or "4" 540 LDA #ASC("0") 550 JSR osasci \ print "0" to make "40" or "80" 560 LDX #ready MOD 256 570 LDY #ready DIV 256 580 JSR print \ ready to format? 590 JSR osrdch 600 BCS escape 610 PHA \ temp store for answer 620 JSR osasci \ print answer 630 JSR osnewl 640 PLA \ pull answer 650 AND #&DF \ upper case 660 CMP #ASC("Y") 670 BNE getdata 680 JSR osnewl 690 LDA #0 700 STA track 710 LDA #&01 \ data size 720 LDX #&10 \ gap 3 730 LDY #&2A \ number of sectors 740 JSR setup 750 LDA #&7F 760 LDX #block MOD 256 770 LDY #block DIV 256 780 JSR osword \ format track 0 ten sectors 790 LDA result \ load result byte 800 BEQ trackzero \ format OK if result = 0 810 .error 820 BRK 830 BRK 840 EQUS "Format error" 850 BRK 860 .escape 870 LDA #&7E 880 JSR osbyte \ acknowledge Escape 890 BRK 900 BRK 910 EQUS "Escape" 920 BRK 930 .trackzero 940 LDA #&7F 950 LDX #catblock MOD 256 960 LDY #catblock DIV 256 970 JSR osword \ store empty catalogue 980 LDA catresult \ check result byte 990 BNE error \ quit if error 1000 JSR printbyte \ print track 00 1010 .loop 1020 LDA &FF \ poll escape flag 1030 BMI escape \ bit 7 set if Escape pressed 1040 INC track \ increment track number 1050 LDA #&02 \ data size 1060 LDX #&4A \ gap 3 1070 LDY #&45 \ number of sectors 1080 JSR setup 1090 LDA #&7F 1100 LDX #block MOD 256 1110 LDY #block DIV 256 1120 JSR osword \ format track with 5 sectors 1130 LDA result \ load result byte 1140 BNE error \ quit if error 1150 LDA track \ load track number 1160 PHA 1170 JSR printbyte \ print track number 1180 PLA 1190 CMP finish \ is that the last track? 1200 BCC loop \ branch if more tracks to format 1210 LDX #another MOD 256 1220 LDY #another DIV 256 1230 JSR print \ another? 1240 JSR osrdch 1250 BCS escape 1260 PHA \ temp store for answer 1270 JSR osasci \ print answer 1280 JSR osnewl 1290 PLA \ pull answer 1300 AND #&DF \ upper case 1310 CMP #ASC("Y") 1320 BNE return 1330 JMP getdata 1340 .setup 1350 STX gap3 1360 STY numsectors 1370 LDX #39 1380 LDY track 1390 STY physical 1400 .setloop 1410 STA table,X 1420 DEX 1430 DEX 1440 DEX 1450 PHA 1460 TYA 1470 STA table,X 1480 PLA 1490 DEX 1500 BPL setloop 1510 .return 1520 RTS 1530 .print 1540 STX zeropage 1550 STY zeropage+1 1560 LDY #0 1570 .printloop 1580 LDA (zeropage),Y 1590 BEQ endprint 1600 JSR osasci 1610 INY 1620 BNE printloop 1630 .endprint 1640 RTS 1650 .printbyte 1660 PHA 1670 LSR A 1680 LSR A 1690 LSR A 1700 LSR A 1710 JSR nybble \ print MS nybble 1720 PLA 1730 JSR nybble \ print LS nybble 1740 LDA #ASC(" ") 1750 JSR oswrch \ print space 1760 JMP oswrch \ print space 1770 .nybble 1780 AND #&0F 1790 SED 1800 CLC 1810 ADC #&90 1820 ADC #&40 1830 CLD 1840 JMP oswrch \ print nybble and return 1850 .block 1860 EQUB &00 \ drive number 0-3 1870 EQUD table \ sector table 1880 EQUB &05 \ 5 parameters 1890 EQUB &63 \ format track 1900 .physical 1910 EQUB &00 \ physical track number 0 1920 .gap3 1930 EQUB &15 \ gap 3 1940 .numsectors 1950 EQUB &2A \ 10 sectors of 256 bytes 1960 EQUB &00 \ gap 5 1970 EQUB &10 \ gap 1 1980 .result 1990 EQUB &00 \ result byte 2000 .table 2010 EQUD &00000000 2020 EQUD &00010000 2030 EQUD &00020000 2040 EQUD &00030000 2050 EQUD &00040000 2060 EQUD &00050000 2070 EQUD &00060000 2080 EQUD &00070000 2090 EQUD &00080000 2100 EQUD &00090000 2110 .catalogue 2120 EQUB &15 \ disc title (disable VDU) 2130 OPT FNfill(7) \ 7 zero bytes 2140 EQUS "!BOOT $" \ next 8 bytes 2150 OPT FNfill(240) \ end of first sector 2160 OPT FNfill(5) \ start of second sector 2170 EQUB &08 \ number of files * 8 2180 EQUW &0A20 \ 10 sectors and *OPT 4,2 2190 EQUD &00 \ load and exec = &0000 2200 EQUW &0800 \ length = &800 bytes 2210 EQUB &00 \ MS 2 bits of sector number 2220 EQUB &02 \ starting at sector 2 2230 OPT FNfill(240) 2240 .catblock 2250 EQUB &00 \ drive number 2260 EQUD catalogue \ address of buffer 2270 EQUB &03 \ number of parameters 2280 EQUB &4B \ save data multi sector 2290 EQUB &00 \ logical track 2300 EQUB &00 \ start logical sector 2310 EQUB &22 \ 2 sectors of 256 bytes 2320 .catresult 2330 EQUB &00 \ result byte 2340 .title 2350 EQUB &0D 2360 EQUS "5 Sector DFS Format" 2370 EQUB &0D 2380 BRK 2390 .drivenum 2400 EQUB &0D 2410 EQUS "Drive number? (0-3) " 2420 BRK 2430 .tracknum 2440 EQUB &0D 2450 EQUS "40 or 80 tracks? (4/8) " 2460 BRK 2470 .ready 2480 EQUB &0D 2490 EQUS "Ready to format? (Y/N) " 2500 BRK 2510 .another 2520 EQUB &0D 2530 EQUS "Another? (Y/N) " 2540 BRK 2550 .track 2560 EQUB &00 \ physical track number 2570 .finish 2580 EQUB &00 \ last track number 2590 .lastbyte 2600 ] 2610 NEXT 2620 INPUT'"Save filename = "filename$ 2630 IF filename$ = "" THEN END 2640 *OPT1,2 2650 OSCLI("SAVE "+filename$+" "+STR$~(mcode)+"+"+STR$~(las tbyte-firstbyte)+" "+STR$~(start)+" "+STR$~(firstbyte)) 2660 *OPT1,0 2670 END 2680 DEF FNfill(size) 2690 FOR count = 1 TO size 2700 ?O%=0 2710 O%=O%+1 2720 P%=P%+1 2730 NEXT 2740 =pass There are a number of points worth noting about the program SECTOR5. The object code is used to create a 5 sector per track formatted disc and, for the sake of demonstrating the protection techniques, the object code will also be stored on the disc it formats. The program assembles the object code to run at PAGE+&100. This is so that the programs which will be used to store the object code on a protected disc and to read that code back from the disc can be located at PAGE. These saving and loading programs are very short and &100 bytes is more than enough room for them. When you design your own protection system you may need more space for your saving and loading programs. The formatting program introduced in module 3 created an empty catalogue which only contained the number of sectors available on the disc. The catalogue created by the program above actually contains a disc title, some file information and the start up option. The disc title is the ASCII character &15 which disables the VDU and so prevents the disc being catalogued. The dummy file $.!BOOT is entered into the catalogue and it is specified as &800 bytes long starting in sector &02. This means that the dummy !BOOT file uses all the available space on track &00. The number of available sectors on the disc is specified as &0A and so the catalogue and the dummy !BOOT file use all the available space on the disc. The start up option is equivalent to *OPT 4,2 so that the !BOOT file will be *RUN on Shift+Break. This formatting program produces the sector map shown in figure 1. You can see that it has not been optimised for speed and you may like to modify using the logical sector offsets described in module 3. DFS format physical sector numbers | 00 01 02 03 04 05 06 07 08 09 ---+--------------------------------------- T 00 | 00 01 02 03 04 05 06 07 08 09 r 01 | 01 02 03 04 05 Logical a 02 | 01 02 03 04 05 sector c 03 | 01 02 03 04 05 numbers k 04 | 01 02 03 04 05 s 05 | 01 ... Figure 1. The distribution of logical sectors --------------------------------------------- When the disc has been formatted you can use the program IDSDUMP (from module 0) to check the ID fields and the program VERIFY (also from module 0) to verify the format. The object code program FORM5, which is used to format the disc, can be stored on the newly formatted disc using the object code generated by the program ENCODE. Run ENCODE and choose a suitable filename for the object code it produces when prompted. For the sake of this demonstration I will assume that you will call it SAVE5. The object code program SAVE5 will save the &400 byte program FORM5 onto the first two logical sectors of track &01, storing the data as deleted data. *LOAD FORM5 and then type *RUN SAVE5. Swap the DFS disc for the newly formatted 5 sector per track disc and press the spacebar. The object code program FORM5 will be stored on the disc. You can use the program VERIFY to make sure that the data is sucessfully stored as deleted data. 10 REM: ENCODE 20 DIM mcode &100 25 page=PAGE 30 osrdch=&FFE0 40 oswrch=&FFEE 50 osword=&FFF1 60 FORpass=4 TO 6 STEP 2 70 O%=mcode 80 P%=page 90 [ OPT pass 100 .firstbyte 110 EQUW &FF0D \ NEW the BASIC program 120 .start 130 LDX #&00 140 .loop 150 LDA message,X 160 BEQ end 170 JSR oswrch 180 INX 190 BNE loop 200 .end 210 JSR osrdch 220 BCC save 230 BRK 240 BRK 250 EQUS "Escape" 260 BRK 270 .save 280 LDA #&7F 290 LDX #block MOD 256 300 LDY #block DIV 256 310 JSR osword \ write deleted data 320 LDA result \ load result byte 330 AND #&1E \ isolate error code 340 BNE error 350 BRK 360 BRK 370 EQUS "Write OK" 380 .error 390 BRK 400 BRK 410 EQUS "Write failed" 420 BRK 430 .block 440 EQUB &FF \ current drive 450 EQUD page+&100 \ start at PAGE+&100 460 EQUB &03 \ 3 parameters 470 EQUB &4F \ write deleted multi-sector 480 EQUB &01 \ logical track &01 490 EQUB &00 \ start logical sector &00 500 EQUB &42 \ 2 sectors of 512 bytes 510 .result 520 EQUB &00 \ result byte 530 .message 540 EQUS "Press Space to save data on current disc" 550 BRK 560 .lastbyte 570 ] 580 NEXT 590 INPUT'"Save filename = "filename$ 600 IF filename$ = "" THEN END 610 *OPT1,2 620 OSCLI("SAVE "+filename$+" "+STR$~(mcode)+"+"+STR$~(las tbyte-firstbyte)+" "+STR$~(start)+" "+STR$~(firstbyte)) 630 *OPT1,0 640 END The data stored on the disc using SAVE5 cannot be read back off the disc using any of the DFS star commands. The disc has been formatted so that a !BOOT file will be *RUN on Shift+Break and so the dummy !BOOT file entered into the catalogue by the formatting program needs to be replaced with a real !BOOT file which will load and run the program stored on track &01. The program DECODE will produce an appropriate machine code !BOOT file which simply reverses the Write Deleted Data operation which put the data onto the disc. When the deleted data have been reloaded into memory the command CALL (PAGE+&102) is inserted into the keyboard buffer, and BASIC is entered using Osbyte &8E. The VDU is disabled before inserting the data into the keyboard buffer (lines 460-470) and re-enabled before the command is executed (line 610). This hides what you are doing from the disc user. Any command, or series of commands, can be executed in this way so that BASIC programs as well as machine code programs can be stored on, and run from, the copy-protected disc. The optional code in lines 140 to 230 of DECODE can be used to further protect your software but you should start to think of your own ideas to incorporate with these techniques. Disc copy-protection is only useful if it is unique and very difficult to break. These programs are neither but they are a good introduction to the necessary techniques and the discs they produce cannot be duplicated with the *BACKUP command. 10 REM: DECODE 20 page=PAGE 30 DIM mcode &100 40 osasci=&FFE3 50 osword=&FFF1 60 osbyte=&FFF4 70 FORpass=4 TO 6 STEP 2 80 O%=mcode 90 P%=page 100 [ OPT pass 110 .firstbyte 120 EQUW &FF0D \ NEW the BASIC program 130 .start 140 \ LDA #&C8 \ write Break effect 150 \ LDX #&02 \ clear memory on Break 160 \ LDY #&00 170 \ JSR osbyte 180 \ LDA #&4C \ JMP opcode 190 \ LDX #&87 \ JMP &287 gives an 200 \ LDY #&02 \ endless loop on Break 210 \ STA &287 220 \ STX &288 230 \ STY &289 240 LDA #&7F 250 LDX #block MOD 256 260 LDY #block DIV 256 270 JSR osword \ read deleted data 280 LDA result \ load result byte 290 AND #&1E \ isolate error code 300 BNE error 310 LDA #&FF \ initialise offset 320 PHA \ save offset 330 .pull 340 PLA 350 TAX 360 INX \ increment offset 370 TXA 380 PHA \ store current offset 390 LDY keyboard,X \ load byte for keyboard buffer 400 BEQ continue \ branch if finished 410 LDX #0 420 LDA #&8A 430 JSR osbyte \ put byte into keyboard buffer 440 JMP pull 450 .continue 460 LDA #&15 470 JSR osasci \ disable VDU 480 LDA #&BB 490 LDX #0 500 LDY #&FF 510 JSR osbyte \ find BASIC 520 LDA #&8E 530 JMP osbyte \ select BASIC no return 540 .error 550 BRK 560 BRK 570 EQUS "Disc read error" 580 BRK 590 .keyboard 600 EQUS "CALL &"+STR$~(page+&102) 610 EQUB &06 \ enable VDU 620 EQUB &0D 630 BRK 640 .block 650 EQUB &FF \ current drive 660 EQUD page+&100 \ start at PAGE+&100 670 EQUB &03 \ 3 parameters 680 EQUB &57 \ read deleted multi-sector 690 EQUB &01 \ logical track &01 700 EQUB &00 \ start logical sector 0 710 EQUB &42 \ 2 sectors of 512 bytes 720 .result 730 EQUB &00 \ result byte 740 .lastbyte 750 ] 760 NEXT 770 PRINT'"Press Space to save !BOOT file" 780 REPEAT 790 UNTIL GET=32 800 *OPT1,2 810 OSCLI("SAVE $.!BOOT "+STR$~(mcode)+"+"+STR$~(lastbyte- firstbyte)+" "+STR$~(start)+" "+STR$~(firstbyte)) 820 *OPT1,0