Low Level Filesystem Access
RAM File System
(Significantly extended from "Published RAM File Handling Routines" in "Inside the TRS-80 Model 100" by Carl Oppedahl)
File List/Pointer Cache
User files reside in RAM starting at the lowest installed RAM address, with BA files at the bottom, followed by DO files and CO files. The directory is located in high memory from $F962 to $FA8B. The directory is terminated by a sentinel value $FF byte. Thus there are 297 bytes in the table itself; dividing by 11 yields 27 slots. The format of the filename information is given in table 18.1.
|0||Directory flag (see table 18.2)|
|1-2||Cached file start pointer|
|3-10||Eight-byte file name|
|7||0 if a killed file|
|6||1 if a DO file|
|5||1 if a CO file|
|4||1 if located in ROM|
|3||1 for invisible file|
|0||Internal use only (known to be used by LNKFIL)|
Notice that no entry exists for filesize. To determine filesize, either of two techniques may be used. By comparing the various start addresses, one can determine the next file's starting address. The two start addresses may simply be subtracted.
However, the filenames in the directory are not in order by start address, so one must search the entire directory to find the next file up. The ending address of the top file is stored at FBB2+.
Another way to determine file size is by examining the files themselves, since the file itself always contains enough information to determine its size. With BA and DO files, you must scan the file, beginning at the start address, to locate the end-of-file marker. For DO files, the EOF characters is $1A (26 decimal). For BA files, it is a three character sequence, 00H,00H,00H.
For CO files, there is no end-of-file character as such, but the length of the file is determined by adding the value of the third byte to the product of 256 times the fourth byte, plus six.
Keep in mind that the cached file start pointer is not necessarily up-to-date at any given time. Some operations do not update it, even BASIC language operations like SAVEM can leave a stale file start pointer. The LNKFIL routine must be used to assure that the pointers are up-to-date.
Suzuki, Hayashi and RickY
Note the difference between the number of slots you can see on the screen, 24, and the number of directory entries, 27. That means there are 3 hidden entries.
If you are experimenting with a Model 100 (as apposed to T102), sooner or later you will stumble across the NUL-prefixed names Suzuki, Hayashi and RickY in the directory.
Hayashi ($F9A4) and RickY ($F9AF) are .DO files. Hayashi is the PASTE buffer. RickY is the current BASIC line when in EDIT mode.
Suzuki ($F999) is the location of the BASIC program, if any, that has not yet been SAVEd and therefore does not have a name. (It is the file you sometimes see listed as BASIC*.) Sometimes you select BASIC from the main menu and find that some lines are still present from the last time you were in BASIC; these lines were hiding in Suzuki.
Suzuki and Hayashi take up memory, of course, like any other files. Suzuki can be emptied by entering BASIC and typing NEW. Hayashi can be emptied by copying a zero-length selection to the PASTE buffer. RickY should only exist while the EDIT command runs.
On the T102, these names were removed. Reportedly, programmers embedding their name in the ROM was considered bad form by Tandy.
A DO file's first character is that which would be returned if you accessed it with TEXT or with BASIC's OPEN and INPUT statements. The file ends with the $1A described above.
BA File Format
A BA file in RAM is set up as a sequence of lines, each with the following form:
|2 bytes||Address of the line number to follow (little endian)|
|2 bytes||Current line number (little endian)|
|Numerous bytes||Program line in tokenized form|
|1 byte||Null (00H)|
The file ends with two more nulls (00H), making the last three characters nulls.
CO File Format
The format of a CO file is as follows:
|2 bytes||Address to load file to (little endian)|
|2 bytes||Number of bytes to load (little endian, six fewer than the size of the RAM file, because of these six address bytes)|
|2 bytes||Transfer (start) address|
|many bytes||Contents of file|
BASIC's PEEK statement can be used to examine the file contents without the danger of altering the file. The use of POKEs with files, however, is discouraged unless you know precisely what you are doing. If you must POKE, keep the following in mind.
- If you find that CHKDC or similar call does not find the file where it actually is, a call to LNKFIL may be required to update the cached start pointers.
- POKE only within the files, not elsewhere in RAM. In particular, do not POKE to addresses at or above 62960 (F5F0).
- If you create an end-of-file (EOF) marker within a DO file, you must delete any characters from there up to the beginning of the next file. Use MASDEL, described below. To grow a file, use MAKHOL.
- If, within a BA file, you tamper with any of the file format addresses described above in memory, such as the BASIC address of the following line number, the program will no longer function properly.
Published ROM Calls for Manipulating RAM Files
Most of the following routines can be used only to manipulate DO files in RAM.
The routine MAKTXT, called at 220F, creates a DO file in the RAM directory with the name specified in the buffer at FC93 through FC98. If the file already exists, the routine returns with the carry flag set.
Upon exiting the routine, the HL register points to the TOP address of the new file (the address at which new characters would be added), and DE points to the address of the directory file flag, located somewhere in the directory starting at F962.
The routine CHKDC, called at 5AA9, examines the directory to determine if a specified filename is present in the directory as a valid file. Prior to the call, DE should point to the first in a series of memory addresses (RAM or ROM) containing a filename in ASCII followed by a null.
(Jhoger 22:39, 4 April 2009 (PDT) note: I've found that I need to call LNKFIL before calling CHKDC if the file I want to access was recently created by SAVEM)
Upon return, the Z flag is set if no such file was found. If the file was found, HL points to the directory entry.
A complementary routine, GTXTTB, called at 5AE3, finds the TOP address of the file assuming its location in the directory is known. If you provide the address of the directory entry in HL, it returns the TOP address of the file.
The routine KILASC, called at 1FBE, kills a DO file. Prior to the call, DE must be set to the starting address of the file, and HL must be set to the address of the directory entry. Everything above it in user memory (CO files and other DO files) is moved down to fill in the space.
The routine INSCHR, called at 6B61, inserts one character in a DO file, Prior to the call, HL points to the address at which to insert the character, and A contains the character to be inserted. Everything above in user area (all CO files and some DO files) is moved up one position.
If there is no more room in RAM, the routine returns with the carry flag set.
The routine MAKHOL, called at 6B6D, inserts a specified number of spaces in a file. Everything is moved up in memory to accommodate the spaces. These spaces may later be changed as desired. Prior to the call, BC must contain the number of spaces to be inserted, and HL must point to the address at which to insert the spaces. HL and BC are preserved, which is handy. If there was insufficient room in RAM, the routine returns with the carry flag set.
The routine MASDEL, called at 6B9F, deletes a specified number of characters from a file. Prior to a call, BC must contain the number of characters to be deleted, and HL should point to the address at which to begin deleting. HL and BC are preserved.
LNKFIL starts at $2146. It is used to correct the file descriptor table (directory) pointers after adding or deleting file data following a call to MAKHOL or MASDEL.
Each file in RAM has an entry in the directory. Along with the name and a flags byte, each entry has a pointer to where the file starts in RAM. Various file operations add, delete, grow, and shrink files in RAM. Many operations, however, do not update these pointers, and leave them to be fixed up later, by calling LNKFIL. It is not clear at what times the system will call LNKFIL.
When LNKFIL is called, it walks all files in RAM and updates the pointers in the file descriptor table to match.
For LNKFIL to operate properly, aside from the filesystem not being corrupted, two conditions must be met:
- The file section pointers must be accurate
- The directory entries, though out of order and never actually sorted, if sorted by start address MUST ALWAYS result in an identical ordering to the order of actual files in RAM.
LNKFIL is dependent on the file section pointers:
File Section pointers:
FBAEH - Pointer to the start of the DO files (2) FBB0H - Pointer to the start of CO files (2)
FBB2H - Pointer to the start of variable table (2) FBB4H - Pointer to the start of array table (2) FBB6H - Pointer to the start of the systems unused memory (2)
To maintain accuracy of section pointers, follow the discipline of only using MAKHOL or MASDEL to wedge open space or remove file bytes, respectively, rather than attempting to do it in some ad hoc fashion.
To avoid corrupting the RAM filesystem, make sure to call LNKFIL before looking up a file entry if there is any chance that the filesystem has been modified without a fixup by LNKFIL. Also, always add a file to the end of a given file section. Basically, call MAKHOL with the value of the pointer to the subsequent section.
There are a number of pointers in high memory that are used to demarcate the area used for
- BASIC files (BA)
- Text files (DO)
- Machine language files (CO)
- The three scratch pads (Suzuki, Hayashi, RickY)
These pointers need to be modified according to what you need to insert or delete. For example, if you add a .CO file, then you don't need to modify the pointers that deal with the lower file space. (Jhoger 20:27, 6 April 2009 (PDT): I think you would still need to call MAKHOL to avoid smacking up BASIC variable region) But, adding a .BA moves all the pointers.
To create a file you need to
- Find an unused slot in the directory table
- Wedge open space in the filesystem for the new file; the hole must be opened between files or at the beginning or end of the memory region set aside for the given file type (BA, DO, CO). The order of files within a given memory region/file type must be the same as files in the directory for the given file type.
- Load all the appropriate data into that memory hole, representing the actual file data
- Complete the directory entry
- Run the LNKFIL ($2146) routine that walks all files and updates the directory's start pointers
Deleting a file is similar.
The fundamental assumption is that the order of the files must be maintained within each file type.