FILE I/O INTRO
Manipulate any ST disk fileby PATRICK BASS, Antic ST Program Editor
Hold an ST disk in one hand and slide the metal shutter open so you can see the disk inside. Look at it very closely. Can you see the tiny letters and numbers stored on the disk? Of course not. But they are there, and this month I'm going to show how you too can inspect and change any legal file on any disk.
There are two different operation sequences we will cover. First we will select, open, and read a file into memory. Then we will modify the file and write it back to the disk. We will do all this within GEM using C.
THE FIRST PART
The GEM environment provides a standard way to examine the disk directory and select a filename through the AES call fsel_input().
Before the call is made, you must prepare string space for a pathname and filename, and prepare a memory location to store a returned "button" value. The pathname is a string describing which directory GEM should open. (e.g. "a: * . *"). The filename can be as long as eight characters, plus a dot (.), and up to three more characters as an extender. So you will need to save string space for at least 12 characters. (e.g. "filename.ext"). The button value should be declared as an integer. (Note: all data types in this article-except strings-will be assumed to be integers, unless otherwise noted.)
When you call fsel_input( path, filename, &button );, the familiar file selector box appears onscreen and GEM takes over control of the computer until you click on either the OK or CANCEL buttons. The path and filename you passed to fsel_ input() show up in the "directory" and "selection" lines, respectively. Every time the user changes the path or clicks on a new filename, the path and filename strings are updated.
When the user clicks on one of the exit buttons, OK or CANCEL, the call is terminated, the selector box vanishes, the number of the button pushed (0 = CANCEL, 1 = OK) gets placed into its previously reserved memory location. The user will find the desired path and filename in their respective strings. At this point we have collected all the information needed to properly open a file.
Actually opening a file is very easy. First, since the routine that opens the disk file will return a unique number that we will use to reference the file with, (the read_handle), we need to reserve a place for it. The call to open the file itself takes the form: read_ handle=Fopen( filename, option ), where filename is the filename collected from the fsel_input() call, (or a string literal), and option can be one of: 0= read, 1= write, 2= read/ write.
When this call is made, the ST will locate and open the file requested in filename. If the file can't be found, a negative error number will be returned. However, if the file is found the routine will assign and return a unique positive number we need to save in read_handle. The file is now open and ready for use.
READ TO ME ONLY
Now, before we can read in bytes from an open file, we need to take care of three other things. First, decide on an amount that is equal to, or greater than the total bytes you need to read in. Here we'll call it max_len. This variable must be a LONG.
Second, reserve or initialize a char array that is at least as long as max_len. For our demonstration we'll call this array file_buffer.
Third, declare a long variable to hold the total number of bytes actually read. We'll call this one bytes_read.
Now, to read the file, all the above components come together as: bytes_read = Fread( read_ handle, max_len, file_buffer );, which will read up to max_len bytes into file_buffer out of disk file read_handle and return the total number of bytes actually read in bytes_read.
When finished working on the file in RAM, we need to write the file back onto the disk. As with the read routine, we first need to select a filename to write to, using the call fsel__input(). If you wish, you may use the same path, filename, and button memory space you set up for reading. When you return from the fsel__ input() call, determine what type of file you need to create. The choices are: 0 = Read/Write, 1 = Read Only, 2 = Hidden from directory search, 4= system file, 8= volume label. These attributes are described fully in Disk Secrets, ST Resource, February 1986.
The actual call to create a disk file is Fcreate(), and it takes the form: file__handle = Fcreate( filename, filetype );, where filetype is as described in the previous paragraph, filename is selected from within fsel_input(), and file_handle is a memory location we will use to store the unique number Fcreate() returns to us. Later, we will use this number to identify the file we write to.
When we've created the file we now need to write our information to it from file_buffer. The call to write to a disk file takes the form: result = Fwrite( file_handle, bytes to_write, file_buffer );.
Here file_buffer is the array in which we have our information, bytes_to_write is a long value- repeat, a LONG value-that describes how many bytes to write. We recieved file_handle from Fcreate(), and result is the value Fwrite() returns to us. This will be negative if an error occurred while writing, or it will be a positive number describing the number of bytes actually written to disk.
When finished writing, we need to properly close the file using the call Fclose( file_handle );. Again, file_handle is the variable returned to us in Fcreate().
We've brought all of the above concepts together in this month's ST program. You may use either Developer's or MegaMax C to type this program in. No changes are required when using either language. (Sorry, Hippo-C owners, this program won't work for you without major revision.) Type in and save the listing, then compile and link your program together. Antic Disk subscribers can find the source code on the monthly disk as FILEIO.C. See the disk's ST Help File for instructions on transfering ST listings to a 31/2 inch disk.
What we're going to do in the program is read a file off the disk, then globally swap one character for another before writing the corrected file back to disk.
Once you've got the program compiled, linked and ready to go, double click and run it. First an identification alert box appears, then the program will present an alert box asking for the character that needs replacing. Click on the or "+32" buttons to change the ASCII value by whatever amount, then click "select" to go on. Follow the same operations to select the replacement character. And now, here come the file selector boxes. We select the file to be searched, then the file the result will be written to. When finished, the program will present a "thank-you" box and then exit.
Examine Listing 1. Again, this program can be written in either Alcyon or MegaMax C. At the top are the opening comments. We also have one #include file here, osbind.h, which is included with both languages. Below that we have a few #defines, to make the source code a bit more readable. Pay close attention to the last one, DELAY. I have included it mainly to show that we can #define not only single characters, but entire statements. Next come typical int declarations, char strings, and three long definitions (including max_len as described above.).
The main() routine is fairly short this month, and pretty much does what it says: initialize(). It then selects the characters to work on, the files to work on, and performs the work. When finished becomes TRUE, we terminate() the application.
The first routine here, pick_character(), presents an alert box that describes the current character selected. It will stay in the loop literally while( button does_not_ equal SELECT). If the user clicks on either the "+ 1" or "+ 32" buttons, the program logic adjusts the character count accordingly and stays in the loop. The next routine, pick_replacement(), operates in exactly the same manner.
Notice the last half of the while...wend loop. These three routines first convert the stored value for the current character into a two-digit hexadecimal number, and then check to see if the current character can indeed be placed on the screen. If not then it is replaced with a printable space.
Next we get to read_the_file(). First we alert the user that we are going to read a file. The Dgetdrv() call returns a number (0,1, 2. . . ) describing the current drive used-or the last drive accessed. We use this number as an offset to the file path by adding the ASCII value of "A" to it, resulting in "A," "B," "C," etc. The next call, fsel_input( path, in_filename, &button); presents our file selector box. The next line down checks to see if the button selected was CANCEL. If it wasn't we use the Fread() call to read in up to max_len bytes into our array, file-buffer. We then Fclose() the file and leave.
The next routine, convert__the__ file(), scans through the array file-buffer looking for the replacable character and replacing it with the desired new character.
... AND HAVING WRIT...
Now we come to write_the_file(), which basically works like read__the__file() above. We select a file from the file selector box, and use the filename gathered there to Fcreate() a file on the disk, which we then write out with the Fwrite() call. Remember to use a LONG value for the number of bytes to write.
Finally, terminate() does just that, by saying goodbye, closing the virtual workstation, and exiting the application.
Listing 1 FILEIO.C