11. FILES Floppy Write Test You are advised to switch the Write Verify test off by setting the system- variable _fverify (at address &H444) to 0: SPOKE &H444,0 ! test off SPOKE &H444,1 ! test on (default) According to experts like Dave Small and Bill Wilkinson the Verify test is a complete waste of valuable time if you write to a disk. Step Rate You will find the current step-rate of drive A with: PRINT DPEEK(&H440) ! system-variable seekrate The following values are possible: 0 - 6 ms 1 - 12 ms 2 - 2 ms 3 - 3 ms (default) For an external 5.25"-drive you probably have to use 6 or 12 ms. From TOS 1.4 onwards you can use XBIOS 41: PRINT XBIOS(41,drive,-1) ! current step-rate (0=A, 1=B) ~XBIOS(41,drive,rate) ! new step-rate RAM-disk Drive D is often reserved for a RAM-disk. GFA will recognize a RAM-disk with DFREE(4) only if it was already present at the time the interpreter was loaded. After switching off your 1040 ST you should wait at least 15 seconds before switching on again. Otherwise an old RAM-disk (or something else in RAM, e.g. a virus...) may still be present when you switch your computer on again. If a RAM-disk is not installed properly after a reset, the reason could be hidden in the drive bit-table at &H4C2 (system-variable _drvbits). The old TOS does not clear this table and a RAM-disk can only be installed as drive D if bit 3 is cleared. By the way, use drive C (bit 2) only for a harddisk. DFREE DFREE(0) could give the wrong number of bytes for the current drive if you use a cache, but this method always works: PRINT DFREE(drive) ! 1 - 15 PRINT DFREE(ASC(drive$)-64) ! "A" - "O" You probably missed drive P here. According to my GFA-manual DFREE can't be used for drive P, but I have not been able to test this. DIR$() Use GEMDOS 25 (Dgetdrv) to find the current drive and combine this with DIR$ to find the current path: path$=CHR$(GEMDOS(25)+65)+":"+DIR$(GEMDOS(25)+1)+"\" With DIR$(0) you'll find the last used path of the current drive. DIR$(1) returns the path of drive A, DIR$(2) the path of drive B, etc. GEMDOS kindly remembers the last used path for all available drives. If you run GFA-Basic from the main directory and load a GFA-program from a folder, DIR$(0) will return the nullstring (""), not the folder. After using CHDIR with the folder-name, the correct path will be returned. I use CHDIR in the Shell-program START.GFA, so the Standard Global default.path$ will contain the correct path of the GFA-program. This makes life easier if you want to load data-files from the same folder, but don't know the precise path when you write the program. It would be nice if you could determine the path of the running GFA-program in the program itself. You can make an array-table of available drives with the aid of BIOS 10 (Drvmap) and system-variable _nflops (&H4A6): DIM drive!(15) SELECT DPEEK(&H4A6) CASE 1 drive!(0)=TRUE CASE 2 drive!(0)=TRUE drive!(1)=TRUE ENDSELECT table%=BIOS(10) FOR n=2 TO 15 IF BTST(table%,n) drive!(n)=TRUE ENDIF NEXT n You can check if a harddisk is connected with: IF LPEEK(&H472)<>0 ! system-variable hdv_bpb harddisk!=TRUE ENDIF My harddisk-driver sets the system-variable hdv_bpb to 0, so I can't use this method myself. Is there a better way to check if a harddisk is available? DIR and FILES After DIR, FILES, TRON or DUMP you can slow down the scrolling with . You can temporarily stop the scrolling by holding down the right -key. The output after DIR fits on any screen, but the FILES-output is too wide for Low resolution. DIR will show only files in the current directory. FILES will also show folders (marked with *). With DIR (and FILESELECT) you will not be able to see "hidden" files or "system" files, but FILES will show all files. You can also search for hidden and/or system files with FSFIRST and FSNEXT by setting bit 1 and/or bit 2 of the attribute- byte (see paragraph 'FSFIRST and FSNEXT', page 11-4). Perhaps you have noticed that after the command FILES the first two lines are peculiar if you happen to be in a folder. The first "name" in a folder is always '.' (one dot) and the second always '..' (two dots). Time and date are incorrect, because the authors of (the old) TOS forgot to convert these to MS-DOS format. In case of nested folders, the operating system finds the preceding folder through a pointer of the '..'-file. That's why you can use 'CHDIR ..' to return to the preceding folder. Don't try this in the main directory, or you'll get an error. Talking about CHDIR, never use 'CHDIR ""' in a compiled program, because the nullstring will cause a crash. You can direct the FILES-output to a file with: FILES path$ TO file$ The directory can then be loaded with RECALL. You'll probably want to manipulate the format of the FILES-output: offset 0 - "*" if a folder, otherwise a space 1 - filename (with extension) 13 - a space 14 - file-length 22 - a space 23 - time as "hh:mm:ss" 31 - a space 32 - date as "dd.mm.yyyy" FSFIRST and FSNEXT The DTA-buffer is usually found at address BASEPAGE+128 (it's always there after start-up and after DIR or FILES), but you should not count on it. Use FGETDTA() to find the current address, before FSFIRST: dta.adr%=FGETDTA() ! current address of DTA-buffer e%=FSFIRST(format$,attr) ! search for first file/folder FSFIRST returns -33 if no file has been found. FSNEXT returns -49 if no more files are found. In an accessory it's safer to create a new DTA-buffer (the desktop will appreciate all this extra work): old.dta%=FGETDTA() ! old buffer dta$=STRING$(44,0) dta.adr%=V:dta$ ~FSETDTA(dta.adr%) ! new buffer (...) ! use FSFIRST/FSNEXT ~FSETDTA(old.dta%) ! restore old buffer The 44-byte DTA-buffer (Data Transfer Address) contains several file-data after a succesful FSFIRST or FSNEXT. You can extract filename, filelength, file-attributes, time and date from the buffer: fname$=CHAR{dta.adr%+30} flen%=LONG{dta.adr%+26} attr|=BYTE{dta.adr%+21} time%=CARD{dta.adr%+22} ftime$=RIGHT$("0"+STR$(SHR(ftime%,11)),2)+":" ftime$=ftime$+RIGHT$("0"+STR$(SHR(ftime%,5) AND &X111111),2)+":" ftime$=ftime$+RIGHT$("0"+STR$((ftime% AND &X11111)*2),2) fdate%=CARD{dta.adr%+24} fdate$=RIGHT$("0"+STR$(fdate% AND &X11111),2)+"." fdate$=fdate$+RIGHT$("0"+STR$(SHR(fdate%,5) AND &X1111),2)+"." fdate$=fdate$+STR$(SHR(fdate%,9)+1980) The DTA-buffer is not an exact copy of the relevant information in the directory of the disk. With a disk-editor you can find a slot of 32 bytes for each file or folder in the directory of the disk: offset 0- 7 - file- or folder-name (without extension) 8-10 - extension 11 - attribute-byte 12-21 - reserved 22-23 - time 24-25 - date 26-27 - FAT-pointer 28-31 - file-length The first byte of the filename has a special meaning in the following cases: &H00 - free slot, never used before &HE5 - erased file, now free slot &H2E - subdirectory Both time and date are stored in MS-DOS format. Consult your GFA-manual for more information. The FAT-pointer, also in an MS-DOS format (Intel- format: first low byte, then high byte), points to the first cluster of the file. If you are looking at a folder, the FAT-pointer points to the cluster where you will find the directory of this folder (subdirectory). If you are looking at a subdirectory (i.e. you are in a folder), the first two slots are reserved for the files '.' and '..' (&H2E and &H2E2E). This has already been mentioned in the paragraph 'DIR and FILES' (page 11-3). Finally, the file-length is stored in, you guessed it, MS-DOS format. You might wonder what MS-DOS has to do with Atari-disks. Read the paragraphs 'Disk Format' and 'File Allocation Table' for the explanation. Use the attribute-byte &X0 (i.e. no bits set) with FSFIRST/FSNEXT to find ordinary files only. If you use the attribute-byte &X10000 with FSFIRST/FSNEXT you will find both folders and files! If the folders in a directory don't have an extension and all files do have an extension, you could find all folders in the main directory as follows: e%=FSFIRST("\*",&X10000) ! and e%=FSNEXT() for next folders If your folders do have an extension, you can't use this simple method. You'll have to check after each FSFIRST/FSNEXT if it's a folder or a file: IF BTST(BYTE{dta.adr%+21},4) (...) ! yes, it's a folder ENDIF Use attribute-byte &X1000 to find the disk-name: dta.adr%=FGETDTA() e%=FSFIRST("\*.*",&X1000) ! finds disk-name only, not files disk.name$=CHAR{dta.adr%+30} You can read the attributes of a file or folder with GEMDOS 67 (Fattrib): attr%=GEMDOS(67,L:V:filename$,0,0) If the file (or folder) is not found, attr% is -33 (or -34), otherwise attr% contains the attributes in the usual format. You can even change the attributes of files (not of folders or the diskname) with: r%=GEMDOS(67,L:V:filename$,1,attribute%) EXIST You can use EXIST to test if a folder exists, but only if the folder contains at least one file: IF EXIST("\FOLDER\*.*") (...) ! folder found ELSE (...) ! folder not found or empty folder ENDIF If you want to include empty folders as well, you could use FSFIRST: e%=FSFIRST("\FOLDER",16) IF e%=-33 (...) ! folder not found ELSE (...) ! folder found (could be empty) ENDIF You can use EXIST also to find the length of a file: IF EXIST(file$) file.length%=LONG{FGETDTA()+26} ! read DTA-buffer ENDIF LOF The length of a file is easily determined with LOF: OPEN "I",#1,file$ file.length%=LOF(#1) CLOSE #1 But you probably are going to use EXIST anyway to test if the file exists, so you might as well use the method decribed in the EXIST-paragraph. To determine the number of records in a random file you could divide the file-length by the total FIELD-length. TOUCH Use this method with TOUCH: OPEN "U",#1,file$ TOUCH #1 CLOSE #1 NAME With the old TOS you can only change the name of files, not of folders. Even from the desktop you can't change the name of a folder, so choose it carefully. KILL KILLing a file does not erase it from the disk. The first byte of the filename in the directory is changed to &HE5. Unfortunately you can't restore a killed file by simply changing this byte with a disk-editor. The operating system will be able to find the first cluster of the restored file, because the first FAT-pointer is located in the directory. The next clusters can only be found through the FAT (File Allocation Table), but after KILL all pointers to this file are irreversibly erased. If you have not killed any file on the disk before your fatal mistake, you are extremely lucky and will find all clusters have been stored consecutively. But after some killing and saving on the disk, the file could be dispersed over the entire disk. Some programs are able to help you, but you will have to recognize clusters as belonging to the killed file. That's easy with ASCII-files, but almost impossible with other files. File Copy You can copy a file source$ to dest$ (use complete pathnames!) with: OPEN "I",#90,source$ OPEN "O",#91,dest$ block%=LOF(#90) WHILE block%>31744 ! 31 clusters of 1024 bytes PRINT #91,INPUT$(31744,#90); SUB block%,31744 WEND PRINT #91,INPUT$(block%,#90); CLOSE #90 CLOSE #91 There are slower methods to copy files, the desktop-copy (TOS 1.0) being a good example. Do not copy a file "to itself" on a harddisk. Thanks to yet another bug in TOS, this action could completely wipe out the harddisk. Or perhaps this should be called a feature of TOS, put there to punish the crazy user who tries to copy a file to itself. Disk Format A disk contains 80 concentric tracks (numbered 0 - 79) or more. Sometimes the expression "cylinder" is used instead of "track". Each track is divided into 9, 10 or even 11 sectors. One sector can contain 512 data- bytes. In order to be compatible with MS-DOS, TOS formats a disk with 80 tracks and 9 sectors/track. Actually it's easy to fit 10 sectors in one track. With a little more effort you can create room for 11 sectors, but some drives run slightly too fast and are not able to read the 11th sector! With a disk-editor you can examine the 512 data-bytes of a sector, but you can't examine the sector-layout without accessing the Floppy Disk Controller (FDC) directly. In that case you would find the following layout for each sector: data-separator (GAP) - 15 bytes ID-Address mark - 1 byte sector-header - 4 bytes (track, side, sector, size) CRC of sector header - 2 bytes data separator - 37 bytes Data-Address Mark - 1 byte data bytes - 512 bytes CRC of data bytes - 2 bytes data separator - 40 bytes The data separator bytes are there to synchronize the FDC properly. The FDC recognizes the sector-header by the preceding ID-Address mark. The sector-header itself contains information about the current track, side and sector and also about the size of the data-field (usually 512 bytes). The FDC checks both the sector-header and the data-field for corrupted bytes by comparing a computed "checksum" with the stored CRC-value. The operating system cannot read/write one byte from/to a sector, only complete sectors are read or written. GFA-Basic takes care of all the dirty work. First some bad news. A CRC-error is not always recognized by the ROM of 520 ST's and 1040 ST's (bug in XBIOS 8, Floprd). If your palms are now getting sweatty, you could check your most precious disks with XBIOS 19 (Flopver). This function checks for 'lost data, RNF- or CRC-errors'. Create a buffer of 1024 bytes and call XBIOS 19. A sector (512 bytes) is loaded from the disk in drive A or B (0 or 1) into the second part of the buffer and checked. If a bad sector is found, the sector-number is stored as a word in the first part of the buffer. After checking all sectors in one track you have to examine the word-list in the buffer. Hope you will find only &H0000 there. I leave the writing of this program as an exercise to the reader. Never thought I would use that phrase myself. Allright, here's something to get you started: buffer$=STRING$(1024,0) adr%=V:buffer$ r%=XBIOS(19,L:adr%,L:0,drive,1,track,side,9) ! if 9 sectors/track You should now be able to find out if the track on this side (0 or 1) is OK. Good luck. You can use BIOS 7 (Getbpb) to examine the disk-format in the so-called BIOS-Parameter-Block (BPB) of the disk: bpb.adr%=BIOS(7,drive) ! address of BPB, or 0 (= error) In 9 words you'll find the following information in the BPB: offset 0 - bytes/sector (usually 512) 2 - sectors/cluster (usually 2) 4 - bytes/cluster 6 - number of directory-sectors 8 - length of FAT 10 - first sector of second FAT 12 - first data-sector 14 - total clusters 16 - flag (not used) Use GEMDOS 54 (Dfree) to find out how many free clusters are available, or simply use DFREE if you want to know how many free bytes are available on a drive. Due to a bug, GEMDOS misses the last two clusters on a disk. You can't write to these clusters (2048 bytes down the drain...), but you can read these clusters if they do contain data. That would be a miracle, or an MS-DOS disk. You'll probably use XBIOS 10 (Flopfmt) to format a disk from GFA-Basic: r%=XBIOS(10,L:buffer%,L:0,0,spt,track,side,1,L:&H87654321,&HE5E5) If you do, use &H0000 as the virgin-value for the first two tracks (18 sectors), and then &HE5E5 for all data-sectors. You can use either 9 or 10 sectors/track (spt), not 11. If you use 10 spt, you could fill the first two tracks with &H0000 and fill sectors 19 and 20 with &HE5E5 afterwards (read paragraph 'Sectors'). The buffer should be (at least) 8192 bytes for 9 spt, probably 9216 bytes for 10 spt. I don't recommend more than 80 tracks, certainly not more than 82 tracks. If XBIOS 10 returns a value other than 0, you'll find a word-list of bad sectors in the buffer you used (terminated with &H00). If one of the first 18 sectors is bad, you can throw the disk away. Using XBIOS 10, the interleave-factor should be 1. This means the sectors on a track are numbered consecutively: 1,2,3,4,etc. The FDC needs more time to read a complete track if the interleave-factor is not 1. TOS loses time if the head moves to the next track. Because the Seek with Verify flag is set, the FDC first verifies the track-number and then reads the sector-number. While checking the track-number, sector 1 was passing, so we have to wait for one complete spin of the disk (at 300 rpm that's 200 ms, yawn) until sector 1 can be read. One solution is to clear the Seek with Verify flag, but that could lead to nasty problems if the head still rattles slightly at the time sector 1 is read. The best solution is the Twisted format (adopted by Atari from the Mega ST onwards). For a single-sided disk with 9 sectors on one track this means: track 0 : sector 1,2,3,4,5,6,7,8,9 track 1 : sector 8,9,1,2,3,4,5,6,7 track 2 : sector 6,7,8,9,1,2,3,4,5 etc. Now, sector 1 is encountered almost immediately after the track-number is verified. A 1-sector offset is not possible, but a 2-sector offset is enough to settle the rattling head. It is impossible to read data faster from a disk! But I'm afraid you can't format a Twisted disk with XBIOS 10 if you have an old TOS. You have to use a special format-program. From the Blitter-TOS you can use XBIOS 10: if interleave is -1 (instead of 1), the third parameter is used as a pointer to a word-table of sector-numbers. If you format a disk from the desktop, bad clusters are flagged with a special value in the FAT. However, if TOS 1.4 encounters a bad sector, something goes wrong and the FAT is corrupted. One more reason to quit smoking, because smoke-particles definitely constitute a serious hazard to the health of your disks. You can use XBIOS 18 (Protobt) to create a bootsector on the formatted disk. Don't worry about the media-byte (disk-type), because TOS doesn't use it. Do use &H1000000 to generate a random serial number, because it is very important that different disks have different serial numbers! TOS assumes you are creating a standard disk, so you should change the number of sectors per track (SPT: default 9) and the total number of sectors on the disk (NSECTS: default 9*80 or 9*80*2) in the sector-buffer if necessary: POKE buffer%+19,nsects AND &HFF POKE buffer%+20,nsects DIV 256 POKE buffer%+24,spt Read the paragraph 'Bootsector' for more information. Write the bootsector to the disk with XBIOS 9 (Flopwr), not with BIOS 4 (Rwabs): r%=XBIOS(9,L:buffer%,L:0,drive,1,0,0,spt) Why are different serial numbers so important? If TOS suspects a disk- swap, the serial number is read from the disk. A disk-swap can only be recognized if the number on the new disk is different from the old number. If the new disk contains the same serial number, TOS uses the FAT of the previous disk. Writing to a swapped disk will probably zap it, if you follow me. Disk-copiers copy everything, including serial numbers. Be careful! Although it is possible to format a disk from GFA-Basic, I don't recommend it (now he tells us...). My favourite format is: 80 tracks 10 sectors/track Twisted format My double-sided disks contain 807936 bytes. I use TWISTER.PRG (not in the Public Domain, as far as I know), but you could use a Public Domain program like DCOPY (actually Shareware). The desktop can't copy a Twisted disk (or any other non-standard format), but file-copy is always possible. DCOPY copies any format, including Twisted format. File Allocation Table (FAT) The first sector on a disk is the boot-sector. The next five sectors are reserved for the FAT. And the next five for a copy of the FAT (actually it's the other way around). Five sectors for a FAT really is too much, three sectors is sufficient. Finally, the main (or root) directory occupies the next 7 sectors (32 bytes for each slot, 16 entries/sector). Seven sectors for the main directory (112 files and/or folders) is quite a lot, five sectors (80 files/folders) should be enough in practice. This means that the first 18 sectors (No. 0-17) of a standard disk are reserved for the operating system, but you could reduce this to 12 sectors. All other sectors are available for storing files. The storage-unit for files is a cluster. A cluster consists of two consecutive sectors (1024 bytes). A file that contains only 1 byte will therefore still occupy 1024 bytes (1 K) on the disk. When a file is saved, the operating system looks for the first empty cluster, then the next, etc. Information about available clusters is stored in the FAT as a collection of pointers. Due to inefficient programming of TOS 1.0, the search for free clusters takes a long time. Try DFREE on a harddisk and you'll agree. Install a program like FATSPEED.PRG (Public Domain, by Ulrich Kuebler) to speed this up! The first three bytes of the FAT are not used by TOS, but are there to enable MS-DOS to read an ST-formatted disk. TOS writes &HF7FFFF, where the first byte (&HF7) is supposed to be the media-byte. Unfortunately MS-DOS doesn't understand this and refuses to read the directory properly. I'm not quite sure why, but changing the first three bytes to &H000000 seems to work. You could try &HF8FFFF (80 tracks, 9 sectors/track, single sided disk) or &HF9FFFF (double sided disk) instead. Or you could use the (correct!) media-byte at offset 21 from the bootsector. Of course the disk really should have 9 sectors/track, or MS-DOS could get confused. To make your MS-DOS friends completely happy, you should change the first three bytes of the bootsector to: &HEB3890. Sometimes (from MS-DOS 4.0 ?) you have to change the next three bytes as well: &H49424D (that's IBM in ASCII; please watch your language). Better still, let them use their own disk editor on their MS-DOS computer (e.g. Norton Utilities). Why should you do all the work? By the way, your ST should be able to use an MS-DOS disk without any modifications. Each FAT-pointer consists of one and a halve byte (3 nibbles, i.e. 12 bits). In hexadecimal notation this means three digits for one pointer. The first pointer (No. 0) points to cluster No. 0, the second to No. 1, etc. Because the first two pointers don't count, you have to subtract two to find the actual cluster (pointer 2 points to cluster No. 0, etc.). The first cluster starts at sector No. 18 (remember, sectors 0-17 are reserved for bookkeeping), so you could find the first sector of a cluster with: (pointer - 2) * 2 + 18 TOS reads the pointers in a peculiar (MS-DOS) way. Suppose the FAT-sector starts with: F7 FF FF 03 40 00 FF 0F 00 Without further explanation, this translates to: FAT-pointer 0 and 1 are ignored FAT-pointer 2 = &H003 (next cluster on sector 20 + 21) FAT-pointer 3 = &H004 (next cluster on sector 22 + 23) FAT-pointer 4 = &HFFF (last cluster of this file) FAT-pointer 5 = &H000 (free cluster) In order to understand this, you have to consult the following table: &H000 : cluster still available &HFF1 - &HFF7 : bad cluster, never available &HFF8 - &HFFF : last cluster of this file (End Of File) &H002 - &HFF0 : pointer to next cluster Assuming the FAT-pointer &H002 at offset 26 in the directory (you'll find &H0200 with a disk editor, that's Intel format with low byte first again), you should be able to figure out that this file will be found in sectors 18 through 23. Sectors 24/25 are empty, so this cluster is available for a new file. Any questions? The one big advantage of all this is compatibi lity with MS-DOS disks. From TOS 1.4 your ST-disks can be made completely compatible, so you don't even have to change a few bytes. Sectors There are two different methods to assign a number to a sector. The first one is to number the "physical" sectors in each track from 1 to 9 (assuming 9 sectors/track). This way you could say the bootsector is sector 1 on track 0 (on side 0 if the disk is double sided). But GEMDOS doesn't care about tracks or sides, it counts "logical" sectors from 0 to 719 (80 tracks, 9 sectors/track, one sided disk) or from 0 to 1439 (double sided disk). According to GEMDOS, the bootsector is on sector 0. And on a double sided disk, physical sector 1 on track 0 of side 1 (the other side) would be sector 9. With BIOS 4 (Rwabs) you can read (and write) complete logical sectors: buffer$=SPACE$(512) ! 512 bytes for 1 sector buffer%=V:buffer$ ! address of buffer r%=BIOS(4,0,L:buffer%,1,sector%,drive%) ! load the sucker You would load the bootsector from the disk in drive A with: r%=BIOS(4,0,L:buffer%,1,0,0) You can use BIOS 4 not only with floppy disks, but also with a harddisk or a RAM-disk. After loading a sector in the buffer, you can read one byte or word with: b|=BYTE{buffer%+i} ! i from 0 to 511 w&=WORD{buffer%+i} The latter only if the word starts at an even address. Otherwise you have to use: w&=BYTE{buffer%+i+1}+256*BYTE{buffer%+i} If necessary, you can speed this up by using the special integer commands for addition and multiplication. How about this Polish monster: DEFFN word(adr%)=ADD(BYTE{SUCC(adr%)},MUL(256,BYTE{adr%})) w&=@word(ADD(buffer%,i)) If you use BIOS 4 (Rwabs) to write a sector after formatting a disk, you should use '3' as a flag (not '1'): r%=BIOS(4,3,L:buffer%,1,sector%,drive%) You can also read and write physical sectors with XBIOS 8 (Floprd) and XBIOS 9 (Flopwr). With these commands you could even read/write all sectors on one track. This is the only way to read a track, because reading one complete track is impossible due to a bug in the FDC. If you swap a disk after loading/writing a sector you should be careful. Testing with BIOS 9 (Mediach) you could miss the disk-swap. This could be fatal, because TOS uses the FAT of the other disk! I think you could use BIOS 7 (Getbpb) in most cases, but use this if in doubt: ' Force media change x$=SPACE$(12) old.vector%=LPEEK(&H47E) ! system-variable hdv_mediach a%=V:x$ DPOKE a%,&H2B7C LPOKE a%+2,old.vector% DPOKE a%+6,&H47E DPOKE a%+8,&H7002 DPOKE a%+10,&H4E75 SLPOKE &H47E,a% ~DFREE(0) ! current drive To be more precise, BIOS 7 can't be used in the following situation: - TOS reads a sector - you swap disks - you use BIOS 7 in your program - TOS reads a sector Now TOS will assume there has been no disk-swap, because there has been no disk-swap after the last BIOS 7 call! You definitely need the decribed method here to force a media change before TOS reads a sector. By the track, TOS also ignores disk-swaps during a screendump (HARDCOPY), or while using DMA (harddisk, laser printer). TOS detects a disk-swap by monitoring the write-protect state. To see this, you should turn all lights off and then watch the drive-light closely. Closer. You can turn the lights on again. If the drive is empty, TOS gets a write-protect signal and assumes the user might have swapped disks. BIOS 9 should return '1' at this point. TOS checks if you really did swap disks by reading the serial number from the bootsector and comparing it with the current number. Only if these numbers are different, a disk-swap is recognized by TOS (BIOS 9 should return '2' now). You probably deduced that TOS will read the bootsector also if you use a write-protected disk. Continuously reading the bootsector is a waste of time, so TOS waits 1.5 seconds before looking again. Never swap disks within 1.5 seconds after a read/write- operation. The drive keeps spinning for 2 seconds, so you can't go wrong if you wait until the drive-light is off before swapping disks. Bootsector In the following table you'll find the lay-out of a bootsector. All words are in Intel-format, except CHKSUM. Standard values are mentrioned between parentheses. offset length name 0 2 &H6038 = branch to bootroutine 2 6 FILLER fill-bytes 8 3 SERIAL serial-number of disk 11 2 BPS bytes/sector (512) 13 1 SPC sectors/cluster (2) 14 2 RES reserved sectors (1, Bootsector) 16 1 NFATS number of FAT's (2) 17 2 NDIRS max. entries in main directory 19 2 NSECTS total sectors 21 1 MEDIA media-byte (not used by TOS) 22 2 SPF sectors/FAT (5) 24 2 SPT sectors/track 26 2 NSIDES sides (1 or 2; no joke this time) 28 2 NHID hidden sectors (ignored by TOS) 30 2 EXECFLAG start of bootcode: flag 32 2 LDMODE 0=load FNAME; <>0=load sectors 34 2 SSECT first sector (LDMODE<>0) 36 2 SECTCNT number of sectors 38 4 LDADDR load at this RAM-address 42 4 FATBUF address of FAT-buffer 46 11 FNAME filename (nnnnnnnneee) (LDMODE=0) 57 1 DUMMY fill-byte 58 boot-routine (could be a boot-virus) 510 2 CHKSUM TOS determines if the bootsector is executable by adding all bytes. If this sum (AND &HFFFF) equals &H1234, the bootsector is executable. If you use GFA-Basic this probably means you have an ancient ST with TOS in RAM, or a boot-virus. A normal GFA-disk has only &H00- or &HE5-bytes from the offset 58. BLOAD BLOAD needs an address, unless you have used BSAVE in the same program before. In that case the BSAVE-address is used automatically by BLOAD if you don't specify a new address. BLOAD (BSAVE) is easier to use than BGET (BPUT), because you don't have to open the file. But with BLOAD you can only load the entire file, while BGET allows you to load any part of the file. INP and OUT Both INP and OUT can also be used with words and (4-byte) integers: a|=INP(#n) ! byte a&=INP&(#n) ! word a%=INP%(#n) ! integer OUT #n,a| OUT& #n,a& OUT% #n,a% INPUT and LINE INPUT Because GFA uses a 4K-buffer for each opened file (from version 3.07), reading data from a file with (LINE) INPUT goes much faster. STORE and RECALL For very fast loading and saving of string-arrays, you should use RECALL and STORE. You can also store (or recall) a part of an array as follows: STORE #1,txt$(),10 ! store elements 0 through 9 STORE #1,txt$(),5 TO 10 ! store elements 5 through 10 The correct syntax is: STORE #i,x$()[,n[ TO m]] ! file must be open RECALL #i,x$(),n[ TO m],x% ! use n=-1 for complete array If you STORE a text-array, GFA puts &H0D0A (CHR$(13);CHR$(10)) after each element. This is the same ASCII-format as used by 1st Word Plus (WP Mode off), Tempus and other editors. If you are going to show a few text-lines on the screen you'll probably use PRINT. An alternative would be the use of DATA-lines: lines=0 RESTORE txt.data READ line$ REPEAT ! count number of lines INC lines READ line$ UNTIL line$="***" DIM text$(lines-1) RESTORE txt.data FOR n=0 TO lines-1 READ text$ text$(n)=SPACE$(5)+text$ ! add a left margin of 5 spaces NEXT n txt.data: DATA text DATA *** If you are going to show more text, I suggest you use 1st Word Plus, or any other wordprocessor or text-editor that can save your text as an ASCII-file. With 1st Word Plus, I use a Ruler length of 70 and the following Page Layout Form: Paper length 66 TOF margin 19 Head margin 4 Foot margin 4 BOF margin 19 Lines/page 20 Enter the text and save as an ASCII-file (turn WP Mode off before saving). In your GFA-Basic program you could load the text in a string-array: DIM text$(lines) OPEN "I",#1,file$ RECALL #1,text$(),-1,lines% ! lines% ó lines+1 CLOSE #1 I like to use a left and right margin of 5 characters, so that's why I use 70 characters/line in my wordprocessor. If the text-array is full, you won't get an error if the file contains more text-lines! You could use a 2-dimensional string-array to store first and last names: name$(i,0)=first_name$ name$(i,1)=last_name$ Again, STORE and RECALL are very fast. But it is now necessary to use exactly the same dimensions with RECALL that you used with STORE. If the dimensions don't match, the array will be scrambled after RECALL: OPEN "O",#1,file$ STORE #1,name$(),-1 CLOSE #1 ' OPEN "I",#1,file$ RECALL #1,name$(),-1,n% CLOSE #1 FILESELECT Don't use the underscore '_' in the path-line of the Fileselector, because a bug in the old TOS will then cause a few bombs. Owners of a Mega ST or TOS-version 1.4 can use as many underscores as they like. Changing drives in the Fileselector (old TOS) is not easy. Click on the path-line and press to clear the line. Enter the drive, e.g. 'D:', and click on the bar under the path-line in order to read the new directory. Also click on this bar after changing disks (in this case you would press on the desktop). Selecting drive A is easier: just clear the path-line and click on the bar. The TOS-code that takes care of all this work is also known as the bartender. If you have changed the extension in the path-line, you should click just below the bar. If you click on the bar, the path-line is overwritten with '*.*' ! That same bartender strikes again. The correct syntax for calling the Fileselector is: FILESELECT [#title$,]path$,default$,file$ The title (max. 30 characters, centered automatically) is used from TOS- version 1.4 and ignored in older TOS-versions. The default$ usually is the null-string (""), but don't use the null-string for path$ or the Fileselector will freeze. Use "\*.*" as path$ for all files in the main directory. Do use the backslash in the pathname (e.g. "A:\*.*"). Due to a bug in GEM, the wrong drive is sometimes used if you forget the backslash ("A:*.*"). If the user has selected a file, file$ will contain the path and filename. The file$ will be the null-string if the user selected . A third possibility is easily overlooked: the user could have selected without choosing a file. In that case file$ contains the current path, ending with a backslash. As a programmer, you should take into account the possibility that a user might start your program from drive A or B, a harddisk or a RAM-disk. I use the Standard Global default.path$ to remember where the program was started (paragraph 'The Standard', page 20). If the user changes the (default) path in the Fileselector, you should note the change and use the new path if the Fileselector is called again. If you have a joystick with Auto-Fire on, you should switch it off. The Fileselector doesn't like Auto-Fire. Neither do I. The Fileselector will warn you with a modest 'ping' if it counts more than 100 files/folders. It will show only the first 100 files/folders. I think 45 files in one folder is really the utter limit for impatient users like me. More than 100 is a crime that should be punished with more than just a little 'ping'. The main directory of drive A can't contain more than 112 files/folders, because the 7 directory-sectors contain 112 32-byte slots. Every time you open a folder (in the Fileselector, or otherwise), TOS stores information about the folder in a table. After opening/accessing 40 folders, TOS will delete clusters, cross-link clusters, and do other nasty things. Your disk could be completely destroyed, thanks to this bug. Atari enlarged the buffer in TOS 1.2 and fixed the bug in TOS 1.4. Atari also distributes the program FOLDRxxx.PRG to extend the 40-folder limit with 100 (FOLDR100.PRG) or more. Be careful with SHOW INFO, it's easy to exceed the 40-folder limit! You could recognize a disaster by one of the following symptoms: - unexpected '0 bytes in 0 items' message in directory - folder-names trashed (usually lots of Greek letters) - Show Info crashes or shows weird information Don't be afraid of a new virus. It's only a TOS-bug. Immediately reset your ST, try to salvage as many files as possible and reformat the disk. If all files are lost, you will have to use your back-up files. If you don't have back-up files, you have nothing left but my sympathy. If you use FILESELECT in a compiled program you need at least 32500 free bytes for the Fileselector. Procedures (CHAPTER.11) Accessory ACCESORY Activate accessory by changing .ACX to .ACC, or deactivate by changing .ACC to .ACX: @accessory(TRUE) ! activate one or more accessories The user is given the opportunity to perform a warm reset in order to install all active accessories. Attr_read (page 11-6) ATR_READ Read the attributes of a file: @attr_read(file$,ok!,read!,hidden!,system!,label!,folder!,archive!) The attributes are only valid if ok!=TRUE. Attr_write (page 11-6) ATR_WRIT Write attributes to file: @attr_write(file$,read!,hidden!system!,archive!,ok!) If ok!=TRUE the attributes of file$ have been changed. Dir_files (page 11-3) DIR_FILE Put names of all files, which are in a certain path and have a given extension, in a string-array: @dir_files("A:\GAMES\","GFA",directory$(),last&) The index of the last file is returned in last&. Any number of files could be accepted, the current limit is 100. Dir_folders (page 11-3) DIR_FOLD Same as Procedure Dir_files, but for folders: @dir_folders("A:","",directory$(),last&) Directory & Directory_read & Directory_calc (page 11-3) DIRECTRY Put all files and folders in certain path in string-array: @directory("A:\",d$(),n) Not only the names (including path) are returned, but also length, date and time: PRINT "File","Length","Date","Time" PRINT d$(0,0),d$(0,1),d$(0,2),d$(0,3) ! first entry (index 0) You could find the same data using FILES, but then you would have to write the directory to a file first. Disk_format (page 11-8) FORMAT Format a disk in drive A or B: @disk_format("A",2,80,9,112,5,"STANDARD.TOS",free%) This is a standard double-sided disk (2 sides, 80 tracks, 9 sectors/track, 112 directory-entries, 5 sectors/FAT) as created by TOS from the desktop. There are free% bytes available on the formatted disk. Disk_ibm & Disk_ibm_error (page 11-12) DISK_IBM Convert disk to IBM-format: @disk_ibm("A",ok!) Disk_info (page 11-15) DISKINFO Return information about disk (from bootsector): @disk_info("A",nsides,tracks,spt,spd,spf,ser%,name$,ex!,ibm!,ok!) Disk_newname (page 11-6) DSK_NAME Change the name of a disk in drive A or B: @disk_newname("A","GFAXPERT.001") That sounds easy, but it's quite difficult. Disk_number (page 11-11,11-15) DISKNUMB Change the serial number of a disk: @disk_number("A",ok!) Drive_speed (page 11-10) DRVSPEED Check the speed of drive A or B @drive_speed("A") ! should be 300 rpm Drives (page 11-2) DRIVES Makes an array-table of available dirves: @drives(driv!()) IF drive!(1) ' External drive B available ENDIF File_arch (page 11-6) FILEARCH Set or clear archive-bit of file: @file_arch(file$,TRUE,ok!) ! set archive-bit File_copy (page 11-8) FILECOPY Copy a file: @file_copy(FALSE,"C:\TEST.GFA","A:\") ! copy TEST.GFA from C to A @file_copy(FALSE,file$,"C:\BACKUP\LASTFILE.GFA") If the flag is TRUE, the source-file is deleted (= move file). If the destination-file already exists, the user may delete that file or rename it with a BAK-extension. File_copy_2 FILECOP2 The same as Procedure File_copy, but uses another copy-method. This Procedure appears to be faster with large files than File_copy. Try it. File_hide (page 11-6) FILEHIDE Hide file or make file visible: @file_hide(file$,TRUE,ok!) ! hide file File_load (page 11-16, 4-4) FILELOAD Load a file into a byte-array: @file_load(file$,music|(),adr%) The start-address of the data is adr%. ERASE could result in moving the byte-array in memory, so adr% would contain the wrong address! File_read (page 11-6) FILEREAD Make file read-only or make it read/write: @file_read(file$,TRUE,ok!) ! make file read-only Fileselect (page 11-18) FILE_SEL Use Fileselector with title (any TOS-version): @fileselect("Load a file...","\*.*","",file$) Fileselect_margins (page 11-18) FILE_SEM A Fileselector with some extras, especially for TOS 1.0 and 1.3: @fileselector(TRUE,"Choose a file:","*.*","","LEFT","RIGHT",file$) Now you can use a title, just as with TOS 1.4. And you can even add an optional title in the left and right margin of the screen. If the flag is TRUE, the original screen is restored. Only for High and Medium resolution. Folder (page 11-6) FOLDER Create a folder: @folder("A:\",TRUE) ! in main directory of drive A If flag is TRUE, the folder will become the default folder. The user enters the folder-name through the fileselector. Force_mediach (page 11-14) FMEDIACH Force a media change: @force_mediach("A") According to Frank Roos this should work, although the method differs from that described on page 11-14. Inarray INARRAY Search a string in an array: @inarray("GFA",1,0,text$(),column,line) ! start at (1,0) Similar to INSTR this Procedure finds a string in an array. The first column is 1, the first line is 0 (index 0 of the array). The position of the found string is returned in column& and line& (both 0 if search failed). If you would like to look further, you should call the Procedure again, with column& and line& as the new start-column and -line. Parse_filename PARSFILE Parse a file-name completely: @parse_filename(file$,d$,p$,f$,e$) PRINT "Drive","Path","File","Extension" PRINT d$,p$,f$,e$ PRINT file$ In most cases you can use the Functions File_name$ and File_path$ instead. Path_search (page 11-4) PATHSRCH Find the path of a file: @path_search("CHESS.GFA","A",path$) In case you forgot where a certain file is located. Returns complete file- path, e.g.: "A:\GAMES\CHESS.GFA" Text_array_init (page 11-17) TEXT_ARR Fill a (short) text-array from DATA-lines: @text_array_init(text$()) Text_load (page 11-17) TEXTLOAD Load a text-file (ASCII-text) into an array: @text_load(file$,200,text$(),lines%) ! maximum 200 text-lines The actual number of loaded text-lines is returned in lines%. You'll probably use Procedure Text_show next. Text_save (page 11-17) TEXTSAVE Save a text-array (ASCII-text) as a file: @text_save(file$,99,text$()) ! line 0-99 Text_show (page 11-17) TEXTSHOW Show an ASCII-text on the High or Medium screen: @text_show("TITLE",text$()) You'll see 20 lines of text on the screen. The following keys can be used: - page down - page up - go to first page - go to last page - find a string (enter string first) - continue string-search or - exit Procedure By international agreement you should use the above keys for these commands. Write_verify (page 11-1) WRVERIFY Turn Write Verify on or off: @write_verify(FALSE) ! turn Write Verify off Functions (CHAPTER.11) Archive_bit (page 11-6) ARCH_BIT Returns TRUE if archive-bit of file is set: PRINT @archive_bit(file$) Boot_check (page 11-15) BOOTCHCK Checks bootsector and warns user if bootsector is executable (could be a virus): IF @boot_check("A") (...) ! no boot-virus on disk in drive A ELSE (...) ! boot-virus? ENDIF Disk_free (page 11-2) DISKFREE Returns free bytes on disk: free.bytes%=@disk_free(FALSE,"A") If the flag is TRUE, the free bytes are shown in an Alert-box. Disk_name$ (page 11-6) DISKNAME Returns the disk-name (label): PRINT @disk_name$("A") You can change this name with Procedure Disk_newname. Disksector_load (page 11-13) SECTLOAD Loads one disk-sector in an INLINE-line and returns the address of that line: PRINT "Sector ";s;" of drive ";d;" loaded at: ";@disksector_load(d,s) Drive_write_protected WRITPRO2 Should return TRUE if drive is write-protected, but it doesn't do that for me. Let me know what's wrong. File_length (page 11-6,11-7) FILE_LEN Returns file-length: PRINT "File-length: ";@file_length(file$) File_load (page 4-6) FILE_MAL The safest way to load a file, but can be done only once in a program: adr%=@file_load(file$) (...) ~MFREE(adr%) ! do restore memory before leaving the program RESERVE File_name$ FILENAME Returns the file-name (without path): PRINT @file_name$("A:\GAMES\CHESS.GFA") Should return "CHESS.GFA" in this case. File_path$ FILEPATH Returns path (without file-name): PRINT @file_path$("A:\GAMES\CHESS.GFA") Should return "A:\GAMES\" in this case. Step_rate (page 11-1) STEPRATE Returns the step-rate of drive A: PRINT @step_rate Track_check (page 11-9) TRCKCHCK Returns TRUE if all sectors on a track are good: IF @track_check(0,0,0) ! drive 0 (A), side 0, track 0 PRINT "Congratulations" ELSE PRINT "Where is your back-up disk?" ENDIF Write_protected WRITPROT Returns TRUE if disk is write-protected: IF @write_protected("A") PRINT "Write-protected" ELSE PRINT "Not write-protected" ENDIF Should work on a normal ST and a Mega ST, but uses an illegal PEEK to determine the write-protect state. The Function Drive_write_protected doesn't look very legal either (and doesn't work on my ST), but at least Write_protected works on my ST.