Atari Diskfile Tutorial - Part III
Disk I/O is a very slow process for the Atari. The disk must be physically spun, which takes a while, and data must be shipped back and forth, which is even slower. The idea behind Atari's DOS is to minimize disk I/O. So we come to the concept of a "buffer."
In Atari's DOS, whenever you read or write to a file, you are reading/writing into a 128-byte reserved area in memory called a buffer. You are not writing to the disk at all. The Atari keeps the contents of one of the sectors of the disk in that buffer. So if you read/write to that buffer, the operation occurs at very high speed, which is what we want. For instance:
10 OPEN #1,4,0,"D1:FILE"
20 FOR A=1 TO 10000
30 GET #1,A
40 NEXT A
merely reads, byte by byte, 10000 bytes from disk. But if you run this program, you will note that the disk isn't being accessed continually; only every once in a while will you hear a beep to indicate another read. What happens is that the Atari opens the file and pulls the first sector full of data into the memory buffer. When you do the first 125 GETs, the bytes are pulled out of that memory buffer. Then you try to read another byte, but the Atari doesn't have that one in memory yet. So it requests the disk to send it the next 128 bytes, fills the buffer with those 128 bytes, and starts reading at the beginning of the buffer again.
(If you are wondering why I said the 125th byte, it is because the Atari DOS reserves three bytes per sector for its own uses, which we will talk about later.)
Similarly, when you PUT to a disk file, the Atari lets you PUT 125 bytes to the buffer, then dumps it to the disk, moves in another 125, and so on.
The result of all this confusion is that the Atari doesn't have to go to disk for every individual byte; rather, it stays in memory for a large number of "disk" accesses, and things run much faster.
When the Atari is LOADing or SAVEing a program, again it uses these buffers. The process is invisible to you, but you can hear the beep as each individual sector in the program is LOADed, or a clunk as a sector is SAVEd. Remember, the Atari can only talk to the disk in terms of complete 128-byte sectors.
Understanding the buffer is important in solving some of the mysterious problems that can occur while using the disk. For instance; let's say you write something out to the buffer, and then your program bombs. You examine the disk file, and find the data never reached the disk. This is because the buffer was never written to disk. If you didn't know about the buffer, you'd be wondering why the disk didn't record what you wrote to it.
The CLOSE Statement
The CLOSE statement is provided for this case. It makes sure that everything in the buffer gets to the disk. It also updates the directory, where all file names are listed.
The DOS program handling all this keeps a table of free sectors on the disk, by the way, and when the buffer fills up and a place is needed to store the 128 bytes, a sector number is taken from that table. When a file is deleted, its sectors are returned to that table. (The XXX FREE SECTORS message at the end of the DOS directory is determined by this).
So much for Basic I/O. Everything done with Basic I/O is a version of the above; everything goes through the 128-byte buffer.
5 REM SAMPLE PROGRAM TO DO DIRECT 6 REM DISK I/O. DAVID SMALL,12/21/81 7 REM 10 DCB=768:REM START OF DISK DC3 20 POKE DCB+1,1:REM DISK 1 30 POKE DCB+2,82:REM $52 = GET SECTOR 35 REM *** GET 128 BYTE OPEN BUFFER 40 DIM BUFFER$(128) 45 DIM CALL$(10) 50 FOR X=1 TO 128:BUFFER$(X)=" ":NEXT X 60 ADDR=ADR(BUFFER$) 70 ADDRHI=INT(ADDR/256) 80 ADDRLO=ADDR-(ADDRHI*256) 90 POKE DCB+4,ADDRLO:REM BUFFER ADR LOW 100 POKE DCB+5,ADDRHI:REM BUFFER ADR HI 110 REM *** SECTOR NUMBER *** 120 PRINT "INPUT SECTOR NUMBER" 130 INPUT SECTOR 140 SECTORHI=INT(SECTOR/256) 150 SECTORLO=SECTOR-(SECTORHI*256) 160 POKE DCB+10,SECTORLO 170 POKE DCB+11,SECTORHI 200 REM *** BUILD SHORT ASSY PROGRAM 210 REM *** OF PLA, JMP DSKINV. 230 CALL$(1)=CHR$(104):REM PLA 240 CALL$(2)=CHR$(32):REM JSR 250 CALL$(3)=CHR$(83):REM $53 260 CALL$(4)=CHR$(228):REM $E4 270 CALL$(5)=CHR$(96):REM RTS 300 X=USR(ADR(CALL$)) 310 DSTATS=PEEK(DCB+3) 320 PRINT "DISK STATUS=";DSTATS;" (1=OK)" 330 PRINT "DATA:" 340 PRINT BUFFER$ 341 REM *** CLEAN OUT BUFFER$ 342 FOR X=1 TO 128:BUFFER$(X)=" ":NEXT X 350 GOTO 110 400 END
How about DOS?
When you issue the COPY disk command from DOS, each sector of the file is sent to memory. In other words, DOS copies the contents of that file into memory, 128 bytes at a time. It then takes the file in memory and writes it out to disk, from memory, again in 128 byte chunks.
When you issue a Duplicate Disk command, DOS reads in every sector on the disk that is marked as "used" and stores it in memory. The unused sectors are ignored to save time. DOS then writes those used sectors out to the new disk, in the same position it found them on the old one, and doesn't worry about the empty sectors. Since there are 92,000 bytes on a disk, and only 48K maximum of memory, it may have to do this in smaller pieces--let's say 32K at a time. You then must physically change the disk several times on a single-drive system. If you are using a multiple disk system, you will see it do the copy in multiple stages.
Note: If you are copying disks for backups, remove any cartridge; each cartridge selects 8K of RAM and makes for more disk swaps.
In order to know what sectors are used, and what files are on the disk, some tables must be kept on that disk. The DOS reserves certain sectors on the disk for these tables. One is known as the VTOC (Volume Table of Contents) and is sector number 360. In it is a table of all 720 sectors on disk, stored as bit-map; i.e., one bit per sector.
The disk directory is stored in sectors 361-368; this is the actual list of the file names and the sectors they occupy. To find a file, DOS must use this directory.
By the way, these reserved sectors explain why only 707 sectors are available on a "clean" disk; the rest are used for the directory tables, etc. You will also hear, during the disk formatting process, right near the end, the clunks as DOS writes out a fresh directory to the disk, after the disk 6507 has finished formatting it.
Remember, DOS at the lowest level, past all of the directory opens, XIO's, and so forth, is only doing get sectors/put sectors. Let's learn about those calls.
Get Sector/Put Sector Calls
All get sector/put sector calls rely on a table called the Device Control Block. Data is put into this table, and a jump is made into the operating system, which in turn uses that table. Table 1 is the disk table, starting at $300 (or 768 decimal).
One POKEs values into this table, and then does a JSR to DSKINV ($E453). DSKINV then does the requested operation and returns to you. Program 1 is a sample program that does the needed POKEs and requests from you a sector number (1-720) to read.
This program POKEs the DCB parameters, reserves 128 bytes in memory as a string for the buffer area, requests the sector number, then runs a very short assembly program which does a PLA, needed by Basic, then jumps off to DSKINV. DSKINV then does the operation, and returns to Basic.
There the status code placed in the table by DSKINV is printed, and the buffer area, as a string, is also printed. Sure, it will be strange, but you can examine individual bytes easily. If you read one of the directory sectors (try 361) you will see the directory entries.
If an 87 instead of an 82 were POKEd into DCOMND, then a Put Sector would occur ($57=Put Sector, $52=Get Sector). Then the contents of that buffer would be written to disk.
The status code will be a 1 if all goes well. If there is a problem, you may find a 144 as the error code. This Device Not Done Error occurs if the disk is had, the disk is write protected, etc.
We have just done disk access the same way DOS does it, at the sector level. Note we have not done an OPEN or an XIO; we have directly accessed the disk. This can be handy if you have a need for a disk allocation scheme free of DOS; I use it to store directly fixed-length records. It is fast and efficient, and removes the DOS overhead.
Also, it enables you to access the directory and modify it as necessary; this allows you, for instance, to un-delete files.
Because we are not using DOS, but are still accessing the disk, we are not dependent on the DOS directory or "In-Use-Table." We can read or write any sector on the disk, regardless of what DOS thinks that sector is for. You may want to take note: This is how to write a "boot record," a special disk record that enables your disk to boot by itself, as so many games do. The boot record is record 1; see the hardware manual for further details.
Now that you can access the directory data directly, you can see all the data stored rather than just what OPENing "*.*" returns. A directory entry is 16 bytes long for each file. There are eight entries per sector in the directory, and eight directory sectors, hence 64 files maximum. See Table 2.
As an example of how the directory is used, if a file is LOCKed from DOS, all that occurs is that its flag byte has bit 5 set to 1.
When you delete a file, all that occurs is that the delete flag on the file is set. Then, next time DOS needs a place to put a new file entry, it knows it can overwrite the current entry.
Reading the Data into a File
In order to read the data in this file, one goes to the Starting Sector number found in the directory. Each sector contains information as shown in Table 3.
The "forward pointer" is where the next sector of the file can be found. It is a sector number from 1 to 720. When that next sector is read in, it in turn tells where the next sector can be found, and so on. This is called a "linked list" or chained sector scheme. This way, files don't have to be in any particular order on the disk (e.g., running continuously from sector 30 to 40). The sectors can jump around all over the disk, yet to DOS they are still linked together.
The file number is the file number in the directory. It is set to the same number as the entry number in the directory for all sectors. This is a safety measure. Let's say we are reading directory entry 6, and are going along, sector by sector. In the file number position in a given sector, we suddenly find a 13 (or whatever) instead of the 6 that should be there.
We know that the data for this sector has gotten scrambled, and the sector chain terminates there. This is an ERROR 164, which I am sure you have seen before; it is a warning that the data in this sector are likely to be bad. The sector link is also likely to be bad, so DOS normally stops an ERROR 164.
Reading in sectors one at a time, by getting the next sector number, reading, and so on, is called "sector tracing." Atari DOS does it every time it reads in a file. Atari also sells a "disk fix" program through their program exchange which uses the above information; you now know enough to use it. For instance, the forward "link" of a given section may be modified if desired.
We can do the same sector tracing out of Basic. We will find a given directory entry, read it in starting at the sector number given by the directory and proceed through each sector, with each new sector number obtained from the data in the previous one, until we have read in the number of sectors specified in the directory. Should anything be wrong (file number, etc.) we will know the file has gone bad.
Program 2 illustrates first how to read in the directory and interpret it, and second how to follow a sector chain for a given file. It reads in the directory, lists it to the screen, and asks which file to trace; if A is entered, all files will be traced (this takes a while). It will then trace the file through, stopping at any "broken links" or bad file numbers. It is an excellent way to check a disk which has some files going bad, and find out which ones are still readable and which aren't. After each check the program produces a directory listing showing which files are good, bad, or still unknown.
The techniques used within the program will probably prove more useful than the program; however, remember that this is an example. It can serve as the basis for custom disk inspect/modify routines if you wish to write them. Also, the program is written in MicroSoft Basic but translating it to the old Basic should be no problem at all. I highly recommend MicroSoft Basic in terms of speed and features as compared to the old Basic; an indepth review of the Atari version of MicroSoft Basic will appear in an upcoming issue.
Sadly, the software pirate community also found out about direct disk I/O, literally years ago. Back then manufacturers were copy protecting disks by fouling up the directory and sector maps so that DOS couldn't perform the copy. With the advent of direct disk I/O, all these schemes were bypassed with what is known as a "sector copier."
10 REM DISK CHECKER -- DAVE SMALL 20 REM 30 GOTO 290 40 REM SUBROUTINES PLACED IN FRONT 50 REM FOR SPEED. NO COMMENTING. 60 REM 70 REM DISK READ SUBROUTINE 80 POKE DCB+1,DFROM 90 POKE DCB+2,82 100 BHI=INT(BUFF/256):BLO=BUFF-(BHI*256) 110 POKE DCB+4,BLO 120 POKE DCB+5,BHI 130 SHI=INT(RSECTOR/256):SLO=RSECTOR-(SHI*256) 140 POKE DCB+10,SLO:POKE DCB+11,SHI 150 X=USR(CALL) 160 DSTATS=PEEK(DCB+3) 170 RETURN 180 REM DISK WRITE SUBROUTINE-(SAMPLE) 190 POKE DCB+1,BLO 200 POKE DCB+2,87 210 BHI=INT(BUFF/256):BLO=BUFF-(BHI*256) 220 POKE DCB+4,BLO 230 POKE DCB+5,BHI 240 SHI=INT(RSECTOR/256):SLO=RSECTOR-(SHI*256) 250 POKE DCB+10,SLO:POKE DCB+11,SHI 260 PRINT "ERROR":GRAPHICS 0 270 DSTATS=PEEK(DCB+3) 280 RETURN 290 REM DISK CHECKER. 300 REM DOES SECTOR TRACE OF ALL 310 REM FILES IN THE DIRTECTORY 320 REM PRODUCES LISTING WITH OK/ 330 REM BAD. 340 REM ASSUMES 48K, MICROSOFT. 350 REM 360 REM GET DISK NUMBER, WELCOM.--- 370 GRAPHICS 0 380 CLEAR 390 PRINT "WELCOME TO SMALL'S DISK CHECKER." 400 PRINT "INPUT DRIVE NUMBER"; 410 INPUT DF$ 420 IF DF$="" THEN DFROM=1:GOTO 440 430 DFROM=VAL(DF$) 440 DTO=DFROM 450 CALL=&E453 !DSKINV ADDRESS 460 REM NOTE: MICROSOFT CAN CALL 470 REM DSKINV$ DIRECTLY, NO PLA 480 REM NEEDED. 490 DCB=&300 500 OPTION RESERVE 20*128 510 B=VARPTR(RESERVE) 520 IF B<0 THEN B=B+65536 530 REM FIRST, READ IN DIRECTORY ---- 540 PRINT "GETTING DIRECTORY." 550 INCR=0 560 FOR RSECTOR =360 TO 368 570 BUFF=B+(128*INCR) !INPUT AREA 580 GOSUB 70 !GET SECTOR 590 IF DSTATS=1 THEN 630 600 PRINT "***DISK RETRY, DIRECTORY SECTOR ";RSECTOR 610 PRINT "***STATUS=";DSTATS 520 GOTO 570 630 INCR=INCR+1 640 PRINT INCR; 650 NEXT RSECTOR 660 PRINT 670 PRINT "WAIT -- BUILDING TABLES." 680 REM OK, NOW EXAMINE DIRECTORY. 690 REM PRODUCE LISTING FIRST OFF. 700 REM NOTE 360=VTOC, NOT DIR. 710 DIM INFO(64,6) !FILE INFO 720 DIM NIME(64) !NAME INFO 730 DIM DEAD(64) !FILE BOMBED? 740 FOR Z%=1 TO 64:DEAD(Z%)=0:NEXT Z% 750 REM 760 Z=B+128 !START OF DIR ENTRY 770 REM BUILD FLAG & INFO TABLE------ 780 FNUM=1 790 FOR LOC=Z TO Z+(64*16) STEP 16 800 REM LOC=ENTRIES' STARTING ADDR 810 FLAG%=PEEK(LOC) 820 IF(FLAG% AND &80)=&80 THEN INFO(FNUM,3)=1 830 IF(FLAG% AND &40)=&40 THEN INFO(FNUM,4)=1 840 IF(FLAG% AND &20)=&20 THEN INFO(FNUM,5)=1 850 IF(FLAG% AND 1)=1 THEN INFO(FNUM,6)=1 860 REM SECTOR INFO---------------------- 870 INFO(FNUM,1)=PEEK(LOC+1)+256*PEEK(LOC+2) 880 INFO(FNUM,2)=PEEK(LOC+3)+256*PEEK(LOC+4) 890 REM NOW GET NAME DATA..------------- 900 NIME$(FNUM)="" 910 FOR Z%=5 TO 15 920 NIME$(FNUM)=NIME$(FNUM)+CHR$(PEEK(LOC+Z%)) 930 NEXT Z% 940 REM CHECK FOR END OF ENTRIES 950 IF PEEK(LOC+5)=0 THEN 1020 960 REM 970 FNUM=FNUM+1 980 IF FNUM/5=INT(FNUM/5) THEN PRINT FNUM; 990 NEXT LOC 1000 PRINT "DIR TOTALLY FULL." 1010 GOTO 1030 1020 PRINT "DIR PARTLY FULL." 1030 FNUM=FNUM-1 1040 PRINT 1050 PRINT "NUMBER OF ENTRIES=";FNUM 1060 REM OUTPUT DIRECTORY 1070 REM ENTRY POINT (FROM BELOW) 1080 IF FNUM=0 THEN 1320 !EMPTY DISK 1090 PRINT 1100 PRINT "D=DELETED/U=USED/L=LOCKED/O=OPEN" 1110 PRINT "SC=SECTOR COUNT(LEN)" 1120 PRINT "SS=STARTING SECTOR(LEN)" 1130 PRINT " + = GOOD FILE / - = BAD FILE / ? = UNKNOWN" 1140 PRINT 1150 FOR F=1 TO FNUM 1160 PRINT USING "##";F; 1170 IF DEAD(F)=-1 THEN PRINT "+"; 1180 IF DEAD(F)=1 THEN PRINT "-"; 1190 IF DEAD(F)=0 THEN PRINT "?"; 1200 PRINT "!";NIME$(F);"!"; 1210 IF INFO(F,3)=1 THEN PRINT "D"; ELSE PRINT " "; 1220 IF INFO(F,4)=1 THEN PRINT "U"; ELSE PRINT " "; 1230 IF INFO(F,5)=1 THEN PRINT "L"; ELSE PRINT " "; 1240 IF INFO(F,6)=1 THEN PRINT "O"; ELSE PRINT " "; 1250 PRINT "!SC:"; 1260 PRINT USING "###";INFO(F,1); 1270 PRINT "!SS:"; 1280 PRINT USING "###";INFO(F,2) 1290 IF F/20=INT(F/20) THEN INPUT RET$ 1300 NEXT F 1310 GOTO 1390 1320 PRINT "EMPTY DISK -- NO ENTRIES" 1330 STOP 1340 PRINT 1350 REM ENTRY POINT (BELOW) 1360 PRINT "PRESS RETURN." 1370 INPUT RET$:CLS:GOTO 1420 1380 GOTO 1400 1390 PRINT "PRESS RETURN." 1400 INPUT RET$ 1410 REM NEXT IS ENTRY POINT 1420 PRINT "SELECT :" 1430 PRINT "1. A FILE NUMBER TO CHECK;" 1440 PRINT "2. 'A' TO CHECK ALL FILES;" 1450 PRINT "3. 'D' TO SEE DIRECTORY LIST AGAIN" 1460 PRINT "NOTE: ERRORS SHOW UP ON DIR LISTING " 1470 PRINT "AFTER THEY OCCUR IN SEARCH." 1480 INPUT N$ 1490 IF N$="A" THEN FILE=128:GOTO 1690 1500 IF N$"D" THEN GRAPHICS 0:GOTO 1070 1510 ON ERROR 1540 1520 FILE=VAL(N$) 1530 GOTO 1560 1540 PRINT "ERROR. RE-ENTER." 1550 RESUME 1420 1560 REM --- PROCESS. CHECK. 1570 IFFILE>FNUM OR FILE<0 OR FILE<>INT(FILE) THEN PRINT "ERROR.":GOTO 1420 1580 REM SINGLE ENTRY HANDLER 1590 PRINT "TRACING=='SELECT' TO ABORT." 1600 SS=INFO(FILE,2) ! STARTING SECT 1610 SC=INFO(FILE,1) ! SECTOR COUNT 1620 RSECTOR=SS 1630 REM GO TRACE IT ...ARROR=RETURN 1640 GOSUB 1850 1650 IF ARROR=1 THEN PRINT "***BAD FILE***" 1660 IF ARROR=1 THEN DEAD(FILE)=1 ELSE DEAD(FILE)=-1 1670 GOSUB 2110 ! MARK FILE NAME 1680 GOTO 1350 1690 REM ALL ENTRIES HANDLER 1700 FOR FILE=1 TO FNUM 1710 CLS 1720 PRINT "TRACING FILE #";FILE 1730 IF LP=1 THEN PRINT #3,"TRACING FILE #";FILE 1740 PRINT "PRESS 'SELECT' TO ABORT." 1750 SS=INFO(FILE,2) ! STARTING SECT 1760 SC=INFO(FILE,1) ! SECTOR COUNT 1770 RSECTOR=SS 1780 GOSUB 1850 1790 IF ARROR=1 THEN PRINT "***BAD FILE***" **" 1800 IF ARROR=1 THEN DEAD(FILE)=1 ELSE DEAD(FILE)=-1 1810 GOSUB 2110 1820 NEXT FILE 1830 PRINT "COMPLETED." 1840 GOTO 1350 1850 REM TRACING SUBROUTINE 1860 ARROR=0 1870 PRINT 1880 PRINT "---SEC CNT=";SC;" READING SEC ";RSECTOR 1890 POKE &D01F,0 ' CLEAR BUTTONS 1900 GOSUB 70 'READ 1910 REM CHECK BUTTONS 1920 IF PEEK(&D01F)<>7 THEN RETURN 1930 IF DSTATS=1 THEN 1960 1940 PRINT "***ERROR. DSTATS=;DSTATS;" ON SECTOR ";RSECTOR 1950 GOTO 1900 !RETRY 1960 IF SC=0 THEN RETURN 1970 SC=SC-1 ! DECR S COUNT 1980 REM GET FORWARD POINTER + FNUM 1990 FWD=(PEEK(BUFF+125) AND 3)*256 2000 FWD=FWD+PEEK(BUFF+126) 2010 FN=INT((PEEK(BUFF+125) AND &FC)/4) ! WENCE, SWIFTED 2 BITS RIGHT 2020 FN=FN+1 ! (DISK STARTS AT 0) 2030 PRINT "---DATA: FILE NUM=";FN;" FWD SEC =";FWD 2040 IF FN<>FILE THEN PRINT "FILE NUMBER ERROR.":ARROR=1 2050 IF FWD>720 OR FWD<0 THEN PRINT "FORWARD PTR OUT OF RANGE,ABORT.:ARROR=1:RETURN 2060 IF FWD=0 THEN 2090 2070 RSECTOR=FWD 2080 GOTO 1870 2090 PRINT "COMPLETED." 2100 RETURN 2110 REM MARK A FILE AS BAD IN NAME. 2120 REM SET HIGH BIT SO IT SHOWS 2130 REM UP IN INVERSE VIDEO.. 2140 IF ARROR=0 THEN RETURN 2150 Z$=NIME$(FILE) 2160 NIME$(FILE)="" 2170 FOR Z=1 TO LEN(Z$) 2180 NIME$(FILE)=NIME$(FILE)+CHR$((ASC(MID$(Z$,Z,1)) OR &80)) !SET HIGH BIT 2190 NEXT Z 2200 RETURN
As a side note, a sector copier is sometimes advertised as a "nibble copier" or "byte copier." This is an Apple term, and refers to individual bytes being read off the disk. It is completely inaccurate when applied to the Atari; all Atari disk I/O is in 128-byte blocks.
A sector copier, as you have guessed, just issues a read of all the possible sectors, then writes them.
The program is simple to write now that you understand about sector I/O and how to call up DSKINV. It is just a matter of reading in 720 sectors and writing out 720 sectors. As I said, about 20 lines of Basic code are required. And now you understand why, as I mentioned last month, selling such a program, at the prices currently being charged, is such a ripoff.
But ...how can a disk be protected against this sort of thing? Fortunately, there is a good way. Several software manufacturers hit on the same idea at the same time: bad sectoring. Let's assume we have a special disk, with some sectors on it that have never been formatted, as with the FORMAT DISK command. Most of them are OK; just a few are bad, as if there were holes in the disk. Now when the disk is told to read these sectors, it can't find them, for they were never formatted. So it returns an ERROR 144, instead of a normal return (1). Then, in the software on the disk, that sector is called, and if an ERROR 144 doesn't result, the program quits.
Let's say the program is then copied to another disk by an average sector copier. The "bad" spots on the disks will not copy; they will return ERROR 144, but on the destination disk, there will still be formatting information on those sectors. This is because the average Atari user cannot create a disk with bad spots in it; the FORMAT command is handled completely inside the disk by the disk 6507 microprocessor. The program will find that where it expected a bad sector, it now finds a good sector. Hence the program knows it is not residing on the original disk, and can quit.
The only way to defeat this scheme is to disassemble code and remove the sector check, and even if the author is slightly clever, it will take so long to find the sector checking routine that the program will be outdated by the time it is finally made copyable. Besides, consider what a person's time is worth compared to the cost of a program; if it takes two weeks to break a copy protection scheme, isn't it a better idea just to buy the program?
This bad-sectoring scheme works quite well in preventing a sector copier from being useful. Sure, a copy of the disk may be made, but without the bad spots on the disk, it will never run. And your average user cannot create these bad spots, for only access to the disk controller program allows that, and only very advanced users can accomplish this.
Atari currently uses this bad sector scheme in several of their disk-based programs. Jawbreakers uses the same scheme (hence the disk retry, or "snaaark," sound when you first boot it up). The Wizard and The Princess use it also. In other words, the move is toward this sort of protection.
Software manufacturers can probably figure out a way to write some bad sectors onto a disk. For instance, what popular personal computer gives you complete control over the disk read/write process? There are other ways, too.
Another manufacturer doesn't bother copy protecting his disks at all. However, a small module must be plugged into the front joystick port before the program will run. The module probably contains some sort of ROM-type circuit which is then read by the program through the PIA chip (joystick ports).
Well, we have covered a great deal, from piracy philosophy to sector chaining in these two columns. I hope you have enjoyed it and have learned something about the Atari disk, and how it really works. I also hope I have described some effective protection schemes for software manufacturers.
Direct disk I/O is a very powerful tool for Atari users; use it to make your programs faster and more efficient.