"Jesus is coming... Look busy!"
YOUR SECOND GFA BASIC 3.XX MANUAL
- or -
HOW I LEARNED TO STOP WORRYING AND LOVE GFA-BASIC
PART 12
CHAPTER ELEVEN - FILES
by Han Kempen
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 <CapsLock>. You can temporarily stop the scrolling by
holding down the right <Shift>-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').
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'. 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 sect/tr
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 compatibility
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, Bootsec)
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 here)
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 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 <Esc> 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 <Esc> 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
<Cancel>. A third possibility is easily overlooked: the user
could have selected <OK> 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'). 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 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 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 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 DIR_FOLD
Same as Procedure Dir_files, but for folders:
@dir_folders("A:","",directory$(),last&)
Directory & Directory_read & Directory_calc 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, idx 0
You could find the same data using FILES, but then you would
have to write the directory to a file first.
Disk_format 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 DISK_IBM
Convert disk to IBM-format:
@disk_ibm("A",ok!)
Disk_info DISKINFO
Return information about disk (from bootsector):
@disk_info("A",nsides,tracks,spt,spd,spf,ser%,name$,ex!,
ibm!,ok!)
Disk_newname 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 DISKNMB
Change the serial number of a disk:
@disk_number("A",ok!)
Drive_speed DRVSPEED
Check the speed of drive A or B
@drive_speed("A") ! should be 300 rpm
Drives DRIVES
Makes an array-table of available dirves:
@drives(driv!())
IF drive!(1)
' External drive B available
ENDIF
File_arch FILEARCH
Set or clear archive-bit of file:
@file_arch(file$,TRUE,ok!) ! set archive-bit
File_copy 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 FILEHIDE
Hide file or make file visible:
@file_hide(file$,TRUE,ok!) ! hide file
File_load 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 FILEREAD
Make file read-only or make it read/write:
@file_read(file$,TRUE,ok!) ! make file read-only
Fileselect FILE_SEL
Use Fileselector with title (any TOS-version):
@fileselect("Load a file...","\*.*","",file$)
Fileselect_margins 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 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 FMEDIACH
Force a media change:
@force_mediach("A")
According to Frank Roos this should work, although the method
differs from that described earlier.~
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 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 TEXT_ARR
Fill a (short) text-array from DATA-lines:
@text_array_init(text$())
Text_load 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 TEXTSAVE
Save a text-array (ASCII-text) as a file:
@text_save(file$,99,text$()) ! line 0-99
Text_show 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:
<Down Arrow> - page down
<Up Arrow> - page up
<ClrHome> - go to first page
<Shift><ClrHome> - go to last page
<Control><F> - find a string (enter string
first)
<Control><G> - continue string-search
<Esc> or <Control><Q> - exit Procedure
By international agreement you should use the above keys for
these commands.
Write_verify WRVERIFY
Turn Write Verify on or off:
@write_verify(FALSE) ! turn Write Verify off
Functions (CHAPTER.11)
Archive_bit ARCH_BIT
Returns TRUE if archive-bit of file is set:
PRINT @archive_bit(file$)
Boot_check 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
Needless to say, this routine will tell you there might be a
virus on *any* executable disk.
Disk_free 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$ DISKNAME
Returns the disk-name (label):
PRINT @disk_name$("A")
You can change this name with Procedure Disk_newname.
Disksector_load 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 FILE_LEN
Returns file-length:
PRINT "File-length: ";@file_length(file$)
File_load 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 STEPRATE
Returns the step-rate of drive A:
PRINT @step_rate
Track_check 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.
Disclaimer
The text of the articles is identical to the originals like they appeared
in old ST NEWS issues. Please take into consideration that the author(s)
was (were) a lot younger and less responsible back then. So bad jokes,
bad English, youthful arrogance, insults, bravura, over-crediting and
tastelessness should be taken with at least a grain of salt. Any contact
and/or payment information, as well as deadlines/release dates of any
kind should be regarded as outdated. Due to the fact that these pages are
not actually contained in an Atari executable here, references to scroll
texts, featured demo screens and hidden articles may also be irrelevant.