home *** CD-ROM | disk | FTP | other *** search
Text File | 1992-11-11 | 65.5 KB | 1,809 lines |
- =============================================================================
- LITTLE RED READER: MS-DOS file reader for the 128 and 1571/81 drives.
-
- by Craig Bruce <csbruce@neumann.uwaterloo.ca>
-
- 1. INTRODUCTION
-
- This article presents a program that reads MS-DOS files and the root directory
- of MS-DOS disks. The program copies only from drive to drive without
- buffering file data internally. This is simpler and imposes no limits on the
- size of the files transferred, although it requires the use of two disk drives
- (or a logical drive). The user-interface code is written in BASIC and
- presents a full-screen file selection menu. The grunt-work code is written in
- assembly language and operates at maximum velosity.
-
- The Burst Command Instruction Set of the 1571/81 is used to read the MS-DOS
- disk blocks and the standard kernel routines are used for outputting the
- data. (I am an operating systems specialist, so I call it a kernEl!) Thus,
- the MS-DOS files must be read from a 1571 or 1581 disk drive, but the output
- device may be any disk drive type, the screen or a printer, or a virtual drive
- type such as RAMLink, RAMDrive, or RAMDOS (for the REU). It is interesting to
- note that the data can be read in from an MS-DOS disk faster than it can be
- written out to a 1571, 1581, or even a RAMDOS file. A RAMLink can swallow the
- data only slightly faster than it can be read.
-
- Little Red Reader (LRR) supports double density 3.5" disks formatted with 80
- tracks, 9 sectors per track, and 2 sides with a 1581 and 5.25" double density
- disks formatted with 40 tracks, 9 sectors per track, and 2 sides with a 1571.
- A limit of 128 directory entries and 3 File Allocation Table (FAT) sectors is
- imposed. There must be 2 copies of the FAT and the cluster size may be 1 or 2
- sectors. The sector size must be 512 bytes.
-
- Oh, about the name. It is a play on the name of another MS-DOS file copier
- available for the C-128. "Little" means that it is smaller in scope than the
- other program, and "Red" is a different primary color to avoid any legal
- complications. It is also the non-white color of the flag of the country of
- origin of this program (no, I am not Japanese). Also, this program is Public
- Domain Software, as is all software I develop for 8-bit Commodore Computers.
- Feel free to E-mail me if you have questions or comments about this article.
-
- 2. USER GUIDE
-
- LOAD and RUN the "lrr.128" BASIC program file. When the program is first run,
- it will display an "initializing" message and will load in the binary machine
- language package from the "current" Commodore DOS drive (the current drive is
- obtained from PEEK(186) - the last device accessed). The binary package is
- loaded only on the first run and is not reloaded on subsequent runs if the
- package ID field is in place.
-
- 2.1. MAIN SCREEN
-
- The main screen of the program is then displayed. The main screen of the
- program will look something like this:
-
- MS-DEV=9 MS-TYPE=1581 CBM-DEV=8
-
- NUM S TRN TYP FILENAME EXT LENGTH
- --- - --- --- -------- --- ------
- 1 * ASC SEQ HACK4 TXT 120732
- 2 BIN PRG RAMDOS SFX 34923
-
- D=DIRECTORY M=MS-DEV F=CBM-DEV Q=QUIT
- T=TOGGLE-COLUMN, C=COPY-FILES, +/- PAGE
-
- except that immediately after starting up, "<no files>" will be displayed
- rather than filenames. The "MS-DEV" and "MS-TYPE" fields give the device
- number and type of the drive containing the MS-DOS disk to copy from, and the
- "CBM-DEV" gives the device number of the drive/virtual drive/character device
- to copy file data to.
-
- Information about all MS-DOS files in the root directory of the MS-DOS disk is
- displayed in columns below the drive information. "NUM" gives the number of
- the MS-DOS file in the directory listing, and "S" indicates whether the file
- is "selected" or not. If the file is selected, an asterisk (*) is displayed;
- otherwise, a blank is displayed. When you later enter Copy Mode, only the
- files that have been "selected" are copied.
-
- The "TRN" field indicates the character translation scheme to be used when the
- file is copied. A value of "BIN" (binary) means no translation and a value of
- "ASC" (ascii) means the file characters are to be translated from MS-DOS ASCII
- (or "ASCII-CrLf") to PETSCII. The "TYP" field indicates the type of
- Commodore-DOS file to create for writing the MS-DOS file contents into. The
- possible values are "SEQ" (sequential) and "PRG" (program). The values of the
- TRN and TYP fileds are set independently, so you can copy binary data to SEQ
- files and ascii data to PRG files if you wish.
-
- The "FILENAME" and "EXT" fields give the filename and extension type of the
- MS-DOS files and "LENGTH" gives the exact length of the files in bytes. Note
- that if you perform "ASC" translation on a file, its PETSCII version will have
- a shorter length.
-
- 2.2. USER COMMANDS
-
- The bottom of the screen gives the command summary. After starting the
- program, you will want to setup the MS-DOS and CBM-DOS drives with the "M" and
- "F" commands. Simply press the (letter) key corresponding to the command
- name to activate the command. Pressing M will prompt you for the MS-DOS Drive
- Number and the MS-DOS Drive Type. In both cases, type the number and press
- RETURN. (Sorry for insulting all non-novices out there, but I want to be
- complete). The MS-DOS drive number cannot be the same as the CBM-DOS drive
- number (since the program copies from drive-to-drive without internal
- buffering). For the drive type, enter an "8", "81", or "1581" for a 1581
- drive or anything else for a 1571 drive.
-
- Pressing F will prompt you for the CBM-DOS device number. You may enter a
- number from 0 to 30, except that it must not be the MS-DOS drive number.
- Enter a "1" for Cassette Drive (God forbid!), a "3" for the screen, a "4" for
- the printer (with an automatic secondary address of 7 (lowercase)), any number
- above 7 for a Commodore disk drive or special virtual drive, or a value of "0"
- for the special "null" drive. A CBM-DEV value of 0 will case the program to
- read MS-DOS files and do nothing with the output. You can use this feature to
- check out the raw reading speed of the program.
-
- After setting up the drives, press D to read in the root directory off the
- MS-DOS disk. The data will come blazing in from the disk but BASIC will take
- its good ole time sifting through it. Filenames are displayed on the screen
- as they are scanned in. The program will (eventually) return to the main
- screen and display the formatted file information. One note: the process of
- logging in a 1581 MS-DOS disk takes about 12 seconds (on my 1581, anyway), so
- be patient. An MS-DOS disk will have to be "logged in" every time you change
- MS-DOS disks. (Disks are logged in automatically).
-
- A couple of notes about accessing MS-DOS disks: don't try to access a device
- that is not present because the machine language routines cannot handle this
- error for some reason and will lock up, requiring a STOP+RESTORE. Also, make
- sure that an actual MS-DOS disk is loaded into the drive. If you accidentally
- place Commodore-DOS disk into the MS-DOS drive, the 1581 will report an
- invalid boot parameters error (#60), but a 1571 will lock up (since I don't
- check the sector size and my burst routines are expecting 512 bytes to come
- out of a sector whereas Commodore disks have only 256 bytes per sector).
-
- Now you are ready to pick what files you want copied and how you want them
- copied. You will notice that a "cursor" appears in the "S" column of the
- first file. You may move the cursor around with the cursor keys: UP, DOWN,
- LEFT, RIGHT, HOME, and CLR. CLR (SHIFT-HOME) will move the cursor back to the
- first file on the first screen. You can move the cursor among the select,
- translation, and file-type columns of all the files. Pressing a SPACE or a
- RETURN will toggle the value of the field that the cursor is on. To toggle
- all of the values of the "cursor" column (including files on all other
- screens), press T. You will notice that moving the cursor around and toggling
- fields is a bit sluggish, especially if you are in Slow mode on the 40-column
- screen. Did I mention that this program will run on either the 40 or
- 80-column screen? Toggling an entire column can take a couple of seconds.
-
- If there are more than 18 MS-DOS files, you can press the "+" and "-" keys to
- move among all of the screens of files. The cursor movement keys will wrap
- around on the current screen. "+" is page forward, and "-" is page backward.
- The screens wrap around too.
-
- After you have selected all of the files you want to copy and their translation
- and file-type fields have been set, press the C key to go into Copy Mode (next
- section). After copying, you are returned to the main screen with all of the
- field settings still intact. To exit from the program, press Q.
-
- 2.3. COPY MODE
-
- When you enter copy mode, the screen will clear and the name of each selected
- file is displayed as it is being copied. If an error is encountered on either
- the MS-DOS or CBM-DOS drive during copying, an error message will be displayed
- and copying will continue (after you press a key for MS-DOS errors).
-
- To generate a CBM-DOS filename from an MS-DOS filename, the eight filename
- characters are taken (including spaces) and a dot (.) and the three characters
- of the extension are appended. Then, all spaces are removed, and if the name
- ends with a dot (.) character, then that dot character is removed as well. I
- think this is fairly reasonable.
-
- If there already is a file with the same filename on the CBM-DOS disk, then
- you will be prompted if you want to overwrite the file or not. Entering an
- "n" will abort the copying of that file and go on to the next file, and
- entering a "y" (or anything else) will cause the CBM-DOS file to be
- "scratched" and then re-written.
-
- The physical copying of the file is done completely in machine language and
- nothing is displayed on the screen while this is happening, but you can follow
- things by looking at das blinkin lichtes and listening for clicks and grinds.
- You will probably be surprised by the MS-DOS file reading speed (I mean in a
- good way). The disk data is read in whole tracks and cached in memory and the
- directory information and the FAT are retained in memory as well. The result
- is that minimal time is spent reading disk data, and no costly seeks are
- required for opening a new MS-DOS file. A result is that small files are
- copied one after another very quickly. You will have to wait, however, on the
- relatively slow standard kernel/Commodore-DOS file writing.
-
- A few changes had to be made to the program to accomodate the RAMDOS program.
- RAMDOS uses memory from $2300 to $3FFF of RAM0, which is not really a good
- place for a device driver, and it uses some of the zero-page locations that I
- wanted to use. But, difficulties were overcome. The importance of RAMDOS
- compatibility is that if you only have one disk drive but you have an REU, you
- can use RAMDOS to store the MS-DOS files temporarily. If you only have one
- disk drive and no REU, you are SOL (Out of Luck) unless you can get a
- RamDisk-type program for an unexpanded 128. The RAMDOS program is available
- from FTP site "ccosun.caltech.edu" in file "/pub/rknop/util128/ramdosii.sfx".
- One note I found out about RAMDOS: you cannot use a
-
- DOPEN#1,(CF$),U(CD),W
-
- with it like you are supposed to be able to; you have to use a
-
- DOPEN#1,(CF$+",W"),U(CD)
-
- Here is a table of copying speeds for copying from 1571s and 1581s with ASC
- and BIN translation modes. All figures are in bytes/second. These results
- were obtained from copying a 127,280 byte text file (the text of C= Hacking
- Issue #3).
-
- FROM \ TO: "null" RAMLink RAMDOS JD1581 JD1571
- -------+ ------ ------- ------ ------ ------
- 81-bin | 5772 3441 2146 n/a 644
- 81-asc | 5772 3434 2164 n/a 661
- 71-bin | 4323 2991 1949 1821 n/a
- 71-asc | 4323 2982 1962 1847 n/a
-
- The "null" device is that "0" CBM-DOS device number, and a couple of entries
- are "n/a" since I only have one 1571 and one 1581. Note that my 71 and 81 are
- JiffyDOS-ified, so the performance of a stock 71/81 will be poorer. JiffyDOS
- gives about a 2x performance improvement for the standard file accessing calls
- (open, close, chrin, chrout). RAMDOS doesn't seem to be as snappy as you
- might think.
-
- The "null" figures are quite impressive, but the raw sector reading speed
- without the overhead of mucking around with file organization is 6700
- bytes/sec for a 1581 and 4600 B/s for a 71. The reason that the 1571 operates
- so quickly is that I use a sector interleave of 4 (which is optimal) for
- reading the tracks. I think that other MS-DOS file copier program uses an
- interleave of 1 (which is not optimal). I lose some of the raw performance
- because I copy the file data internally once before outputting it (to simplify
- some of the code).
-
- In a couple of places you will notice that ASC translation gives slightly
- better or slightly worse performance than BIN. This is because although
- slightly more work is required to translate the characters, slightly fewer
- characters will have to be written to the CBM-DOS file, since PETSCII uses
- only CR where MS-DOS ASCII uses CR and LF to represent end-of-line.
- Translation is done by using a table (that you can change if you wish). Many
- entries in this table contain a value of zero, which means that no character
- will be output on translation. Most of the control characters and all of the
- characters of value 128 (0x80) or greater are thrown away on being
- translated. The table is set up so that CR characters are thrown away and the
- LF character is translated to a CBM-DOS CR character. Thus, both MS-DOS ASCII
- files and UNIX ASCII files can be translated correctly.
-
- 2. BURST COMMANDS
-
- Three burst commands from the 1571/81 disk drive Burst Command Instruction Set
- are required to allow this program to read the MS-DOS disks: Query Disk
- Format, Sector Interleave, and Read. The grungy details about issuing burst
- commands and burst mode handshaking are covered in C= Hacking Issue #3. The
- Query Disk Format command is used to "log in" the MS-DOS disk. The Inquire
- Disk burst command cannot be used with an MS-DOS disk on the 1581 for some
- unknown reason. I found this out the hard way. The Query Disk Format command
- has the following format:
-
- BYTE \ bit: 7 6 5 4 3 2 1 0 | Value
- -------+--------+-----+-----+-----+-----+-----+-----+-----+-------
- 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | "U"
- 1 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | "0"
- 2 | F | X | X | S | 1 | 0 | 1 | N | 10
- -------+--------------------------------------------------+-------
-
- where the F, S, and N bits have a value of 0 for our purposes. A response of
- a burst status byte and six other throw-away bytes is given from the drive.
- This command takes quite a long time to execute on my 1581 but works quite
- quickly on my 1571. You only have to log in a disk whenever you change
- disks.
-
- The Sector Interleave command is used to set a soft interleave for the Read
- command. I use an interleave of 1 for the 1581 and an interleave of 4 for the
- 1571. This means that the MS-DOS sectors will come from 1571 to the computer
- in the following order: 1, 5, 9, 4, 8, 3, 7, 2, 6 (there are 9 sectors per
- track on an MS-DOS disk (both 3.5" and 5.25"), numbered from 1 to 9). LRR
- handles the data coming in in this order, and in straight order from the
- 1581. The Sector Interleave command has the following format, where the W and
- N bits are 0 for us:
-
- BYTE \ bit: 7 6 5 4 3 2 1 0 | Value
- -------+--------+-----+-----+-----+-----+-----+-----+-----+-------
- 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | "U"
- 1 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | "0"
- 2 | W | X | X | 0 | 1 | 0 | 0 | N | 8
- 3 | <interleave> | 1 or 4
- -------+--------------------------------------------------+-------
-
- The Read command is used to transfer the nine sectors of a track to the
- computer in the order specified by the interleave. The format is:
-
- BYTE \ bit: 7 6 5 4 3 2 1 0 | Value
- -------+--------+-----+-----+-----+-----+-----+-----+-----+-------
- 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | "U"
- 1 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | "0"
- 2 | T/L | E | B/X | S | 0 | 0 | 0 | N | 0 or 16
- 3 | <track> | ???
- 4 | <sector> | 1
- 5 | <number of sectors> | 9
- -------+--------------------------------------------------+-------
-
- There are a couple of differences between the 1571 and 1581 versions of this
- command. Most important, the S bit (Side of disk to use) has the opposite
- meaning on the two drives. There's no good reason that I know of for this
- inconsistency. This is the reason that LRR needs to know what type of MS-DOS
- drive it is dealing with (plus interleaving).
-
- The read command returns the following data using burst mode handshaking:
-
- +-------------------+
- 0 | Burst Status Byte |
- +-------------------+
- 1 | |
- ... + 512 Data Bytes |
- 512 | |
- +-------------------+
-
- for each sector transferred. If the Burst Status Byte indicates an error,
- then the data is not transferred and none of the following sectors are
- either. If the status byte gives a "Disk Changed" error, then you have to log
- in the disk with the Query Disk Format command before read will work
- properly. This is actually a good feature since it lets you know about a disk
- change so you can update any data structures you may have. LRR simply re-logs
- in the disk without updating any data structures and re-tries the failed read
- operation.
-
- 3. MS-DOS DISK FORMAT
-
- An MS-DOS disk is separated into 4 different parts: the Boot Sector, the
- FAT(s), the Root Directory, and the File Data Sectors. The logical sectors
- (blocks) of a disk are numbered from 0 to some maximum number (1439 for a
- 3.5", 719 for a 5.25" DD disk). The physical layout and the logical sector
- numbers typically used by a 3.5" disk are shown here:
-
- +-------------------+
- 0 | Boot Sector |
- +-------------------+
- 1..3 | FAT copy #1 |
- +-------------------+
- 4..6 | FAT copy #2 |
- +-------------------+
- 7..14 | Root Directory |
- +-------------------+
- 15 | |
- ... | File Data Sectors |
- 1439 | |
- +-------------------+
-
- 3.1. THE BOOT SECTOR
-
- The Boot Sector is always at logical sector number 0. It contains some
- important information about the format of the disk and it also contains code
- to boot an MS-DOS machine from. We aren't concerned with the bootstrapping
- code, but the important values we need to obtain from the boot sector are:
-
- ABBR OFFSET 1571 1581 DESCRIPTION
- ---- ------ ---- ---- -----------
- CS 13 2 2 Cluster size in sectors
- NB 14 1 1 Number of boot sectors
- NF 16 2 2 Number of FATs
- FL 23 2 3 FAT size in sectors
- DE 17 112 112 Number of root directory entries
- TS 19,20 720 1440 Total Number of sectors
- NS 24 9 9 Number of sectors per track
- NH 26 2 2 Number of sides
-
- The 1571 and 1581 columns give the typical values of these parameters for
- 5.25" and 3.5" disks. The OFFSET is the address of the parameter within the
- boot sector. The total number of sectors is given in low-byte, high-byte
- order (since the 80x86 family is little-endian like the 6502 family). From
- the above parameters, we can derive the following important parameters:
-
- ABBR FORMULA 1571 1581 DESCRIPTION
- ---- ------- ---- ---- -----------
- F1 NB+NF*FL 5 7 First root directory sector
- FS NB+NF*FL+DE 12 14 First file data sector number
- NC (TS-FS)/CS 354 713 Total number of file clusters
-
- LRR imposes a number of limits on these parameters and will error-out if you
- try to use a disk that is outside of LRR's limits.
-
- 3.2. CHEWING THE FAT
-
- MS-DOS disks use a data structure called a File Allocation Table (FAT) to
- record which clusters belong to which file in what order and which blocks are
- free. A cluster is a set of contiguous sectors which are allocated to files
- as a group. LRR handles cluster sizes of 1 and 2 sectors, giving a logical
- file block size of 512 or 1024 bytes. Typically, a cluster size of 2 sectors
- is used.
-
- The FAT is an array of 12-bit numbers, with an entry corresponding to each
- cluster that can be allocated to files. FAT entries 0 and 1 are reserved. If
- a FAT entry contains a value of $000, then the corresponding cluster is free
- and can be allocated to a file; otherwise, the cluster is allocated and the
- FAT entry contains the number of the NEXT FAT entry that belongs to the file.
- Thus, MS-DOS files are stored in a singly-linked list of clusters like
- Commodore-DOS files are, except that the links are not in the data sectors but
- rather are in the FAT. The pointer to the first FAT entry for a file is given
- in the file's directory entry.
-
- A special NULL/NIL pointer value of $FFF is used to indicate the end of the
- chain of clusters allocated to a file. This value is stored in the FAT entry
- of the last cluster allocated to a file (of course). Consider the following
- example FAT:
-
- ENTRY VALUE
- ----- -----
- $000 $FFF
- $001 $FFF
- $002 |----$003 <------Directory Entry
- $003 +--> $005----+
- $004 $000 |
- $005 $FFF <--+
-
- Entries 0 and 1 are insignificant since they are reserved. Say that a file
- starts at FAT entry #2. Then, it consists of the following chain of clusters:
- 2, 3, and 5. Cluster #4 is free. Clusters can be allocated to a file in
- random order, but if they are allocated contiguously in forward order, then
- the file will be able to be read faster. The FAT is such an important data
- structure that typically two copies are kept on the disk incase one of them
- should become corrupted.
-
- The MS-DOS designers were a little sneaky in storing the 12-bit FAT entries -
- they used only 12 real bits per entry. Ie., they store two FAT entries in
- three bytes, where the two entries share the two nybbles of the middle byte.
- The following diagram shows how the nybbles 1 (high), 2 (mid), and 3 (low) are
- stored into FAT entries A and B:
-
- BYTE: 0 1 2
- +---+---+ +---+---+ +---+---+
- ENTRY: | A | A | | B | A | | B | B |
- NYBBLE: | 2 | 3 | | 3 | 1 | | 1 | 2 |
- +---+---+ +---+---+ +---+---+
-
- Anyway, let's just say it's a bit tricky to extract the 12-bit values from
- this compressed data structure. On top of that, I don't think there is any
- saving in disk space resulting from compressing this structure; they might as
- well have just used a 16-bit FAT (like they do nowadays on larger disks).
-
- 3.3. THE ROOT DIRECTORY
-
- The root directory has a fixed size, although I don't think that
- subdirectories do. LRR cannot access subdirectories. Each 512-byte sector of
- the root directory contains sixteen 32-byte directory entries. One directory
- entry is required for each file stored on the disk. A directory entry has the
- following structure:
-
- OFFSET LEN DESCRIPTION
- ------ --- -----------
- 0..7 8 Filename
- 8..10 3 Extension
- 11 1 <unused?>
- 12 1 Attributes: $10=Directory, $08=VolumeId
- 13..21 9 <unused>
- 22..25 4 Date
- 26..27 2 Starting FAT entry number
- 28..31 4 File length in bytes
-
- The filename and extension are stored with trailing padding spaces. If a
- directory entry is unused or deleted, then the first character of the filename
- is either a $00 or a $E5 (229). This is why you have to provide the first
- character of a filename if you are undeleting a file on an MS-DOS machine.
- Note that there is enough unused space that Microsoft or IBM could have
- ditched the annoying 8+3 character filename format.
-
- The attributes bits tell whether the directory entry is for a regular file, a
- subdirectory, a disk volume name (in which case there is no file data), and a
- couple of other things I can't remember. I'm not sure about the exact
- position or format of the date, but LRR doesn't use it anyway. The starting
- FAT entry number and the file length are stored in lower-byte-first order.
-
- 3.4. THE FILE DATA SPACE
-
- The ramainder of the disk space is used for storing file data in clusters of 1
- or 2 sectors each. Given a cluster number (which is also the FAT entry
- number), the following formula is used to calculate the starting logical
- sector number of the cluster:
-
- (ClusterNumber - 2) * ClusterSizeInBlocks + FirstFileDataLogicalSectorNumber
-
- where "FirstFileDataLogicalSectorNumber" is the "FS" parameter derived
- earlier. The following consecutive logical sector numbers up to the number of
- sectors per cluster form the rest of the cluster. Note that a single cluster
- can span sectors from one side of the disk to another or from one track to
- another. We perform the "(ClusterNumber - 2)" portion of the calculation
- since the first two FAT entries are reserved.
-
- Since the Read burst command of the 1571/81 wants the side, track, and sector
- number of a sector rather than its logical number, we also need formulae for
- these conversions:
-
- Track = LogicalSectorNumber / 18
- Sector = LogicalSectorNumber % 9 + 1
- Side = (LogicalSectorNumber / 9) % 2
-
- These formulae are more problematic than the previous one since they require
- division by 9 and 18. LRR uses the method of repeated subtraction to perfrom
- the necessary division (only one division is necessary). The above formulae
- imply that sequential logical sectors are stored on the top of the disk
- first and then the bottom of the disk of the same track, and then on the top
- of the next track, etc. This is a good sector numbering scheme (unlike the
- CBM-DOS scheme for 1571 sectors) since it is faster to switch sides of the
- disk than it is to switch tracks, so you can read the disk faster.
-
- Oh yeah, the way that you know how many file data bytes are in the last
- cluster of a file chain (the cluster with the NULL FAT entry) is to take the
- file length from the directory entry and "mod" (the C language % operator) it
- with the cluster size. One special case is if this calculation results in a
- zero, then the last cluster is completely full (rather than completely empty
- as the calculation would suggest). This calculation is easily done in
- machine language with an AND operation since the cluster size is always a
- power of two.
-
- 4. FILE COPYING PACKAGE
-
- This section discusses the interface to and implementation of the MS-DOS file
- copying package. It is written in assembly language and is loaded into memory
- at address $8000 on bank 0 and requires about 13K of memory. The package is
- loaded at this high address to be out of the way of the main BASIC program,
- even if RAMDOS is installed.
-
- 4.1. INTERFACE
-
- The subroutine call and parameter passing interface to the file copying
- package is summarized as follows:
-
- ADDRESS DESCRIPTION
- ------- -----------
- PK InitPackage subroutine
- PK+3 LoadDirectory subroutine
- PK+6 CopyFile subroutine
- PK+9 two-byte package identification number
- PK+15 errno : error code returned
- PK+16 MS-DOS device number (8 to 30)
- PK+17 MS-DOS device type ($00=1571, $FF=1581)
- PK+18 two-byte starting cluster number for file copying
- PK+20 low and mid bytes of file length for copying
-
- where "PK" is the load address of the package. Additional subroutine
- parameters are passed in the processor registers.
-
- The "InitPackage" subroutine should be called when the package is first
- installed, whenever the MS-DOS device number is changed, and whenever a new
- disk is mounted to invalidate the internal track cache. It requires no
- parameters.
-
- The "LoadDirectory" subroutine will load the directory, FAT, and the Boot
- Sector parameters into the internal memory of the package from the current
- MS-DOS device number. No (other) input parameters are needed and the
- subroutine returns a pointer to the directory space in the .AY registers and
- the number of directory entries in the .X register. If an error occurs, then
- the subroutine returns with the Carry flag set and the error code is available
- in the "errno" interface variable. The directory entry data is in the
- directory space as it was read in raw from the directory sectors on the MS-DOS
- disk.
-
- The "CopyFile" subroutine will copy a single file from the MS-DOS disk to a
- specified CBM-Kernal logical file number (the CBM file must already be
- opened). If the CBM logical file number is zero, then the file data is simply
- discarded after it is read from the MS-DOS file. The starting cluster number
- of the file to copy and the low and mid bytes of the file length are passed in
- the PK+18 and PK+20 interface words. The translation mode to use is passed in
- the .A register ($00=binary, $FF=ascii) and the CBM logical file number to
- output to is passed in the .X register. If an error occurs, the routine
- returns with the Carry flag set and the error code in the "errno" interface
- variable. There are no other output parameters.
-
- Note that since the starting cluster number and low-file length of the file to
- be copied are required rather than the filename, it is the responsibility of
- the front-end application program to dig through the raw directory sector data
- to get this information. The application must also open the Commodore-DOS
- file of whatever filetype on whatever device is required; the package does not
- need to know the Commodore-DOS device number.
-
- The MS-DOS device number and device type interface variables allow you to set
- the MS-DOS drive and the package identification number allows the application
- program to check if the package is already loaded into memory so that it only
- has to load the package the first time the application is run and not on
- re-runs. The identification sequence is a value of $CB followed by a value
- of 131.
-
- 4.2. IMPLEMENTATION
-
- This section presents the code that implements the MS-DOS file reading
- package. It is here in a special form; each code line is preceded by a few
- special characters and the line number. The special characters are there to
- allow you to easily extract the assembler code from the rest of this magazine
- (and all of my ugly comments). On a Unix system, all you have to do is
- execute the following command line (substitute filenames as appropriate):
-
- grep '^\.%...\!' Hack4 | sed 's/^.%...\!..//' | sed 's/.%...\!//' >lrr.s
-
- You'll notice that the initial comment lines here were an afterthought.
-
- .%000! ;Little Red Reader MS-DOS file copier program
- .%000! ;written 92/10/03 by Craig Bruce for C= Hacking Net Magazine
- .%000!
-
- The code is written for the Buddy assembler and here are a couple setup
- directives. Note that my comments come before the section of code.
-
- .%001! .org $8000
- .%002! .obj "@:lrr.bin"
- .%003!
- .%004! ;====jump table and parameters interface ====
- .%005!
- .%006! jmp initPackage
- .%007! jmp loadDirectory
- .%008! jmp copyFile
- .%009!
- .%010! .byte $cb,131 ;identification
- .%011! .byte 0,0,0,0
- .%012!
-
- These variables are included in the package program space to minimize unwanted
- interaction with other programs loaded at the same time, such as the RAMDOS
- device driver.
-
- .%013! errno .buf 1 ;(location pk+15)
- .%014! sourceDevice .buf 1
- .%015! sourceType .buf 1 ;$00=1571, $ff=1581
- .%016! startCluster .buf 2
- .%017! lenML .buf 2 ;length medium and low bytes
- .%018!
- .%019! ;====global declaraions====
- .%020!
- .%021! kernelListen = $ffb1
- .%022! kernelSecond = $ff93
- .%023! kernelUnlsn = $ffae
- .%024! kernelAcptr = $ffa2
- .%025! kernelCiout = $ffa8
- .%026! kernelSpinp = $ff47
- .%027! kernelChkout = $ffc9
- .%028! kernelClrchn = $ffcc
- .%029! kernelChrout = $ffd2
- .%030!
- .%031! st = $d0
- .%032! ciaClock = $dd00
- .%033! ciaFlags = $dc0d
- .%034! ciaData = $dc0c
- .%035!
-
- These are the parameters and derived parameters from the boot sector. They
- are kept in the program space to avoid interactions.
-
- .%036! clusterBlockCount .buf 1 ;1 or 2
- .%037! fatBlocks .buf 1 ;up to 3
- .%038! rootDirBlocks .buf 1 ;up to 8
- .%039! rootDirEntries .buf 1 ;up to 128
- .%040! totalSectors .buf 2 ;up to 1440
- .%041! firstFileBlock .buf 1
- .%042! firstRootDirBlock .buf 1
- .%043! fileClusterCount .buf 2
- .%044!
-
- The cylinder (track) and side that is currently stored in the trach cache.
-
- .%045! bufCylinder .buf 1
- .%046! bufSide .buf 1
- .%047! formatParms .buf 6
- .%048!
-
- This package is split into a number of levels. This level interfaces with the
- Kernal serial bus routines and the burst command protocol of the disk drives.
-
- .%049! ;====hardware level====
- .%050!
-
- Connect to the MS-DOS device and send the "U0" burst command prefix and the
- burst command byte.
-
- .%051! sendU0 = * ;( .A=burstCommandCode ) : .CS=err
- .%052! pha
- .%053! lda #0
- .%054! sta st
- .%055! lda sourceDevice
- .%056! jsr kernelListen
- .%057! lda #$6f
- .%058! jsr kernelSecond
- .%059! lda #"u"
- .%060! jsr kernelCiout
- .%061! bit st
- .%062! bmi sendU0Error
- .%063! lda #"0"
- .%064! jsr kernelCiout
- .%065! pla
- .%066! jsr kernelCiout
- .%067! bit st
- .%068! bmi sendU0Error
- .%069! clc
- .%070! rts
- .%071!
- .%072! sendU0Error = *
- .%073! lda #5
- .%074! sta errno
- .%075! sec
- .%076! rts
- .%077!
-
- Toggle the "Data Accepted / Ready For More" clock signal for the burst
- transfer protocol.
-
- .%078! toggleClock = *
- .%079! lda ciaClock
- .%080! eor #$10
- .%081! sta ciaClock
- .%082! rts
- .%083!
-
- Wait for a burst byte to arrive in the serial data register of CIA#1 from the
- fast serial bus.
-
- .%084! serialWait = *
- .%085! lda #$08
- .%086! - bit ciaFlags
- .%087! beq -
- .%088! rts
- .%089!
-
- Wait for and get a burst byte from the fast serial bus, and send the "Data
- Accepted" signal.
-
- .%090! getBurstByte = *
- .%091! jsr serialWait
- .%092! ldx ciaData
- .%093! jsr toggleClock
- .%094! txa
- .%095! rts
- .%096!
-
- Send the burst commands to "log in" the MS-DOS disk and set the Read sector
- interleave factor.
-
- .%097! mountDisk = * ;() : .CS=err
- .%098! lda #%00011010
- .%099! jsr sendU0
- .%100! bcc +
- .%101! rts
- .%102! + jsr kernelUnlsn
- .%103! bit st
- .%104! bmi sendU0Error
- .%105! clc
- .%106! jsr kernelSpinp
- .%107! bit ciaFlags
- .%108! jsr toggleClock
- .%109! jsr getBurstByte
- .%110! sta errno
- .%111! and #$0f
- .%112! cmp #2
- .%113! bcs mountExit
-
- Grab the throw-away parameters from the mount operation.
-
- .%114! ldy #0
- .%115! - jsr getBurstByte
- .%116! sta formatParms,y
- .%117! iny
- .%118! cpy #6
- .%119! bcc -
- .%120! clc
-
- Set the sector interleave to 1 for a 1581 or 4 for a 1571.
-
- .%121! ;** set interleave
- .%122! lda #%00001000
- .%123! jsr sendU0
- .%124! bcc +
- .%125! rts
- .%126! + lda #1 ;interleave of 1 for 1581
- .%127! bit sourceType
- .%128! bmi +
- .%129! lda #4 ;interleave of 4 for 1571
- .%130! + jsr kernelCiout
- .%131! jsr kernelUnlsn
- .%132! mountExit = *
- .%133! rts
- .%134!
-
- Read all of the sectors of a given track into the track cache.
-
- .%135! bufptr = 2
- .%136! secnum = 4
- .%137!
- .%138! readTrack = * ;( .A=cylinder, .X=side ) : trackbuf, .CS=err
- .%139! pha
- .%140! txa
-
- Get the side and put it into the command byte. Remember that we have to flip
- the side bit for a 1581.
-
- .%141! and #$01
- .%142! asl
- .%143! asl
- .%144! asl
- .%145! asl
- .%146! bit sourceType
- .%147! bpl +
- .%148! eor #$10
- .%149! + jsr sendU0
- .%150! bcc +
- .%151! rts
- .%152! + pla ;cylinder number
- .%153! jsr kernelCiout
- .%154! lda #1 ;start sector number
- .%155! jsr kernelCiout
- .%156! lda #9 ;sector count
- .%157! jsr kernelCiout
- .%158! jsr kernelUnlsn
-
- Prepare to receive the track data.
-
- .%159! sei
- .%160! clc
- .%161! jsr kernelSpinp
- .%162! bit ciaFlags
- .%163! jsr toggleClock
- .%164! lda #<trackbuf
- .%165! ldy #>trackbuf
- .%166! sta bufptr
- .%167! sty bufptr+1
-
- Get the sector data for each of the 9 sectors of the track.
-
- .%168! lda #0
- .%169! sta secnum
- .%170! - bit sourceType
- .%171! bmi +
-
- If we are dealing with a 1571, we have to set the buffer pointer for the next
- sector, taking into account the soft interleave of 4.
-
- .%172! jsr get1571BufPtr
- .%173! + jsr readSector
- .%174! bcs trackExit
- .%175! inc secnum
- .%176! lda secnum
- .%177! cmp #9
- .%178! bcc -
- .%179! clc
- .%180! trackExit = *
- .%181! cli
- .%182! rts
- .%183!
-
- Get the buffer pointer for the next 1571 sector.
-
- .%184! get1571BufPtr = *
- .%185! lda #<trackbuf
- .%186! sta bufptr
- .%187! ldx secnum
- .%188! clc
- .%189! lda #>trackbuf
- .%190! adc bufptr1571,x
- .%191! sta bufptr+1
- .%192! rts
- .%193!
- .%194! bufptr1571 = *
- .%195! .byte 0,8,16,6,14,4,12,2,10
- .%196!
-
- Read an individual sector into memory at the specified address.
-
- .%197! readSector = * ;( bufptr ) : .CS=err
-
- Get and check the burst status byte for errors.
-
- .%198! jsr getBurstByte
- .%199! sta errno
- .%200! and #$0f
- .%201! cmp #2
- .%202! bcc +
- .%203! rts
- .%204! + ldx #2
- .%205! ldy #0
- .%206!
-
- Receive the 512 sector data bytes into memory.
-
- .%207! readByte = *
- .%208! lda #$08
- .%209! - bit ciaFlags
- .%210! beq -
- .%211! lda ciaClock
- .%212! eor #$10
- .%213! sta ciaClock
- .%214! lda ciaData
- .%215! sta (bufptr),y
- .%216! iny
- .%217! bne readByte
- .%218! inc bufptr+1
- .%219! dex
- .%220! bne readByte
- .%221! rts
- .%222!
-
- This next level of routines deals with logical sectors and the track cache
- rather than with hardware.
-
- .%223! ;====logical sector level====
- .%224!
-
- Invalidate the track cache if the MS-DOS drive number is changed or if a new
- disk is inserted. This routine has to establish a RAM configuration of $0E
- since it will be called from RAM0. Configuration $0E gives RAM0 from $0000 to
- $BFFF, Kernal ROM from $C000 to $FFFF, and the I/O space over the Kernal from
- $D000 to $DFFF. This configuration is set by all application interface
- subroutines.
-
- .%225! initPackage = *
- .%226! lda #$0e
- .%227! sta $ff00
- .%228! lda #$ff
- .%229! sta bufCylinder
- .%230! sta bufSide
- .%231! clc
- .%232! rts
- .%233!
-
- Locate a sector (block) in the track cache, or read the corresponding physical
- track into the track cache if necessary. This routine accepts the cylinder,
- side, and sector numbers of the block.
-
- .%234! sectorSave = 5
- .%235!
- .%236! readBlock = * ;( .A=cylinder,.X=side,.Y=sector ) : .AY=blkPtr,.CS=err
-
- Check if the correct track is in the track cache.
-
- .%237! cmp bufCylinder
- .%238! bne readBlockPhysical
- .%239! cpx bufSide
- .%240! bne readBlockPhysical
-
- If so, then locate the sector's address and return that.
-
- .%241! dey
- .%242! tya
- .%243! asl
- .%244! clc
- .%245! adc #>trackbuf
- .%246! tay
- .%247! lda #<trackbuf
- .%248! clc
- .%249! rts
- .%250!
-
- Here, we have to read the physical track into the track cache. We save the
- input parameters and call the hardware-level track-reading routine.
-
- .%251! readBlockPhysical = *
- .%252! sta bufCylinder
- .%253! stx bufSide
- .%254! sty sectorSave
- .%255! jsr readTrack
-
- Check for errors.
-
- .%256! bcc readBlockPhysicalOk
- .%257! lda errno
- .%258! and #$0f
- .%259! cmp #11 ;disk change
- .%260! beq +
- .%261! sec
- .%262! rts
-
- If the error that happened is a "Disk Change" error, then mount the disk and
- try to read the physical track again.
-
- .%263! + jsr mountDisk
- .%264! lda bufCylinder
- .%265! ldx bufSide
- .%266! ldy sectorSave
- .%267! bcc readBlockPhysical
- .%268! rts
- .%269!
-
- Here, the physical track has been read into the track cache ok, so we recover
- the original input parameters and try the top of the routine again.
-
- .%270! readBlockPhysicalOk = *
- .%271! lda bufCylinder
- .%272! ldx bufSide
- .%273! ldy sectorSave
- .%274! jmp readBlock
- .%275!
-
- Divide the given number by 18. This is needed for the calculations to convert
- a logical sector number to the corresponding physical cylinder, side, and
- sector numbers that the lower-level routines require. The method of repeated
- subtraction is used. This routine would probably work faster if we tried to
- repeatedly subtract 360 (18*20) at the top, but I didn't bother.
-
- .%276! divideBy18 = * ;( .AY=number ) : .A=quotient, .Y=remainder
- .%277! ;** could repeatedly subtract 360 here
- .%278! ldx #$ff
- .%279! - inx
- .%280! sec
- .%281! sbc #18
- .%282! bcs -
- .%283! dey
- .%284! bpl -
- .%285! clc
- .%286! adc #18
- .%287! iny
- .%288! tay
- .%289! txa
- .%290! rts
- .%291!
-
- Convert the given logical block number to the corresponding physical cylinder,
- side, and sector numbers. This routine follows the formulae given earlier
- with a few simplifying tricks.
-
- .%292! convertLogicalBlockNum = * ;( .AY=blockNum ) : .A=cyl, .X=side, .Y=sec
- .%293! jsr divideBy18
- .%294! ldx #0
- .%295! cpy #9
- .%296! bcc +
- .%297! pha
- .%298! tya
- .%299! sbc #9
- .%300! tay
- .%301! pla
- .%302! ldx #1
- .%303! + iny
- .%304! rts
- .%305!
-
- Copy a sequential group of logical sectors into memory. This routine is used
- by the directory loading routine to load the FAT and Root Directory, and is
- used by the cluster reading routine to retrieve all of the blocks of a
- cluster. After the given starting logical sector number is converted into its
- physical cylinder, side, and sector equivalent, the physical values are
- incremented to get the address of successive sectors of the group. This
- avoids the overhead of the logical to physical conversion. Quite a number of
- temporaries are needed.
-
- .%306! destPtr = 6
- .%307! curCylinder = 8
- .%308! curSide = 9
- .%309! curSector = 10
- .%310! blockCountdown = 11
- .%311! sourcePtr = 12
- .%312!
- .%313! copyBlocks = * ;( .AY=startBlock, .X=blockCount, ($6)=dest ) : .CS=err
- .%314! stx blockCountdown
- .%315! jsr convertLogicalBlockNum
- .%316! sta curCylinder
- .%317! stx curSide
- .%318! sty curSector
- .%319!
- .%320! copyBlockLoop = *
- .%321! lda curCylinder
- .%322! ldx curSide
- .%323! ldy curSector
- .%324! jsr readBlock
- .%325! bcc +
- .%326! rts
- .%327! + sta sourcePtr
- .%328! sty sourcePtr+1
- .%329! ldx #2
- .%330! ldy #0
-
- Here I unroll the copying loop a little bit to cut the overhead of the branch
- instruction in half. (A cycle saved... you know).
-
- .%331! - lda (sourcePtr),y
- .%332! sta (destPtr),y
- .%333! iny
- .%334! lda (sourcePtr),y
- .%335! sta (destPtr),y
- .%336! iny
- .%337! bne -
- .%338! inc sourcePtr+1
- .%339! inc destPtr+1
- .%340! dex
- .%341! bne -
-
- Increment the cylinder, side, sector values.
-
- .%342! inc curSector
- .%343! lda curSector
- .%344! cmp #10
- .%345! bcc +
- .%346! lda #1
- .%347! sta curSector
- .%348! inc curSide
- .%349! lda curSide
- .%350! cmp #2
- .%351! bcc +
- .%352! lda #0
- .%353! sta curSide
- .%354! inc curCylinder
- .%355! + dec blockCountdown
- .%356! bne copyBlockLoop
- .%357! clc
- .%358! rts
- .%359!
-
- Read a cluster into the Cluster Buffer, given the cluster number. The cluster
- number is converted to a logical sector number and then the sector copying
- routine is called. The formula given earlier is used for the conversion.
-
- .%360! readCluster = * ;( .AY=clusterNumber ) : clusterBuf, .CS=err
- .%361! ;** convert cluster number to logical block number
- .%362! sec
- .%363! sbc #2
- .%364! bcs +
- .%365! dey
- .%366! + ldx clusterBlockCount
- .%367! cpx #1
- .%368! beq +
- .%369! asl
- .%370! sty 7
- .%371! rol 7
- .%372! ldy 7
- .%373! + clc
- .%374! adc firstFileBlock
- .%375! bcc +
- .%376! iny
- .%377!
- .%378! ;** read logical blocks comprising cluster
- .%379! + ldx #<clusterBuf
- .%380! stx 6
- .%381! ldx #>clusterBuf
- .%382! stx 7
- .%383! ldx clusterBlockCount
- .%384! jmp copyBlocks
- .%385!
-
- This next level of routines deal with the data structures of the MS-DOS disk
- format.
-
- .%386! ;====MS-DOS format level====
- .%387!
- .%388! bootBlock = 2
- .%389!
-
- Read the disk format parameters, directory, and FAT into memory.
-
- .%390! loadDirectory = * ;( ) : .AY=dirbuf, .X=dirEntries, .CS=err
- .%391! lda #$0e
- .%392! sta $ff00
- .%393!
-
- Read the boot sector and extract the parameters.
-
- .%394! ;** get parameters from boot sector
- .%395! lda #0
- .%396! ldy #0
- .%397! jsr convertLogicalBlockNum
- .%398! jsr readBlock
- .%399! bcc +
- .%400! rts
- .%401! + sta bootBlock
- .%402! sty bootBlock+1
- .%403! ldy #13 ;get cluster size
- .%404! lda (bootBlock),y
- .%405! sta clusterBlockCount
- .%406! cmp #3
- .%407! bcc +
- .%408!
-
- If a disk parameter is found to exceed the limits of LRR, error code #60 is
- returned.
-
- .%409! invalidParms = *
- .%410! lda #60
- .%411! sta errno
- .%412! sec
- .%413! rts
- .%414!
- .%415! + ldy #16 ;check FAT replication count, must be 2
- .%416! lda (bootBlock),y
- .%417! cmp #2
- .%418! bne invalidParms
- .%419! ldy #22 ;get FAT size in sectors
- .%420! lda (bootBlock),y
- .%421! sta fatBlocks
- .%422! cmp #4
- .%423! bcs invalidParms
- .%424! ldy #17 ;get directory size
- .%425! lda (bootBlock),y
- .%426! sta rootDirEntries
- .%427! cmp #129
- .%428! bcs invalidParms
- .%429! lsr
- .%430! lsr
- .%431! lsr
- .%432! lsr
- .%433! sta rootDirBlocks
- .%434! ldy #19 ;get total sector count
- .%435! lda (bootBlock),y
- .%436! sta totalSectors
- .%437! iny
- .%438! lda (bootBlock),y
- .%439! sta totalSectors+1
- .%440! ldy #24 ;check sectors per track, must be 9
- .%441! lda (bootBlock),y
- .%442! cmp #9
- .%443! bne invalidParms
- .%444! ldy #26
- .%445! lda (bootBlock),y
- .%446! cmp #2 ;check number of sides, must be 2
- .%447! bne invalidParms
- .%448! ldy #14 ;check number of boot sectors, must be 1
- .%449! lda (bootBlock),y
- .%450! cmp #1
- .%451! bne invalidParms
- .%452!
-
- Calculate the derived parameters.
-
- .%453! ;** get derived parameters
- .%454! lda fatBlocks ;first root directory sector
- .%455! asl
- .%456! clc
- .%457! adc #1
- .%458! sta firstRootDirBlock
- .%459! clc ;first file sector
- .%460! adc rootDirBlocks
- .%461! sta firstFileBlock
- .%462! lda totalSectors ;number of file clusters
- .%463! ldy totalSectors+1
- .%464! sec
- .%465! sbc firstFileBlock
- .%466! bcs +
- .%467! dey
- .%468! + sta fileClusterCount
- .%469! sty fileClusterCount+1
- .%470! lda clusterBlockCount
- .%471! cmp #2
- .%472! bne +
- .%473! lsr fileClusterCount+1
- .%474! ror fileClusterCount
- .%475!
-
- Gee, I have more comments embedded in the code than I did last issue.
-
- .%476! ;** load FAT
- .%477! + lda #<fatbuf
- .%478! ldy #>fatbuf
- .%479! sta 6
- .%480! sty 7
- .%481! lda #1
- .%482! ldy #0
- .%483! ldx fatBlocks
- .%484! jsr copyBlocks
- .%485! bcc +
- .%486! rts
- .%487!
- .%488! ;** load actual directory
- .%489! + lda #<dirbuf
- .%490! ldy #>dirbuf
- .%491! sta 6
- .%492! sty 7
- .%493! lda firstRootDirBlock
- .%494! ldy #0
- .%495! ldx rootDirBlocks
- .%496! jsr copyBlocks
- .%497! bcc +
- .%498! rts
- .%499! + lda #<dirbuf
- .%500! ldy #>dirbuf
- .%501! ldx rootDirEntries
- .%502! clc
- .%503! rts
- .%504!
-
- This routine locates the given FAT table entry number and returns the value
- stored in it. Some work is needed to deal with the 12-bit compressed data
- structure.
-
- .%505! entryAddr = 2
- .%506! entryWork = 4
- .%507! entryBits = 5
- .%508! entryData0 = 6
- .%509! entryData1 = 7
- .%510! entryData2 = 8
- .%511!
- .%512! getFatEntry = * ;( .AY=fatEntryNumber ) : .AY=fatEntryValue
- .%513! sta entryBits
-
- Divide the FAT entry number by two and multiply by three because two FAT
- entries are stored in three bytes. Then add the FAT base address and we have
- the address of the three bytes that contain the FAT entry we are interested
- in. I retrieve the three bytes into zero-page memory for easy manipulation.
-
- .%514! ;** divide by two
- .%515! sty entryAddr+1
- .%516! lsr entryAddr+1
- .%517! ror
- .%518!
- .%519! ;** times three
- .%520! sta entryWork
- .%521! ldx entryAddr+1
- .%522! asl
- .%523! rol entryAddr+1
- .%524! clc
- .%525! adc entryWork
- .%526! sta entryAddr
- .%527! txa
- .%528! adc entryAddr+1
- .%529! sta entryAddr+1
- .%530!
- .%531! ;** add base, get data
- .%532! clc
- .%533! lda entryAddr
- .%534! adc #<fatbuf
- .%535! sta entryAddr
- .%536! lda entryAddr+1
- .%537! adc #>fatbuf
- .%538! sta entryAddr+1
- .%539! ldy #2
- .%540! - lda (entryAddr),y
- .%541! sta entryData0,y
- .%542! dey
- .%543! bpl -
- .%544! lda entryBits
- .%545! and #1
- .%546! bne +
- .%547!
-
- If the original given FAT entry number is even, then we want the first 12-bit
- compressed field. The nybbles are extracted according to the diagram shown
- earlier.
-
- .%548! ;** case 1: first 12-bit cluster
- .%549! lda entryData1
- .%550! and #$0f
- .%551! tay
- .%552! lda entryData0
- .%553! rts
- .%554!
-
- Otherwise, we want the second 12-bit field.
-
- .%555! ;** case 2: second 12-bit cluster
- .%556! + lda entryData1
- .%557! ldx #4
- .%558! - lsr entryData2
- .%559! ror
- .%560! dex
- .%561! bne -
- .%562! ldy entryData2
- .%563! rts
- .%564!
-
- Finally, this is the file copying level. It deals with reading the clusters
- of MS-DOS files and copying the data they contain to the already-open CBM
- Kernal file, possibly with ASCII-to-PETSCII translation.
-
- .%565! ;====file copy level====
- .%566!
- .%567! transMode = 14
- .%568! lfn = 15
- .%569! cbmDataPtr = $60
- .%570! cbmDataLen = $62
- .%571! cluster = $64
- .%572!
-
- Copy the given cluster to the CBM output file. This routine fetches the next
- cluster of the file for the next time this routine is called, and if it hits
- the NULL pointer of the last cluster of a file, it adjusts the number of valid
- file data bytes the current cluster contains to FileLength % ClusterLength
- (see note below).
-
- .%573! copyFileCluster = * ;( cluster, lfn, transMode ) : .CS=err
-
- Read the cluster and setup to copy the whole cluster to the CBM file.
-
- .%574! lda cluster
- .%575! ldy cluster+1
- .%576! jsr readCluster
- .%577! bcc +
- .%578! rts
- .%579! + lda #<clusterBuf
- .%580! ldy #>clusterBuf
- .%581! sta cbmDataPtr
- .%582! sty cbmDataPtr+1
- .%583! lda #0
- .%584! sta cbmDataLen
- .%585! lda clusterBlockCount
- .%586! asl
- .%587! sta cbmDataLen+1
- .%588!
-
- Fetch the next cluster number of the file, and adjust the cluster data length
- for the last cluster of the file.
-
- .%589! ;**get next cluster
- .%590! lda cluster
- .%591! ldy cluster+1
- .%592! jsr getFatEntry
- .%593! sta cluster
- .%594! sty cluster+1
- .%595! cmp #$ff
- .%596! bne copyFileClusterData
- .%597! cpy #$0f
- .%598! bne copyFileClusterData
- .%599! lda lenML
- .%600! sta cbmDataLen
- .%601! lda #$01
- .%602! ldx clusterBlockCount
- .%603! cpx #1
- .%604! beq +
- .%605! lda #$03
- .%606! + and lenML+1
-
- The following three lines were added in a last minute panic after realizing
- that if FileLength % ClusterSize == 0, then the last cluster of the file
- contains ClusterSize bytes, not zero bytes.
-
- .%000! bne +
- .%000! ldx lenML
- .%000! beq copyFileClusterData
- .%607! + sta cbmDataLen+1
- .%608!
- .%609! copyFileClusterData = *
- .%610! jsr commieOut
- .%611! rts
- .%612!
-
- Copy the file data in the MS-DOS cluster buffer to the CBM output file.
-
- .%613! cbmDataLimit = $66
- .%614!
- .%615! commieOut = * ;( cbmDataPtr, cbmDataLen ) : .CS=err
-
- If the the logical file number to copy to is 0 ("null device"), then don't
- bother copying anything.
-
- .%616! ldx lfn
- .%617! bne +
- .%618! clc
- .%619! rts
-
- Otherwise, prepare the logical file number for output.
-
- .%620! + jsr kernelChkout
- .%621! bcc commieOutMore
- .%622! sta errno
- .%623! rts
- .%624!
- .%625! commieOutMore = *
-
- Process the cluster data in chunks of up to 255 bytes or the number of data
- bytes remaining in the cluster.
-
- .%626! lda #255
- .%627! ldx cbmDataLen+1
- .%628! bne +
- .%629! lda cbmDataLen
- .%630! + sta cbmDataLimit
- .%631! ldy #0
- .%632! - lda (cbmDataPtr),y
- .%633! bit transMode
- .%634! bpl +
-
- If we have to translate the current ASCII character, look up the PETSCII value
- in the translation table and output that value. If the translation table
- entry value is $00, then don't output a character (filter out invalid
- character codes).
-
- .%635! tax
- .%636! lda transBuf,x
- .%637! beq commieNext
- .%638! + jsr kernelChrout
- .%639! commieNext = *
- .%640! iny
- .%641! cpy cbmDataLimit
- .%642! bne -
- .%643!
-
- Increment the cluster buffer pointer and decrement the cluster buffer character
- count according to the number of bytes just processed, and repeat the above if
- more file data remains in the current cluster.
-
- .%644! clc
- .%645! lda cbmDataPtr
- .%646! adc cbmDataLimit
- .%647! sta cbmDataPtr
- .%648! bcc +
- .%649! inc cbmDataPtr+1
- .%650! + sec
- .%651! lda cbmDataLen
- .%652! sbc cbmDataLimit
- .%653! sta cbmDataLen
- .%654! bcs +
- .%655! dec cbmDataLen+1
- .%656! + lda cbmDataLen
- .%657! ora cbmDataLen+1
- .%658! bne commieOutMore
-
- If we are finished with the cluster, then clear the CBM Kernal output channel.
-
- .%659! jsr kernelClrchn
- .%660! clc
- .%661! rts
- .%662!
-
- The file copying main routine. Set up for the starting cluster, and call
- the cluster copying routine until end-of-file is reached. Checks for a
- NULL cluster pointer in the directory entry to handle zero-length files.
-
- .%663! copyFile = * ;( startCluster, lenML, .A=transMode, .X=lfn ) : .CS=err
- .%664! ldy #$0e
- .%665! sty $ff00
- .%666! sta transMode
- .%667! stx lfn
- .%668! lda startCluster
- .%669! ldy startCluster+1
- .%670! sta cluster
- .%671! sty cluster+1
- .%672! jmp +
- .%673! - jsr copyFileCluster
- .%674! bcc +
- .%675! rts
- .%676! + lda cluster
- .%677! cmp #$ff
- .%678! bne -
- .%679! lda cluster+1
- .%680! cmp #$0f
- .%681! bne -
- .%682! clc
- .%683! rts
- .%684!
-
- This is the translation table used to convert from ASCII to PETSCII. You can
- modify it to suit your needs if you wish. If you cannot reassemble this file,
- then you can sift through the binary file and locate the tabel and change it
- there. An entry of $00 means the corresponding ASCII character will not be
- translated. You'll notice that I have set up translations for the following
- ASCII control characters into PETSCII: Backspace, Tab, Linefeed (CR), and
- Formfeed. I also translate the non-PETSCII characters such as {, |, ~, and _
- according to what they probably would have been if Commodore wasn't so
- concerned with the graphics characters.
-
- .%685! transBuf = *
- .%686! ;0 1 2 3 4 5 6 7 8 9 a b c d e f
- .%687! .byte $00,$00,$00,$00,$00,$00,$00,$00,$14,$09,$0d,$00,$93,$00,$00,$00 ;0
- .%688! .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;1
- .%689! .byte $20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$2a,$2b,$2c,$2d,$2e,$2f ;2
- .%690! .byte $30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$3a,$3b,$3c,$3d,$3e,$3f ;3
- .%691! .byte $40,$c1,$c2,$c3,$c4,$c5,$c6,$c7,$c8,$c9,$ca,$cb,$cc,$cd,$ce,$cf ;4
- .%692! .byte $d0,$d1,$d2,$d3,$d4,$d5,$d6,$d7,$d8,$d9,$da,$5b,$5c,$5d,$5e,$5f ;5
- .%693! .byte $c0,$41,$42,$43,$44,$45,$46,$47,$48,$49,$4a,$4b,$4c,$4d,$4e,$4f ;6
- .%694! .byte $50,$51,$52,$53,$54,$55,$56,$57,$58,$59,$5a,$db,$dc,$dd,$de,$df ;7
- .%695! .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;8
- .%696! .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;9
- .%697! .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;a
- .%698! .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;b
- .%699! .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;c
- .%700! .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;d
- .%701! .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;e
- .%702! .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;f
- .%703!
-
- This is where the track cache, etc. are stored. This section requires 11K of
- storage space but does not increase the length of the binary program file
- since these storage areas are DEFINED rather than allocated with ".buf"
- directives. The Unix terminology for this type of uninitialized data is "bss".
-
- .%704! ;====bss storage====
- .%705!
- .%706! bss = *
- .%707! trackbuf = bss
- .%708! clusterBuf = trackbuf+4608
- .%709! fatbuf = clusterBuf+1024
- .%710! dirbuf = fatbuf+1536
- .%711! end = dirbuf+4096
-
- 5. USER-INTERFACE PROGRAM
-
- This section presents the listing of the user-interface BASIC program. You
- should be aware that you can easily change some of the defaults to your own
- preferences if you wish. This program is not listed in the ".%nnn!" format
- that the assembler listing is since you can recover this listing from the
- uuencoded binary program file. This program should be a little easier to
- follow than the assembler listing since BASIC is a self-commenting language. :-)
-
- 10 rem little red reader, by craig bruce, 30-sep-92, for c= hacking netmag
- 11 :
-
- These lines set up the default CBM-DOS and MS-DOS device numbers, taking care
- to disallow them to be the same device. You can change this to your own drive
- configuration.
-
- 20 cd=peek(186) : rem ** default cbm-dos drive **
- 25 dv=9:dt=0 : rem ** ms-dos drive, type (0=1571,255=1581)
- 26 if dv=cd then dv=8:dt=0 : rem ** alternate ms-dos drive
- 27 :
- 30 print chr$(147);"initializing..." : print
- 40 bank0 : pk=dec("8000")
- 50 if peek(pk+9)=dec("cb") and peek(pk+10)=131 then 60
- 55 print"loading machine language routines..." : bload"lrr.bin",u(cd)
- 60 poke pk+16,dv : poke pk+17,dt : sys pk
-
- I "dim" the following variables before the arrays to avoid the overhead of
- pushing the arrays around when creating new scalar variables.
-
- 70 dim t,r,b,i,a$,c,dt$,fl$,il$,x,x$
- 80 dim di$(128),cl(128),sz(128)
- 90 if dt=255 then dt$="1581" :else dt$="1571"
- 100 fl$=chr$(19)+chr$(17)+chr$(17)+chr$(17)+chr$(17)
- 110 il$=fl$:fori=1to19:il$=il$+chr$(17):next
- 120 goto 500
- 130 :
- 131 rem ** load ms-dos directory **
- 140 print"loading directory..." : print
- 150 sys pk : sys pk+3
- 160 dl=0
-
- The "rreg" instruction returns the return values of the .A, .X, .Y, and .S
- registers from the last "sys" call. I check the 1-bit of the .S register
- (the Carry flag) for error returns.
-
- 170 rreg bl,dc,bh,s : e=peek(pk+15)
- 180 if (s and 1) then gosub 380 : return
- 190 print"scanning directory..." : print
- 200 db=bl+256*bh
- 210 if dc=0 then 360
- 220 for dp=db to db+32*(dc-1) step 32
- 230 if peek(dp)=0 or peek(dp)=229 then 350
- 240 if peek(dp+12) and 24 then 350
- 250 dl=dl+1
-
- This next line is where I set the default selection status, translation type,
- and CBM file type for the MS-DOS files. You can change these defaults simply
- by overtyping the string in ( | ||| ||| ) the "V" locations.
- V VVV VVV
- 260 d$=right$(" "+str$(dl),3)+" asc seq " : rem ** default sel/tr/ft **
- 270 a$="" : fori=0to10 : a$=a$+chr$(peek(dp+i)) : next
- 280 a$=left$(a$,8)+" "+right$(a$,3)
- 290 print dl; a$
- 300 d$=d$+a$+" "
- 310 cl(dl)=peek(dp+26)+256*peek(dp+27)
- 320 sz=peek(dp+28)+256*peek(dp+29)+65536*peek(dp+30)
- 330 di$(dl)=d$+right$(" "+str$(sz),6)
- 340 sz(dl)=sz
- 350 next dp
- 360 return
- 370 :
- 371 rem ** report ms-dos disk error **
- 380 print chr$(18);"ms-dos disk error #";mid$(str$(e),2);
- 390 print " ($";mid$(hex$(e),3);"), press key.";chr$(146)
- 400 getkey a$ : return
- 410 :
- 411 rem ** screen heading **
- 420 printchr$(147);"ms-dev=";mid$(str$(dv),2);" ms-type=";dt$;
- 430 print" cbm-dev=";mid$(str$(cd),2):print
- 440 return
- 450 :
- 451 rem ** screen footing **
- 460 print il$;"d=directory m=ms-dev f=cbm-dev q=quit"
- 470 print"t=toggle-column, c=copy-files, +/- page";
- 480 return
- 490 :
- 491 rem ** main routine **
- 500 t=1 : c=0
- 510 r=0
- 520 gosub 420
- 530 print "num s trn typ filename ext length"
- 540 print "--- - --- --- -------- --- ------"
- 550 gosub 460
- 560 b=t+17 : if b>dl then b=dl
- 570 print fl$;: if t>dl then 590
- 580 for i=t to b : print di$(i) : next
- 590 if dl=0 then print chr$(18);"<no files>";chr$(146)
- 600 if dl=0 then 660
- 610 print left$(il$,r+5);chr$(18);
- 620 on c+1 goto 630,640,650
- 630 print spc(4);mid$(di$(t+r),5,3) : goto 660
- 640 print spc(7);mid$(di$(t+r),8,5) : goto 660
- 650 print spc(12);mid$(di$(t+r),13,5) : goto 660
- 660 getkey a$
-
- Oh shi^Hoot. I screwed up the following line in the string after the
- "+chr$(13)+" part. You'll notice that I have avoided putting cursor control
- characters into the strings everywhere else, but I forgot to do that here.
- The "{stuff}" should be CursorUp, CursorDown, CursorLeft, CursorRight,
- CursorHome, and CursorCLR control characters, respectively. These characters
- give the index for the "on" statement below.
-
- 670 i=instr("dmftc+-q "+chr$(13)+"{stuff}",a$)
- 680 print left$(il$,r+5);di$(t+r)
- 690 if i=0 then 600
- 700 onigoto760,1050,1110,950,1150,1000,1020,730,860,860,770,790,810,830,850,500
- 710 stop
- 720 :
- 721 rem ** various menu options **
- 730 print chr$(147);"have an awesome day."
- 740 end
- 760 gosub 420 : gosub 140 : goto 500
- 770 r=r-1 : if r<0 then r=b-t
- 780 goto 600
- 790 r=r+1 : if t+r>b then r=0
- 800 goto 600
- 810 c=c-1 : if c<0 then c=2
- 820 goto 600
- 830 c=c+1 : if c>2 then c=0
- 840 goto 600
- 850 r=0 : c=0 : goto 600
- 860 if dl=0 then 600
- 870 x=t+r : on c+1 gosub 890,910,930
- 880 print left$(il$,r+5);di$(x) : goto 600
- 890 if mid$(di$(x),6,1)=" " then x$="*" :else x$=" "
- 900 mid$(di$(x),6,1)=x$ : return
- 910 if mid$(di$(x),9,1)="a" then x$="bin" :else x$="asc"
- 920 mid$(di$(x),9,3)=x$ : return
- 930 if mid$(di$(x),14,1)="s" then x$="prg" :else x$="seq"
- 940 mid$(di$(x),14,3)=x$ : return
- 950 if dl=0 then 600
- 960 for x=1 to dl
- 970 on c+1 gosub 890,910,930
- 980 next x
- 990 goto 520
- 1000 if b=dl then t=1 : goto 510
- 1010 t=t+18 : goto 510
- 1020 if t=1 then t=dl-(dl-int(dl/18)*18)+1 : goto 510
- 1030 t=t-18 : if t<1 then t=1
- 1040 goto 510
- 1050 print il$;chr$(27);"@";
- 1060 input"ms-dos device number (8-30)";dv
- 1061 if cd=dv then print"ms-dos and cbm-dos devices must be different!":goto1060
- 1070 input"ms-dos device type (71/81)";x
- 1080 if x=8 or x=81 or x=1581 then dt=255:dt$="1581" :else dt=0:dt$="1571"
- 1090 poke pk+16,dv : poke pk+17,dt : sys pk
- 1100 goto 520
- 1110 print il$;chr$(27);"@";
- 1120 input "cbm-dos device number (0-30)";cd
- 1130 if cd=dv then print"ms-dos and cbm-dos devices must be different!":goto1120
- 1140 goto 520
- 1141 :
- 1142 rem ** copy files **
- 1150 print chr$(147);"copy files":print:print
- 1160 if dl=0 then fc=0 : goto 1190
- 1170 fc=0 : for f=1 to dl : if mid$(di$(f),6,1)="*" then gosub 1200
- 1180 next f
- 1190 print : print"files copied =";fc;" - press key"
- 1191 getkey a$ : goto 520
- 1200 fc=fc+1
- 1210 x$=mid$(di$(f),19,8)+"."+mid$(di$(f),29,3)
- 1220 cf$="":fori=1tolen(x$):if mid$(x$,i,1)<>" " then cf$=cf$+mid$(x$,i,1)
- 1230 next
- 1231 if right$(cf$,1)="." then cf$=left$(cf$,len(cf$)-1)
- 1232 cf$=cf$+","+mid$(di$(f),14,1)
- 1240 print str$(fc);". ";chr$(34);cf$;chr$(34);tab(20);sz(f)"bytes";
- 1245 print tab(35);mid$(di$(f),9,3)
- 1250 cl=cl(f) : lb=sz(f) - int(sz(f)/65536)*65536
-
- I had to use a DOPEN statement here for disk files because the regular OPEN
- statment does not redirect the DS and DS$ pseudo-variables. You'll notice
- that the non-disk OPEN statment below has a secondary address of 7. This is
- to put the printer into lowercase mode if you are outputting directly to it.
- You can replace this with a 5 (or whatever) if you have a special interface
- to an IBM-compatible printer and you want to print directly in ASCII. In this
- case, you would select the "BIN" translation mode for the file you are routing
- directly to the printer.
-
- 1260 if cd>=8 then dopen#1,(cf$+",w"),u(cd) :else if cd<>0 then open 1,cd,7
- 1265 if cd<8 then 1288
- 1270 if ds<>63 then 1288
- 1275 x$="y" : print "file exists; overwrite (y/n)";
- 1280 close 1 : input x$ : if x$="n" then fc=fc-1 : return
- 1285 scratch(cf$),u(cd)
- 1286 dopen#1,(cf$+",w"),u(cd)
- 1288 if cd<8 then 1320
- 1300 if ds<20 then 1320
- 1310 print chr$(18)+"cbm disk error: "+ds$ : fc=fc-1 : close1 : return
- 1320 poke pk+19,cl/256 : poke pk+18,cl-peek(pk+19)*256
- 1330 poke pk+21,lb/256 : poke pk+20,lb-peek(pk+21)*256
- 1340 tr=0 : if mid$(di$(f),9,1)="a" then tr=255
- 1346 x=1 : if cd=0 then x=0
- 1350 sys pk+6,tr,x
- 1355 rreg x,x,x,s : e=peek(pk+15)
- 1356 if (s and 1) then gosub 380 : fc=fc-1
- 1360 if cd<>0 and cd<8 then close1
- 1370 if cd>=8 then dclose#1 : if ds>=20 then 1310
- 1380 return
-
- 6. UUENCODED FILES
-
- Here are the binary executables in uuencoded form. The CRC32s of the two
- files are as follows:
-
- "lrr.128" 1106058594
- "lrr.bin" 460671650
-
- The "lrr.128" file is the main BASIC program and the "lrr.bin" file contains
- the machine lanugage disk-accessing routines.
-
- [LRR.128 and LRR.BIN are included in this archive. -RW]
-
- 7. BIBLIOGRAPHY
-
- The following works were consulted in creating this article:
-
- [1] Jim Butterfield, "Jim Butterfield's Complete C128 Memory Map",
- _The_Transactor_, Volume 7, Issue 01, July 1986 (A Must!).
-
- [2] Commodore Business Machines, _Commodore_1571_Disk_Drive_User's_Guide_,
- CBM, 1985.
-
- [3] Some program called "msdos-to-128" included with "cs-dos" by
- M. G-something. Originally published in COMPUTE!'s Gazzette, I think.
-
- [4] Commodore Business Machines, _Commodore_128_Programmer's_Reference_Guide_,
- Bantam Books, 1986.
-
- [5] _The_Transactor_, Volume 4, Issue 05 ("The Reference Issue"), May 1983.
-
- =============================================================================
-