Inside Commodore DOS
Inside Commodore DOS
Inside Commodore DOS
and
Technical Illustrations by
Diane M. corralejo
fflDATAMOST
19821 Nordhoff Street, Northridge, C A 91324 (818) 709-1202
ISBN 0-8359-3091-2 Copyright 1984 by DATAMOST, Inc. All Rights Reserved This manual is published and copyrighted by DATAMOST, Inc. All rights are reserved by DATAMOST, Inc. Copying, duplicating, selling or otherwise distributing this product is hereby expressly forbidden except by prior written consent of DATAMOST, Inc. The words COMMODORE, CBM, COMMODORE 64, VIC-20, VIC-1541 and the Commodore logo are registered trademarks of Commodore Business Machines, Inc. Commodore Business Machines was not in any way involved in the writing or other preparation of this manual, nor were the facts presented here reviewed for accuracy by them. The information presented in this manual is the result of intensive study of the disassembly of the 1541 DOS. Every effort has been made to provide error-free information. However, neither the authors nor DATAMOST, Inc. can accept responsibility for any loss or damage, tangible or intangible, resulting from use or improper or unintended use of this information.
Printed in U.S.A.
ACKNOWLEDGEMENTS
A manual like this one would not be possible without a great deal of technical assistance. Mike Todd's Disk File column in the ICPUG Newsletter proved to be an invaluable source of insight into the inner workings of Commodore's DOS. Raeto West's book, Programming the PET/CBM, was a constant companion. Jim Butterfield's numerous articles also provided valuable bits and pieces of information. Brad Templeton's POWER system and PAL assembler made the development of the programs in this manual a real joy. These packages are commercially available from Professional Software Inc. In addition, both the PAL disassembler and MICROMON were used as tools for disassembling the 1541 DOS. We would also like to acknowledge the patience and forebearance of our families and friends. Without their support, producing this manual would have been considerably more difficult. Mike Louder of DATAMOST, Inc. also provided tremendous support for its production. Finally, we would like to extend a special note of thanks to Dr. Tom MacNeil and Nancy Neufeld for their diligent work in proofreading this manual. This manual was written on a Commodore computer system using the WordPro 4 Plus word processing system. The WordPro Plus Series is commercially available from Professional Software Inc. This sophisticated word processing system made editing and last minute revisions much easier.
TABLE OF CONTENTS
Chapter 1 - INTRODUCTION A Brief Word About the Programs How to Type in the Programs Chapter 2 - USING THE 1541'S DOS The Purpose of DOS Communicating with the 1541 The Command Channel Using the Command Channel Diskette Housekeeping Chapter 3 - DISKETTE FORMATTING Layout of Tracks and Sectors Layout of a Sector The Header Block The Data Block Chapter 4 - DISKETTE ORGANIZATION Information Management The Directory You See The Block Availablity Map The Directory Entries Program File Storage Sequential File Storage Relative File Storage User File Storage Deleted File Storage Locked Files Chapter 5 - DIRECT-ACCESS PROGRAMMING Introduction to Direct-Access Programming Beginning Direct-Access Programming Block-Read Command Buffer-Pointer Command Block-Write Command Memory-Read Command Memory-Write Command Block-Allocate Command Block-Free Command Memory-Execute Command Block Execute Command Direct-Access Entomology 11 11 12 15 15 15 16 17 20 29 29 31 32 33 35 35 35 36 40 48 53 56 69 69 70 71 71 71 73 75 77 81 85 89 94 96 97 98
Chapter 6 - INTERMEDIATE DIRECT-ACCESS PROGRAMMING Chapter 7 - DOS PROTECTION Commodore's Data Encoding Scheme Checksums Description of DOS Error Messages Analyzing a Protected Diskette Duplicating a Protection Scheme How to Create 21 Errors on a Full Track How to Create a 21 Error on a Single Sector How to Create a 23 Error on a Single Sector How to Duplicate a 23 Error on a Single Sector How to Create 23 Errors on a Full Track How to Create 20 Errors on a Full Track How to Create 27 Errors on a Full Track How to Create a 22 Error on a Single Sector How to Duplicate a 22 Error on a Single Sector How to Format a Diskette with Multiple IDs How to Backup a DOS Protected Diskette How to Copy a File Chapter 8 - GETTING OUT OF TROUBLE Unscratching a File Recovering a Soft Sector Recovering a Hard Sector Recovering a Relative File Recovering an Entire Diskette Recovering a Physically Damaged Diskette Recovering an Unclosed File Recovering from a Short New Recovering from a Full New Chapter 9 - OVERVIEW OF THE 1541 DOS Introduction to 1541 DOS The Hard Working 6502 Major IP Routines Using the IP Routines Major FDC Routines Using the FDC Routines The Recording Process Block Diagram of the 1541 Writing Data to a Diskette Reading Data From a Diskette Summary Bugs in DOS 2.6 Write Incompatability with 4040 Late News
103 113 113 118 119 122 123 124 126 129 133 137 144 150 155 156 158 162 168 173 173 175 175 176 177 177 177 178 179 181 181 181 182 185 188 193 199 201 202 204 206 208 215
Appendix A - 1541 RAM VARIABLE DEFINITIONS Appendix B - ANALYSIS OF THE 1541's ROM Appendix C - PROGRAM LISTINGS Appendix D - MATHEMATICAL CONVERSION ROUTINES Index
CHAPTER 1
INTRODUCTION
This manual is intended to supplement the documentation provided in the 15^1 User's Manual. Although this manual is primarily designed to meet the needs of the intermediate to advanced programmer, it will also be ofinterest to the novice Commodore user who wants to know more about how his 1541 disk drive works. This manual is not intended to replace the documentation provided by Commodore Business Machines, Inc. and the reader is assumed to be relatively familiar with the contents of the 15J+1 User's Manual. For the sake of continuity and clarity, some of the information covered in the 15U1 User's Manual is also presented here. However, the majority of the information presented in this manual is original and is the result of intensive disassembly and annotation of the 1541's DOS by the authors. Some information is based on articles and notes published in a variety of publications as well as discussions with other knowledgeable disk experts. This manual was not prepared with the assistance of Commodore Business Machines, Inc. Although we cannot guarantee the accuracy of all the information presented in this manual, the material has been thoroughly researched and tested. There were several reasons for writing Inside Commodore DOS: 1. 2. 3. 4. 5. 6. 7. 8. To correct errors and omissions in the 15J>1 User's Manual. To help you make more effective use of your disk drive. To provide complete information on diskette formatting. To provide complete information on the storage of files. To allow you to read and write data in non-standard ways. To help you make a backup copy of your "protected" diskettes. To help you recover damaged diskettes. To help you understand the operation of your disk drive.
Although this manual focuses primarily on the 1541 disk drive, much of the information also applies to other Commodore disk drives.
11
The programs in this book are disk utilities. They do not use flashy graphics or sound. Rather, they are extremely powerful tools. Remember, any tool can be dangerous if it is used improperly. Be sure that you know what you are doing before you use a given program. Always experiment with a program on a test diskette before you actually use it on one that contains valuable programs or data. Practice makes perfect. Each program was individually tested on a variety of 1541 disk drives having a wide range of serial numbers. Moreover, each program always worked perfectly. Unfortunately, it is impossible to guarantee that a particular program will work with your model. If a given program does not seem to work properly, check your typing carefully. Any errors, especially in the DATA statements which contain a machine language program, will produce problems. As a courtesy to the more advanced programmer, we have also included the source listings for each machine language routine. A source listing immediately follows a related BASIC program listing and has a file name ending in "PAL". It is for use with the PAL assembler. Note: Ifyou are using a different assembler, you may have to make some minor changes. The programs in this book were designed to be not only useful and beneficial, but instructive as well. Many of them illustrate the "state of the art" in the use of Commodore's direct-access disk commands. Enjoy!
When You See {CLR} {HOME} {DOWN} {UP} {RIGHT} {LEFT} {RVS} {ROFF}
What It Represents Clear Screen Home Cursor Cursor Down Cursor Up Cursor Right Cursor Left Reverse Field ON Reverse Field OFF
What You Type Hold down SHIFT and press CLR/HOME Press CLR/HOME Press CRSR/DOWN Hold down SHIFT and press CRSR/UP Press CRSR/RIGHT Hold down SHIFT and press CRSR/LEFT Hold down CTRL and press 9 Hold down CTRL and press 0
12
NOTE 1: When a number appears inside the curly brackets, it means you repeat the control character immediately to the left of the number that many times. For example: {DOWN 5} means to press CRSR/DOWN five (5) times. NOTE 2: All programs have been listed in a column 40 characters wide. Except where special characters have been spelled out between curly brackets, the lines are listed exactly as they appear on a Commodore 64 display. Spaces must be typed in as listed. Where necessary, count the character columns to determine the appropriate number of spaces. Happy hunting and pecking!
13
CHAPTER 2
In many computer systems, a DOS is loaded into the main computer's memory from diskette when the computer is first switched on. In this type of system many of the tasks are carried out using the computer's microprocessor and RAM. Commodore uses a different approach. All of Commodore's disk drives are intelligent peripherals. They do not have to use the computer's resources; they have their own. For example, the 1541 disk drive contains its own 6502 microprocessor, 2K of RAM, two 6522 I/O chips, and a DOS program permanently stored in 15.8K of ROM. The advantages of having an intelligent disk drive are: 1. 2. 3. 4. The DOS does not use any of the computer's memory. Some disk operations can be carried out independently from the CPU. Disk operations do not slow down processing. One disk drive can be shared among several computers.
The disadvantages of having an intelligent disk drive are: 1. It is very difficult to customize DOS routines. 2. You must replace the ROMs to convert to a new version of DOS.
15
Let's examine each of these in greater detail. 1. LOAD, SAVE, and VERIFY commands: These BASIC commands are used to store and retrieve programs on the Commodore tape and disk drives. They are designed for ease of use, even by the novice. The BASIC interpreter in the computer interprets these commands and sends the disk drive the necessary information over the serial bus. 2. I/O using the command channel: The command channel is used to send messages to the disk drive to carry out disk operations like: formatting a blank diskette, erasing an unwanted file, renaming a file, etc. These operations are often referred to as disk housekeeping. The command channel is also used to input messages, such as the current error status of the drive, generated by the DOS. For more details on how to use the command channel, see Section 2.4. 3. I/O using data communication channels: The 1541 DOS supports a variety ofkinds offiles:program files, sequential files, relative files, user files, and direct-access files. The storage and retrieval of information in files is carried out using a data communication channel. Although this manual provides detailed information regarding how files are stored and organized, no attempt is made to teach you how to develop programs that make extensive use of file handling. We would encourage readers who are interested in file handling techniques to refer to Jim Butterfield's series of articles in COMPUTE!. The only I/O applications discussed in this manual are those relating to direct-access programming (see Chapter 5). Since the rest of this manual makes extensive use of the command channel, let's examine it in some detail.
16
Let's go over each step to ensure that you know exactly what to do. 1. Establishing communications using an OPEN statement. In order to establish a communication channel between your computer and your 1541 disk drive, you use an OPEN statement. An OPEN statement is a BASIC command which looks like this:
SYNTAX: EXAMPLE: OPEN f i l e # , OPEN 15, 8, device#, 15 channel#
device# = the device number (8 for a stock 1541) channel# = the channel number or secondary address (2-15) NOTE: Channel numbers 0 & 1 are reserved for use by the DOS. Channel numbers 2-14 are data communications channels. Channel number 15 is the command channel. The OPEN statement can be used either in immediate mode (typed and executed directly from the keyboard) or under program control (embedded in a program). In the example above (OPEN 15, 8, 15) we opened logical file number 15 on the C64 to device number 8 (the disk drive) through channel 15 (the command channel). 2. Sending commands to the DOS using a PRINT# statement. In order to send commands from your computer to the 1541, you use a PRINT# statement. A PRINT# statement is a BASIC command which looks like this:
SYNTAX: EXAMPLE: PRINT# f i l e # , PRINT#15, "command" DISKETTE,MD"
"NO:MY
17
where file# = the logical file number you used when you opened the command channel
command = the disk command to be sent to the DOS NOTE: The statement is PRINT# not PRINT #. You must not put a space before the # sign. Spaces following the # sign are always optional. DO NOT use ?# as an abbreviation either. The correct abbreviation is pR(p then SHIFTED R). In this example, the disk command is "NO:MY DISKETTE,MD". This command causes the DOS to prepare the blank diskette in the drive for first-time use. Although there are many different disk commands, they fall into two groups: 1. Commands related to disk housekeeping. 2. Commands to read or write data to a diskette or the disk drive's RAM. The disk housekeeping commands are discussed in the next part of this chapter. The commands relating to reading or writing data are discussed in Chapter 5 on Direct-Access Programming. 3. Reading DOS messages using a GET# or an INPUT# statement. You may use either an INPUT# or a GET# statement to read the command channel and access any messages or data prepared for the computer by the DOS. Both INPUT# and GET# statements are BASIC commands. They look like this:
SYNTAX: EXAMPLE: INF'UT# f i l e # , v a r i a b l e l i s t GET# f i l e # , v a r i a b l e l i s t INPUT# 15, EN, GET# 15, A* EM*, ET, ES
where file# = the logical file number you used when you opened the command channel
variable list = one or more variable names separated by commas NOTE: As was noted for PRINT# above, the BASIC statements are INPUT# and GET#, not INPUT # and GET #. You must not put a space before the # sign. Spaces following the # sign are always optional. Neither the INPUT# statement nor the GET# statement can be used in immediate mode (typed and executed directly from the keyboard). They must be included within a program. The INPUT# command and the GET# command operate in much the same way as the more familiar INPUT and GET commands. INPUT# always reads as far as the next carriage return character while GET# reads a single byte of information. Generally, GET# is used in direct-access programming and INPUT# is used only for monitoring the drive's error status as indicated immediately below.
18
You can check the error status ofyour disk drive using the command channel. The DOS monitors every disk operation as it is carried out and prepares a status report indicating whether or not the operation was completed successfully. The report consists of an error code number, an English language message, and the track and sector where the problem, if any, was encountered. Here is a subroutine that checks the error status.
lOO OPEN 1 5 , 8 , 1 5 : REM THE OPEN COMMAND CHANNEL 500 I N P U T # 1 5 , E N , E M * , E T , E S : REM INPUT THE ERROR STATUS 510 I F EN < 20 THEN RETURN : REM NO ERROR ENCOUNTERED 520 PRINT E N ; E M * ; E T ; E S : REM PRINT THE ERROR STATUS ON SCREEN 530 CLOSE 15 : END : REM ABORT ON BAD STATUS
Line 100 opens the command channel. It is a good idea to open the command channel at the beginning of your program and leave it open until the end. Line 500 inputs the status report. The error code number is stored in EN, the message in EM$, the track in ET, and the sector in ES. Error codes less than 20 may be ignored Qine 510). A complete list of the error codes and messages is contained in the back of your 15J^1 User's Manual. A detailed explanation of the nature and cause of many of these errors is provided in Chapter 7 on Disk Protection. 4. CLOSE the command channel when you are done. After you have finished using the command channel, it should be closed. Recall that the open command has three parameters: the logical file number, the device number, and the channel number. The close command has only one, the logical file number. It looks like this:
SYNTAX: EXAMPLE: CLOSE file#
CLOSE 15
where file# = the logical file number you used when you opened the command channel NOTE: Loading, running, or editing a program closes down all communication channels automatically. The command channel is closed properly in each instance. However, data channels are aborted rather than closed. When a data channel is aborted, the file is NOT CLOSED properly on the disk drive. You do not have to close the command channel after the issuance of every command. If you forget to close it, the worst that can happen is a ?FILE OPEN ERROR when you attempt to open it again. However, you should get into the habit of always closing a file when you are finished using it. You won't get into trouble leaving the command channel open, but you may lose an important data file if you leave a data communication channel open.
19
These operations are carried out by the DOS in response to commands sent to the drive using the command channel as indicated above. Once a disk housekeeping command is issued, the disk drive will carry out the task without further intervention by the computer. This means that you could edit or even RUN a program in RAM while the disk drive busily formats or validates a diskette. This is not really spooling. It occurs because the 1541 is an intelligent peripheral. The only thing that will cause your computer to wait for the disk drive to complete its task is your attempting to perform another disk operation. This includes closing the command channel. Let's take a look at the disk commands used for housekeeping. NOTE: If you are using the DOS SUPPORT program that came on your 1541TEST/DEMO, the syntax for these disk commands is remarkably shorter. The > or @ keys are used to send a command to the disk drive. They take the place of the PRINT# statement. In addition, you do not have to open or close the command channel or embed the disk command in quotation marks. The DOS SUPPORT program will do this automatically for you. The DOS 5.1 syntax can be used only in immediate mode, however. It cannot be used in a program or a ?SYNTAX ERROR will result. The New Command When a fresh diskette is taken from its storage envelope, the 1541 cannot recognize it. The diskette must be formatted or newed prior to first-time use. Formatting or newing a diskette is performed by the DOS. The DOS proceeds to write concentric tracks made up of blocks/sectors to the diskette. In addition, a directory is set up, wherein the drive records information about all the files stored on the diskette. Chapter 3 provides a much more detailed account of this operation. The syntax for formatting a diskette is really quite simple:
SYNTAX: OPEN 15, 8 , 15 PRINT#15, "NO:DISK CLOSE 15 PRINT#15, NAME,ID"
ALTERNATE: EXAMPLE:
"N:DISKNAME,ID" DISKETTE,MD"
DOS 5 . 1 :
20
The disk command, "NO:MY DISKETTE,MD", is sent to the drive by the PRINT#15 statement. The command has three parameters. The first parameter within quotes is N0:. The N stands for NEW. The 0 is a holdover from the dual drive system and indicates which drive. The 0 is optional on the 1541 and may be omitted. The colon terminates the DOS command. The second parameter is the disk name. It is limited to 16 characters in length. Generally these are alphanumeric characters. In the example above, we named the diskette: MY DISKETTE. The disk name is cosmetic and appears in the directory for reference purposes only. It is not written anywhere else on the diskette. The disk name is followed by a comma. The DOS looks or parses for this. After the comma are two alphanumeric characters for the disk ID. In the above example we selected MD as our disk identifier. The ID is written to every block or sector on the diskette. It is impossible to alter. The DOS repeatedly looks at the ID of a sector to be sure that you have not switched diskettes on it. Each diskette should be formatted with a unique ID. This will prevent the DOS from inadvertently overwriting programs on what appears to be an identical diskette. A "full" new on a diskette takes roughly 2-3 minutes. There is a quicker way to erase a diskette that has already been used. This is accomplished by leaving off the disk ID. For example:
SYNTAX: OPEN 15, 8 , 15 PRINT#15, "NO:DISK NAME" CLOSE 15 PRINT#15, "N:DISK NAME" DISKETTE 1
ALTERNATE: EXAMPLE:
DOS 5 . 1 :
Notice that no comma or ID follows the disk name. This command will work only on a diskette that has previously been formatted. It is referred to as a "short" new. A "short" new simply erases the first sector in the directory and writes an empty BAM ftlock availability map) to tell the DOS that we have a fresh diskette in use. NOTE: A diskette that is plagued by read or write errors does not have to be pitched. Copy the files to another diskette first. Then do a "full" new on the offending diskette. This will erase and reformat the entire diskette. A "short" new rewrites only sectors 0 and 1 of track 18 and will not eliminate any read or write errors. See Chapter 8 about how to recover from both a "short" new and a "full" new. The Initialize Command Initialization has nothing to do with formatting. APPLE owners format a diskette by "initializing" it. This is NOT TRUE with Commodore. Initializing a diskette forces the DOS to read the disk ID and the contents of the BAM and store them in the drive's internal memory. The BAM establishes where the next available sector is for writing. Without it files would be overwritten. To initialize a diskette perform the following:
21
SYNTAX:
ALTERNATE: DOS 5 . 1 :
>1 The I is short for INITIALIZE. The drive number can be ignored if you are using only one 1541. The drive motor purrs for a few seconds and then settles down. It's that simple. It is a good habit to initialize a diskette each time you insert it into your 1541 drive. This point cannot be overemphasized. Do it yourself. Do not rely upon the "autoinit" feature of the drive. Initialization prevents the DOS from overwriting files in the event that two diskettes with identical IDs are swapped. The drive cannot tell the difference between two diskettes with identical IDs since it is the ID that the DOS uses to identify a diskette. Initialization also assures you that a diskette is properly seated in the drive before use. The 1541 drive has a built in autoinitialization feature. Once it encounters an error it will retry a disk operation several times. Often it can recover from an error on its own. If it fails, it gives up. Before doing so, though, it will do a "bump." On a bump the read/write head is stepped outwards 45 tracks (slight overkill) to assure that it is on track 1. The drive clatters when a protrusion on the stepper motor's drive pulley bumps up against a mechanical stop. (It really isn't a melt down.) The head then steps inwards to track 18 and the DOS awaits further instructions. Seff initialization avoids this scenario. Initialize every time you insert a diskette into the drive. Initialization clears the error channel and turns off the flashing red LED. Unless, of course, you are trying to initialize an unformatted diskette or forgot to put one in the drive to begin with. Clearing the error channel destroys the error status the DOS prepared for you. If error checking is important, retrieve the error message first; then initialize the drive. The Rename Command Occasionally you will want to change the name of a file stored on a diskette. To rename a file you first open the command channel and then send the rename command like this:
SYNTAX: OPEN 15, 8 , 15 PRINT#15, "R0:NEW NAME=OLD NAME" CLOSE 15 PRINT#15, "R:NEW NAME=OLD NAME"
ALTERNATE: EXAMPLE:
OPEN 15, 8 , 15 PR I NT#15 , " R0: D I SPLAY TScS=DTS " CLOSE 15 >RO:NEW NAME=OLD NAME >R:NEW NAME=OLD NAME
DOS 5 . l :
22
Again the syntax is exacting but simple to follow. The R0: means to rename on drive 0. It is short for RENAME0:. As before, the 0 is optional on the 1541. The next parameter is the new file name. A file name is generally alphanumeric in nature and 16 characters are allowed at the maximum. (Commas, colons, semicolons, and wild cards are not permitted. Cursor control and reverse video characters should be avoided.) The new file name is followed by an " = " sign. The last parameter is the existing or old file name. It must be spelled out exactly as it appears in the directory. Wild cards (*,?) are not allowed. If you make a typo on this parameter or the file does not appear in the directory, the rename command fails. No damage is done, so relax. In the above example our new file name is DISPLAY T&S. It replaces the old file name DTS. One final point. You cannot rename a file that is currently open for a read or write. The Copy Command The copy command allows you to easily backup an existing file on your diskette. There are three restrictions attached. First, the new file must have a different name. Second, the copy command will not work on a relative file. Third, you must have enough room on the diskette. The copy command looks like this:
SYNTAX: OPEN 15, 8 , 15 PRINT#15, "CO:BACKUP=0:ORIGINAL" CLOSE 15 ALTERNATE: PRINT#15, "C:BACKUP=ORIGINAL"
EXAMPLE: OPEN 15, 8 , 15 PRINT#15, "CO:MY PROGRAM B/U=0:MY CLOSE 15 DOS 5 . 1 : >CO:BACKUP=0:ORIGINAL >C:BACKUP=ORIGINAL
PROGRAM"
The C is short for COPY. The new file above is called MY PROGRAM B/U. It is a backup copy of a previous program called MY PROGRAM. Note that we must specify the drive number twice. Again this is a holdover from a dual drive configuration. The C does not appear twice, however. The same restrictions that apply to the rename command are also in effect here, i.e., 16 character file name limit, use of restricted characters, etc. The drive number is optional. See the alternate syntax to save a few keystrokes. It is also possible to merge two or more sequential data files using the copy command. The syntax for this is as follows:
SYNTAX: OPEN 15, 8 , 15 PRINT#15, "CO:COMBINED=0:FILE1,0:FILE2, 0:FILE3" CLOSE 15
23
ALTERNATE: PRINT#15,
"C:CQMBINED=FILE1,FILE2,FILE3"
EXAMPLE: OPEN 15, 8, 15 PRINT#15, H CO:MAILFILE=0:NAME,0:ADDRESS, 0:CITY" CLOSE 15 DQS 5-1: >CO :COMBINED=0:FILE1,0:FILE2,0:FILE3 >C:COMBINED=FILE1,FILE2,FILE3 Our large file now consists of several files appended together. While this feature of the copy command is available, it is rarely used. Few programming techniques would require or ever utilize this feature. Note that this technique cannot be used to append a subroutine onto a BASIC program; the subroutine cannot be merged into the main program by the disk drive. You will need to use a programmer's aid like POWER, SYSRES, or BASIC AID for the C64 to do this. The Scratch Command To get rid of an unwanted file, we scratch it. The only exception is an unclosed file. An unclosed file is one that appears in the directory as having zero blocks and whose file type is preceded by an asterisk (*SEQ, *PRG, etc.). This will be explained below. To scratch a file, first remove the write protect tab and key in: SYNTAX: OPEN 15, 8, 15 PRINT#15, "SG:FILE NAME" CLOSE 15 "S:FILE NAME"
123"
DOS 5.1:
The scratch command requires a single parameter, the file name, preceded by S or SCRATCH. As before, the drive number is optional. There are some variations that incorporate wild cards. Wild cards in a file name are asterisks (*) or question marks (?). They should be used with utmost caution since more than one file can be scratched at a time. EXAMPLE: OPEN 15, 8, 15 PRINT#15, "SO:T*" CLOSE 15 DOS 5.1: >SO:T*
24
In the above example all files beginning with the letter T, regardless of file type, will be scratched. In the event that no file starts with the letter T, none will be affected. Careless use of a wild card can have catastrophic results. For example:
EXAMPLE: OPEN 15, 8 , 15 PRINT#15, "SO:*" CLOSE 15 >so:*
DOS 5 . l :
The above command will scratch every file on the diskette. It is the equivalent of performing a short new on a diskette. Be careful! The second wild card is the question mark. It is used to mask out characters that are not of importance. Suppose we want to scratch a number of files whose names are all eight characters long and end in .C64. We could not use .C64* to scratch them since the match falls at the end of the file name. However, we could use:
EXAMPLE: OPEN 15, 8 , 15 PR I NT# 15, " SO: ? ? ? ? . C6411 CLOSE 15 >SO:????-C64
DOS 5 . 1 :
Note that we used four question marks in the above example. An exact match of .C64 must occur on characters 5 through 8 of the file name. No match no scratch. If we had 1541.C64 and C100.C64 on the disk, both would be scratched by the previous command. However, BACKUP.C64 would not be affected. More than one wild card can be used within the same command. For example:
EXAMPLE: OPEN 1 5 , 8 , 1 5 PRINT#15, " S O : T ? S T * " CLOSE 1 5 >S0:T7ST*
DOS 5 . 1 :
This command would scratch files with these names: TEST, TASTY, TESTING123. The file TOAST would not be affected. Note that it makes no sense to send a command like this: "SO:T*ST???". The asterisk has priority over the question mark. All characters that appear after the asterisk are ignored. Afiletype that begins with a * is unclosed: *SEQ, *PRG, etc. It was never closed properly. This can happen for a variety of reasons: 1. The diskette may have been at its physical capacity and a disk-full situation occurred during a save or write to a diskette. 2. A bad sector may have been encountered during a write to a diskette.
25
3. The file may have been left open following a write operation because you forgot to CLOSE the file, or you aborted the program by hitting either the RUN/STOP key or the RUN/STOP and the RESTORE keys. 4. Your program had a syntax error in it and the BASIC interpreter returned you to immediate mode. (See Chapter 8 about how to recover an unclosed file.) Whatever the cause, an unclosed file should never be scratched! Since the write operation was aborted, the internal organization ofthe diskette (i.e., the BAM), has been left in disarray. It does not match the actual file contents of the diskette. Any further attempt to write to that diskette will probably cause a loss of one or more files. Files can actually overlap one another now and you will be left with a poisoned diskette. The DOS does have a command to decorrupt itself. This is the validate command. When in doubt, validate your diskette! The scratch command does not actually erase the file on your diskette. Rather it traces the file across the surface of the diskette and frees any sectors the file occupied. The file-type byte is also changed to a zero in the directory which indicates to the DOS that it is no longer active. If you inadvertently scratch afile that you didn't mean to, stop right then and there! You can recover it. Do not attempt to write to the diskette. The sectors just freed will be used on subsequent writes to the diskette. Once you write to the diskette, recovery is impossible. Chapter 8 on Getting Out of Trouble shows you how to recover a scratched file. The Validate Command This command tells the DOS to reconstruct its map which shows where information is stored on the diskette, so it conforms to the files listed in the directory. This is a simple way to decorrupt a damaged diskette. However, it is not a failsafe command as will be explained shortly. A validate command looks like this:
SYNTAX: OPEN 15, 8 , 15 PRINT#15, "VO" CLOSE 15 PRINT#15, >VO
ALTERNATE: DOS 5 . 1 :
>V The V is an abbreviation for VALIDATE. As before, the 0 is optional for the 1541 drive. What does a validate do? The DOS keeps a map that indicates which sectors on a diskette are currently in use. This map is stored on track 18, sector 0. It is referred to as the Block Availability Map or just the BAM for short. When the validate command is issued, all blocks are freed in the BAM on the diskette simulating a newly formatted blank diskette. The drive then picks up the first file in the directory and chains through the
26
entire file. As sectors are picked up along the way, they are allocated in the BAM as currently in use. If the file is traced successfully, all blocks associated with it are put back into the BAM as in use. The next file is then picked up out of the directory and the process continues. When all files have been traced, the new BAM is written to the diskette and the internal count now matches the directory contents. So far so good. Now let's see what happens to an unclosedfile.When the DOS encounters an unclosed file in the directory during a validate command, all it does is change the file type byte in the directory entry to a 0 (scratched file). No attempt is made to trace the file. When the validate operation is complete, the unclosed file will no longer appear in a directory listing and any blocks associated with it will be free. This is what you want to happen. Now let's see what happens if you attempt to SCRATCH an unclosed file. When you scratch a file, two things happen: the file-type byte in the directory for this file is set to 0 (scratched file) and the DOS traces through the chain of sectors that make up the file and marks each sector it encounters as available for use (free) in the BAM. This is just what you want to have happen for a normal file, but it can poison the diskette when you try it on an unclosed file. Here's why. The last sector of an unclosed file was never written out to the diskette. As a result, the second to the last sector points to a sector that is not really part of the file. The DOS doesn't realize this and continues to follow the "chain." If you are lucky, the "unwritten sector" will be a empty sector (never used since the disk was formatted). If this happens, the DOS will stop because pointers point to a non-existent track and sector (75,1). If you are unlucky, the "unwritten sector" will be part of a file that you scratched last week and the pointer will just happen to point into the middle of that very important file you just saved yesterday. When this happens, the DOS will merrily deallocate the remaining sectors in your file. The next write operation to the diskette will see this nice big open space and the new information will be saved right on top of your active file. Now the situation has gone from bad to worse and is in fact pathological hence a poisoned disk. The only solution is to inspect each file first to ensure that it is not tainted and then copy it onto another diskette. The validate routine is aborted if an error (an unreadable sector) is encountered. When it aborts, nothing radical occurs. The new BAM is not written to the disk until the validation process has been completed. Don't worry about the blank BAM getting you in trouble; the DOS will read the old one back in before it allows you to write to the disk. However, the diskette still remains corrupted with no quick remedy in sight. Chapter 8 on recovery deals with this and other disasters.
27
CHAPTER 3
DISKETTE FORMATTING
When you take a new floppy diskette out of the package, it is blank. Before the drive can store data onto it, it must be formatted. This is done by inserting the diskette into the drive and sending a NEW command to the DOS (see Section 2.5). During "formatting" or "newing," 35 concentric tracks are written to the diskette. Each track is made up of varying numbers of sectorsA>locks where programs and data will eventually be stored. In addition to laying down empty blocks/sectors, the DOS creates a directory and a block availability map (BAM) and records them on track 18. This chapter describes the formatting process and the tracks and sectors of a diskette. Chapter 4 describes the directory and the block availability map (BAM).
Although there are only 35 tracks, the stepper motor can position the read/write head to more than 70 different positions. This might seem to imply that additional tracks could be recorded on the surface of the diskette to increase its storage capacity. Unfortunately, the accuracy of the head positioning mechanism and the width of the path of magnetization produced by the readAvrite head makes the use of these "phantom" tracks unreliable. If you would like to experiment with this, the programs described in Chapter 9 allow you to experiment with stepping the head around. Each track is divided into seventeen or more sectorsft>locks).Each sector holds 256 bytes of data. (Some manufacturer's record data in 512 or 1024 byte sectors.) Whenever data is read from or written to a diskette, it is done one complete sector at a time. On Commodore disk drives, the tracks are not divided into a fixed number of sectors. The number of sectors depends on the track number. The outer tracks Qower numbers) are longer and are divided into more sectors than the inner (higher numbered) tracks. The table below summarizes how the diskette is organized. Organization of Tracks and Sectors on a 1541 Formatted Diskette Zone 1 2 3 4 Track Numbers 1 to 17 18 to 24 25 to 30 31 to 35 Range of Sector Numbers 0 0 0 0 to to to to 20 18 17 16 Total Sectors Per Track 21 19 18 17 Total Bytes Per Track 5376 4864 4608 4352
A total of 683 sectors are written at the time of initial formatting. Since the disk rotates at a constant speed of 300 rpm, you may wonder how Commodore manages to vary the number of sectors from zone to zone. This is accomplished by varying the rate at which data is read or written (changing the clock rate). Each of the four zones uses a different
30
clock rate. This is accomplished by using a high speed clock and dividing the clock by N, where the value of N is determined by the zone. The table below summarizes the clock rates for each zone. Zone 1 2 3 4 Tracks 1 to 17 18 to 24 25 to 30 31 to 35 Divisor 13 14 15 16 Clock Rate 307,692 285,714 266,667 250,000 bits/sec bits/sec bits/sec bits/sec Bits/Rotation 61,538.4 57,142.8 53,333.4 50,000.0
This scheme provides a recording density that varies from about 4000 bits/inch on the outer tracks to almost 6000 bits/inch on the inner tracks. If all of the possible bits could be used for data alone, we would be able to store a total of 2,027,676 bits or 253,459 bytes on a diskette. Unfortunately, not all of these bytes can be used for data. The total storage capacity of a diskette formatted on the 1541 is 174,848 bytes. The need for space to store a directory to keep track of the location of the files on a diskette (see Chapter 4) further reduces us to an effective storage capacity of 169,984 bytes (256 bytes * 664 sectors).
SECTOR #1
SECTOR #2
DATA BLOCK
HEADER BLOCK
HEADER BLOCK
The sectors are recorded in numerical sequence along the circular track. Each sector consists of an identifying header block followed by a data block. The sectors are separated from each other by an inter-record gap. A special character called a SYNC MARK is used to mark the beginning of each header or data block.
31
A SYNC MARK is a very special character. It consists of 10 or more 1 bits in a row (normally 40 of them). This particular pattern ofbits only occurs at the start of a header or data block. The hardware in the 1541 drive can detect this character and signal the DOS that a new data or header block is coming. If you are puzzled about why several $FF characters in a row in the data block are not interpreted as a sync character, you may want to skip ahead to the section on Commodore's GCR encoding scheme in Chapter 7.
SYNC MARK
HEADER BLOCK ID
SECTOR NUMBER
TRACK NUMBER
$0F BYTE
$0F BYTE
HEADER 6AP
NOTE: The header is recorded on disk exactly as indicated above. The diagram on page 54 of the 15U1 User's Manual is incorrect. Let's examine the bytes that make up the header block: Sync Mark: This consists of 10 or more 1 bits as described above. It warns the DOS that either a data block or a header block is coming. Header Block ID: This is normally a $08 byte. It serves to indicate to the DOS that this is a header block and not a data block. Header Block Checksum: This is a checksum character used by the DOS to ensure that the header block was read correctly. It is found by EORing the track number, the sector number, and the two ID characters. If you are not sure what an EOR is, you may want to read through Section 7.1. Sector Number: This byte is the number of this particular sector. The sectors are numbered consecutively around a track. Track Number: This byte is the number of this particular track. The DOS uses this byte to check to be sure that the record/play head is positioned to the correct track. ID Character # 2:This is the second ID character that you specified in the NEW command when the diskette was formatted (e.g., the 1 in "N0:GAMES,V1"). It is sometimes referred to as the ID HI. The DOS checks this byte against a master disk ID to ensure that you have not swapped diskettes.
32
ID Character #1: This is the first ID character that you specified in the NEW command when the diskette was formatted (e.g., the V in "N0:GAMES,V1"). It is sometimes referred to as the ID LO. The DOS checks this byte against a master disk ID to ensure that you have not swapped diskettes. $0F Bytes: These bytes are used as padding (spacing) by the DOS during initial formatting. They are called "OFF" bytes. Once formatting is complete OFF bytes are never referenced again. Header Gap: The header gap consists of eight $55 bytes. These eight bytes are used to provide breathing room between the header block and the data block. The DOS never reads these bytes. They allow the DOS time to set-up for reading the data block that follows. NOTE: The 4040 drive uses a nine byte header gap. This is one of the reasons why 1541 drives and 4040 drives are NOT WRITE COMPATIBLE! See Chapter 9 for more information. NOTE: A header block is written only during the formatting process. It is never rewritten again, period.
SYNC MARK
DATA BLOCK ID
$00 BYTE
INTERSECTOR 6AP
SYNC MARK
HEADER BLOCK ID
Let's examine the bytes that make up the data block: Sync mark: This consists of 10 or more 1 bits as previously described. It warns the DOS that either a data block or a header block is coming. Data Block ID: This byte is normally a $07. It serves to indicate to the DOS that this is a data block and not a header block ($08). 256 Data Bytes: This is the actual data stored in the sector. See Chapter 4 about how Commodore uses the first two bytes as a forward track and sector pointer instead of actual data. Data Block Checksum: This is a checksum character used by the DOS to ensure that the data block was read correctly. It is found by EORing all 256 data bytes together. $00 Bytes: These two bytes are also called OFF bytes. They are used to pad a data block before it is written. They are not referenced again by the DOS.
33
Inter-sector Gap: This is also known as the "tail gap." Its purpose is to provide breathing room between the end of the data block and the start of the next sector. The length of the gap varies from zone to zone and from one drive to another (see the chart in Section 7.1). Between consectutive sectors the gap is normally 4 to 12 bytes long. The gap between the last sector on a track and sector zero is often longer up to 100 bytes in length. The gap is designed to be long enough so that if you write a data block on a day when your drive is turning slightly faster than 300 rpm, you won't overwrite the start of the next sector. (Your drive may not be turning at exactly 300 rpm all the time because of fluctuations in the power supplied to your home or office, mechanical wear, belt slippage, changes in temperature, etc.) Note that the DOS never reads these bytes. The entire data block (including the preceding sync mark) is rewritten each time data is recorded on a diskette. This concludes our overview on how a diskette is formatted. Additional details about how bytes are encoded on the surface of a diskette are provided in Section 7.1. The actual recording process is described in Section 9.7.
34
CHAPTER 4
DISKETTE ORGANIZATION
4.1 Information Management
The information that is stored on a floppy disk is virtually useless unless it can be retrieved quickly. As a result, the organization and management of information is one of the most important tasks of the DOS. To do an efficient job of management, the DOS must be able to: 1. Keep track of which sectors contain data and which are still empty (available for use). 2. Assign names and storage locations to large blocks of related information (files). 3. Keep track of the sequence of sectors that were used to store a file. The DOS stores most of this information in the directory on track 18, haLfway between the outermost track (1) and the innermost track (35). Centering the directory serves to minimize head movement across the diskette and extends the life of both the drive and the media. The directory is subdivided into two areasthe map showing which sectors are in use and which are free (the Block Availability Map or BAM) and directory entries. The BAM resides solely on sector 0 of track 18. It informs the drive as to what sectors are currently in use and where subsequent writing to the diskette can safely take place. The remaining sectors (1-18) of track 18 contain directory entries (file names, file types, and pointers to where files are stored on the diskette).
then type
LIST
35
After a brief pause you should see the following on your screen:
" ZX O " 1541TEST/DEMO 13 "HOW TO USE" 5 "HOW PART TWO" 4 " V I C - 2 0 WEDGE" 1 " C - 6 4 WEDGE" 4 "DOS 5 . 1 " 11 "COPY/ALL" 9 "PRINTER TEST" 4 "DISK ADDR CHANGE" 4 "DIR" 6 "VIEW BAM" 4 "CHECK DISK" 14 "DISPLAY T&S" "PERFORMANCE TEST" 9 5 "SEQUENTIAL F I L E " 13 "RANDOM F I L E " 558 BLOCKS FREE. 2A PRG PRG PRG PRG PRG PRG PRG PRG PRG PRG PRG PRG PRG PRG PRG
The 0 refers to which drive was accessed. This is a holdover from the 4040 dual drive system. Next you see the diskette name 1541TEST/DEMO. In the event that the diskette name is less than 16 characters in length, blank spaces are appended to the end of the name. This forced spacing is known as padding. Following the name of the diskette is the disk ID ZX in this instance. These two characters are generally faut not always) the unique alphanumeric characters under which the diskette in question was formatted originally. The diskette name and ID are cosmetic in nature and appear in the directory for your reference purposes only. The 2A indicates the DOS version and format, 4040 in this instance again a holdover. Next we see the active file entries on the diskette itself. Each directory entry has three fields: 1. The number of blocks/sectors the given file occupies. 2. The file name. 3. The file type. Your demo diskette came with 15 active files on it. Moreover, they are all program files denoted by PRG. The last entry in the directory is the remaining number of available blocks/sectors left on the diskette for storage. It is the difference between 664 blocks available at the time of original formatting and the sum of the blocks of the active files (664 - 106 = 558). What you see on your screen is not necessarily how the directory is stored on your diskette, however. Let's begin our look at the directory with the Block Availability Map (BAM).
36
1541TEST/DEM0 TRACK 18 SECTOR O ..A BAM TRACK BAM TRACKS BAM TRACKS BAM TRACKS BAM TRACKS BAM TRACKS BAM TRACKS BAM TRACKS BAM TRACKS BAM TRACKS BAM TRACKS BAM TRACKS BAM TRACKS BAM TRACKS BAM TRACKS BAM TRACKS BAM TRACKS BAM TRACKS DISK NAME 1 2-3 4-5 6-7 8-9 10-11 12-13 14-15 16-17 18-19 20-21 22-23 24-25 26-27 28-29 30-31 32-33 34-35 ID
.W ?..
1541TEST /DEMO ZX 2A
BO: OO OO OO 0 0 OO OO OO 0 0
B8: co:
DO: D8:
C8:
00 OO 00 00 00 00 00 00 o o o o o o o o 0 0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00
E0: E8:
00 00 F 0 : 00 F 8 : 00
OO OO OO OO OO OO
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
As indicated above, the BAM does not take up all 256 bytes on this sector. There are several other things stored here as well. The table below identifies the various parts. Note that the sector dump above uses hexadecimal notation while the table below gives the decimal equivalents. Bytes 0/1 2 3 4-143 144-159 160-161 Contents 18/1 65 0 160 Purpose Pointer to first firs1 sector of directory entries ASCII charact character A indicating 1541/4040 format Unused Block Availabi Diskette name Shifted spaces
37
Diskette ID Shifted space DOS version and format type (2A) Shifted spaces Unused
In the BAM four bytes are used to describe the status of each track. As a result, the BAM takes up a total of 4 x 35 = 140 bytes ft>ytes 4-143 or $04-$8F). Let's examine the entry for track 14 to see what these four bytes mean. The entry for track 14 begins at byte 14 x 4 = 56 ($38). It looks like this:
. 38: 11 D7 5F l F OO 00 00 OO .W
* * * * * * * *
BAM TRACKS
14-15
The first byte for track 14 Qocation $38 = 56) indicates the number of blocks free on this track.
. 38: 11 D7 5F
* *
l F OO 00 00 00 .W
BAM TRACKS
14-15
In this case there are $11 or 17 (1 * 16 + 1) blocks free. When the DOS calculates the number of blocks free on a diskette, it sums this byte from each track's entry in the BAM. Let's do our own blocks free calculation to see how it is done. All we have to do is sum up the decimal values of every fourth byte starting with byte 4 like this:
ZONE
BYTE 4 8 12 16 20 24 28 32 36 40 44 48 52 56 60 64 68
TRACK 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
HEX VALUE $lF $lF $lF $lF $lF $lF $lF $lF $lF $lF $lF $lF $lF $11 $00 $00 $00
DECIMAL VALUE 21 21 21 21 21 21 21 21 21 21 21 21 21 17 0 0 0
38
72 76 80 84 88 92 96 100 104 108 112 116 120 124 128 132 136 140
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
$10 $13 $13 $13 $13 $13 $13 $12 $12 $12 $12 $12 $12 $11 $11 $11 $11 $11
Wait a minute! We calculated 574 blocks free but the directory shows 558. How do we explain this discrepancy? Easy. Remember that the DOS reserves track 18 for its own use. Therefore the blocks free on that particular track are not returned to us (574 16 = 558). Sixteen sectors on track 18 are still free, but available only to the DOS. Now that you have seen how to calculate the number of blocks free on a diskette, let's get back to our analysis of track 14. The BAM entry looked like this:
. 38: 11 D7 5F ** ** ** l F 00 00 OO OO .W. ** BAM TRACKS 14-15
The first byte was easy to interpret. The remaining three bytes are a bit trickier (no pun intended). They are a bit map showing the status of the sectors on a given track. Bit mapping is used to save space. If one byte were used for each of the 683 sectors, the BAM would take up three sectors (683 / 256). This would be inefficient. By using bit mapping, each byte describes the status of eight sectors. This way only three bytes are needed for each track. Let's examine the bit map for track 14 of our 1541 TEST/DEMO.
. 38: 11 D7 5F l F 00 00 00 00
* * * * * * * *
TRACKS
14-15
$39=57 *D7
11010111
76543210
OOOlllll
21111 *xx09876
111111
1 = FREE 0 = ALLOCATED
39
Sectors 0 to 7 are represented by the byte at location 57. Sectors 8 through 15 are stored in the byte at location 58. Finally, sectors 16 through 20 are depicted by the byte at location 59. When decoded, a bit that is high or a 1 indicates that a sector is not currently in use (free) and can be written to. A bit that is low or a 0 is currently in use (allocated) and will be overlooked by the DOS when writing subsequently takes place to the diskette. The third byte is always incomplete since a maximum of 21 sectors are written to any track. This particular byte is automatically adjusted by the DOS during initial formatting to indicate the proper number of sectors for this track. Three bytes are still used irregardless of the zone, however. If you count up the ls in the bit map for track 14, you will find that there are 17 free sectors on track 14. This agrees with the blocks free count for the track stored at byte location $38 (56) in the BAM, i.e., $11 or 17 decimal. To ensure that you understand how the bit mapping works, let's take a look at track 18. Since track 18 is used for storing the directory we would expect some allocation of sectors here. Byte 72 shows $10 or 16 sectors available here. They are bit mapped in bytes 73, 74, and 75 as follows:
. 48: 10 EC FF 07 OO OO OO OO ** ** ** ** $49=73 *EC *4A=74 *FF BAM TRACKS 18*4B=75 *07
lllOllOO
76543210
11111111
54321098
OOOOOlll
21111 x x x 09876
111111
1 = FREE 0 = ALLOCATED
Ifyou are still unsure of yourseLf, don't be too concerned. The DOS looks after the BAM. Let's move on and explore the actual directory entries themselves. Sectors 1 through 18 on track 18 are reserved specifically for them.
12 04 82 11 OO 48 4F 57 20 54 4F 20 55 53 45 AO AO AO AO AO AO 00 OO OO
40
18 20 28 30 38 40 48 50 58 60 68 70 78 80 88 90 98 AO A8 BO B8 CO C8 DO D8 EO E8 F0 F8
00 OO 20 4F OO OO 2D 45 OO OO 34 AO 00 OO 20 AO 00 00 59 AO 00 00 4E 54 OO 00 4B 48 00
OO 00 50 AO OO OO 32 AO OO OO 20 AO OO OO 35 AO OO OO 2F AO 00 00 54 AO OO OO 20 41 00
00 82 41 AO OO 82 30 AO OO 82 57 AO OO 82 2E AO OO 82 41 AO OO 82 45 AO OO 82 41 4E 00
OO 11 52 AO OO 11 20 AO 00 13 45 AO OO 13 31 AO 00 13 4C AO 00 13 52 AO OO 10 44 47 00
00 03 54 AO OO 09 57 AO 00 OO 44 AO OO Ol AO AO OO 03 4C AO OO 09 20 AO OO OO 44 45 OO
OO 48 20 OO OO 56 45 OO OO 46 47 OO 00 44 AO 00 00 43 AO OO OO 50 54 00 OO 44 52 OO OO
OD 4F 54 OO 05 49 44 OO 04 2D 45 OO Ol 4F AO OO 04 4F AO 00 OB 52 45 OO 09 49 20 OO 04
OO 57 57 00 00 43 47 OO OO 36 AO OO 00 53 AO 00 00 50 AO OO OO 49 53 00 00 53 43 00 OO
F I L E ENTRY
F I L E ENTRY
F I L E ENTRY
. . . . - DOS 5. 1
m m m
F I L E ENTRY
Y/ALL
COP . . .
F I L E ENTRY
F I L E ENTRY
F I L E ENTRY
The contents of any directory sector can be tabled as follows: Byte 0 1 2-31 32-33 34-63 64-65 66-95 96-97 98-127 128-129 130-159 0 0 0 0 Contents Purpose Track of the next directory block Sector of the next directory block File entry #1 in the directory block Unused File entry #2 in the directory block Unused File entry #3 in the directory block Unused File entry #4 in the directory block Unused File entry #5 in the directory block 41
160-161
0 0 0
Unused File entry #6 in the directory block Unused File entry #7 in the directory block Unused File entry #8 in the directory block
Eight file entries are recorded per sector. Let's examine the contents of a single directory file entry.
00: . 08: 10: . 18: 12 04 82
* * * *
11 OO 48 4F 57
HOW TO USE
20 54 4F 20 55 53 45 AO AO AO AO AO AO 00 00 OO 00 OO 00 OO 00 OO OD 00
Because this is the first entry in the directory, bytes 0 and 1 are significant. They point to track 18, sector 4 (converts to 18). This indicates that there are further directory entries. You will note that the sectors are not sequential in nature, i.e., sector 1 does not point to sector 2, etc. Remember that the diskette itselfis rotating at 300 rpm. Staggering the use of the sectors allows quicker access and fewer rotations of the drive mechanism and the media. Typically sectors are staggered in increments of 10. The directory track is staggered in increments of 3, however. The table below indicates the sequence in which a full directory containing 144 files is stored: SECTOR FILLING SEQUENCE FOR THE DIRECTORY 0 (BAM) 1, 4, 7, 10, 13, 16 2, 5, 8, 11, 14, 17 3, 6, 9, 12, 15, 18 When a diskette is initially formatted, sector 1 is set up with 8 null entries. As you store files on the diskette the directory grows. It soon becomes a long chain of directory sectors. The first two bytes in a sector point to the next directory sector in the chain (this is known as a forward pointer). But, what about the last sector in the chain? It has nothing to point to! In the last sector in the chain, there is no forward pointer; byte 0 contains a 0 ($00) and byte 1 contains a 255 ($FF) as indicated below. This indicates to the DOS that there are no more sectors in the directory.
. 00: 00 FF xx xx xx xx xx xx
One final note about chaining. Commodore uses only forward pointers. A sector does not show where it came from, only where it is going. This makes recovery of corrupted files much more difficult, but more about that later.
42
oo: 12 04 82 11 OO 48 4F 57
* *
HOW
TO USE
.
The first byte in the file entry is the file-type byte. In this instance we see an $82. This is interpreted by the DOS to mean that the file entry is a program. The following table outlines Commodores file types. HEX $00 $80 $81 $82 $83 $84 $00 $01 $02 $03 $04 $A0 $A1 $A2 $A3 $A4 $C0 $C1 $C2 $C3 $C4 ASCII FILE TYPE 0 128 129 130 131 132 0 1 2 3 4 160 161 162 163 164 192 193 194 195 196 Scratched Deleted Sequential Program User Relative Unclosed Unclosed Unclosed Unclosed Unclosed deleted sequential program user relative DIRECTORY SHOWS Does not appear DEL SEQ PRG USR REL Same as scratched *SEQ *PRG *USR Cannot occur DEL SEQ PRG USR Cannot occur DEL SEQ PRG USR REL < < < < <
Deleted @ replacement Sequential @ replacement Program @ replacement User @ replacement Relative @ replacement Locked Locked Locked Locked Locked deleted sequential program user relative
Note: It is possible to edit thefile-type byte and get very unusualfile types appearing in the directory (SR?< is one possibility). However, thesefile types have no practical use. Enough esoterica for now. Let's get back to our example: The next two bytes in the file entry are a pointer to where the first sector of that particular file is stored on the diskette.
43
. -
12 04 82 11 OO 48 4F 57
* * * *
HOW TO USE
20 54 4F 20 55 53 45 AO AO AO AO AO AO 00 OO OO
1 8 : OO OO OO 0 0 0 0 OO 0D OO . :
This file starts on track 17 ($11), sector 0 ($00). Next we have the file name. - OO: 12 04 82 11 00 48 4F 57
* * * *
* *
, HOW TO USE
* *
* *
20 54 4F 20 55 53 45 AO
* * * * * * * * * * * *
AO AO AO AO AO 00 00 00
* * * * * * * * * *
00 00 00 00 00 00 OD 00
In this case our file is named "HOW TO USE". Note that file names are padded out to 16 characters with shifted spaces ($AO)just like the diskette name. The shifted spaces do not show as part of the file name, however, when the directory is displayed.
. . . 00: 08: lO: 18: 12 04 82 11 00 48 4F 57 20 54 4F 20 55 53 45 AO AO AO AO AO AO 00 00 OO
* * * * * *
HOW TO USE
00 OO 00 00 00 00 OD 00
The next three bytes are unused except for relative file entries. For a relative file bytes $15 (21) and $16 (22) point to the first set of side sectors. Byte $17 (23) gives the record size with which the relative file was created. This special file type will be examined in detail later. The next four bytes are always unused and therefore null ($00).
. . . 00: 08: io: 18: 12 20 AO 00
* *
04 54 AO 00
* *
82 4F AO 00
* *
11 20 AO 00
* *
00 55 AO 00
48 53 00 OO
4F 45 00 OD
57 AO 00 00
HOW TO USE
The following two bytes are reserved for use by the DOS during the save and replace operation (@ replacement). Their function can only be viewed by interrupting the drive during a SAVE "@0:file name",8 routine. This is not recommended for obvious reasons. (During an @ replacement the file-type byte is ORed with $20 first. A new copy of the file is then written to the disk. Bytes 28 ($lC) and 29 ($lD) contain the track and sector pointer to the start of the new replacement file. At the end of the @ operation the sectors that held the old file are marked as free in the BAM. The new track and sector
44
pointer is then moved from bytes 28 and 29 to bytes 3 ($03) and 4 ($04) respectively and bytes 28 and 29 are zeroed again. The proper file type is then restored at byte 2. See Chapter 9 about the bug in the @ replacement command.)
. . . . oo: 08: lO: 18: 12 20 AO 00 04 54 AO 00 8 2 11 4F 20 AO AO 00 00 00 55 AO 00
* *
48 53 00 OO
* *
4F 45 00 OD
57 AO OO OO
HOW TO USE
Thefinaltwo bytes in a file entry are the number of blocks it occupies on the diskette. It is the sum of the leftmost byte (lo-byte) + the rightmost byte (hi-byte) * 256.
12 20 AO 00 04 54 AO OO 82 4F AO OO 11 20 AO 00 00 55 AO OO 48 53 00 00 4F 45 OO OD
* *
oo:
57 AO OO OO
* *
LO HI
In our example, the file is (13 + 0 * 256) = 13 blocks long. To be sure you understand the file entries work let's break out the first sector of the test/demo directory to show each file entry. Remember that bytes 0 and 1 of each entry are unused with the exception of the first entry. Here they represent a forward track and sector chain and have nothing to do with that file in particular.
1541TEST/DEMO TRACK 18 SECTOR O l
File type = $82 = PRG Starts on 17/1 ($ll/$00) Name: HOW TO USE File length: 13 BLOCKS
File type = $82 = PRG Starts on 17/3 ($ll/$03) Name: HOW PART TWO File length: 5 BLOCKS
45
DIRECTORY ENTRY 3
00 2D 45 OO
OO 32 AO OO
82 30 AO OO
11 20 AO OO
09 57 AO OO
56 45 00 OO
49 44 OO 04
43 VIC Filetype = $ 8 2 = PRG 47 -20 WEDG Starts on 17/9 ($11/09) OO E . . . Name: VIC-20 WEDGE OO File length: 4 BLOCKS
DIRECTORY ENTRY 4 . . . . 60: 68: 70: 78: OO 34 AO OO OO 20 AO OO 82 57 AO OO 13 45 AO OO OO 44 AO OO 46 47 OO OO 2D 45 00 01 36 C - 6 File type = $82 = PRG AO 4 WEDGE Starts on 19/0 ($13/$00) 00 Name C-64 WEDGE
OO
5
DIRECTORY ENTRY
00 20 AO OO
OO 35 AO OO
82 2E AO OO
13 31 AO 00
Ol AO AO 00
44 AO OO OO
4F AO OO 04
53 AO OO OO
5. 1
DOS Filetype = $82 = PRG Starts on 19/1 ($13/$01) Name: DOS 5.1 Filelength:4BLOCKS
. B8: 00 OO OO 00 00 00 OB OO
DIRECTORY ENTRY 7 . . . . CO: C8: DO: D8: OO 4E 54 00 OO 54 AO 00 82 45 AO 00 13 52 AO 00 09 20 AO OO 50 54 00 00 52 45 00 09
File type = $82 = PRG Starts on 19/3 ($13/03) Name: COPY/ALL File length: 11 BLOCKS
49 PRI File type = $82 = PRG 53 NTER TES Starts on 19/9 ($13/09) 00 T . . . Name: PRINTER TEST OO File length: 9 BLOCKS
File type = $82 = PRG Starts on 16/0 ($10/00) Name: DISK ADDR CHANGE File length: 4 BLOCKS
46
We will end our tour of the directory by displaying the next sector (track 18, sector 4) which happens to end the directory chain ($00, $FF in bytes 0 and 1, respectively). Notice that only seven directory entries are present in this block. The last directory entry is a null entry. It will be converted into a valid entry when the directory is expanded.
0 0 : oo FF 8 2 10 0 1 4 4 4 9 5 2 0 8 : AO AO AO AO AO AO AO AO
10:
1 8 : 0 0 0 0 0 0 OO OO 0 0 0 4 0 0
OO OO 8 2
AO AO AO AO AO OO OO OO
10 03 5 6 4 9 4 5 V I E File type = $82 = PRG Starts on 16/3 ($10/03) 5 7 2 0 4 2 41 4D AO AO AO W BAM Name: VIEW BAM AO AO AO AO AO OO OO OO File length: 6 BLOCKS OO OO 0 0 OO OO OO 0 6 OO
m m
4 0 : OO OO 8 2 10 4 8 : 4 3 4B 2 0 44 5 0 : AO AO AO AO 5 8 : OO 00 00 00
07 43 48 45 CHE File type = $82 = PRG 4 9 5 3 4B AO CK DISK Starts on 16/7 ($10/07) AO OO OO 00 . . Name: CHECK DISK OO OO 0 4 OO File length: 4 BLOCKS 49 53 D I S File type = $82 = PRG 2 6 5 3 PLAY TScS Starts on 16/15 ($10/$0F) . . Name: DISPLAY T&S OO OO File length: 14 BLOCKS OE OO 52 PER File type = $82 = PRG FORMANCE 45 Starts on 20/2 ($14/$02) OO T E S T . . . Name: PERFORMANCE TEST 00 File length: 9 BLOCKS File type = $82 = PRG Starts on 20/7 ($14/$07) Name: SEQUENTIAL FILE File length: 5 BLOCKS File type = $82 = PRG Starts on 15/1 ($0F/$01) Name: RANDOM FILE File length: 13 BLOCKS NULL ENTRY
6 0 : OO OO 8 2 l O OF 44 6 8 : 5 0 4C 41 5 9 2 0 5 4 7 o : AO AO AO AO AO OO 7 8 : 0 0 OO OO OO OO OO 8 0 : OO OO 8 2 14 8 8 : 4 6 4F 5 2 4D 90: 20 54 45 53 9 8 : 0 0 OO OO 0 0
AO: AS: BO: B8: co: C8: DO: D8: E0: E8: F0: F8:
02 41 54 00
50 45 4E 4 3 OO OO 00 09
OO 55 46 00
OO 45 49 OO
8 2 14 4E 5 4 4C 4 5 OO OO
07 50 4 9 41 AO OO 00 00
45 52 SEQ 4C 2 0 UENTIAL OO OO F I L E 0 5 OO
RAN 0 0 OO 8 2 OF O l 5 2 41 4E 4 4 4F 4D 2 0 4 6 4 9 4C 4 5 DOM F I L E AO AO AO AO AO OO OO OO . .. OO 0 0 OO OO OO OO OD OO OO OO OO OO OO OO OO OO OO OO OO 0 0 OO OO OO OO OO OO OO OO OO OO 0 0 00
OO 0 0 0 0 0 0 0 0 0 0 OO 0 0
You will find four of the utilities listed in Appendix C particularly helpful in furthering your understanding of the organization of a diskette. The first program is DISPLAY TRACK & SECTOR. The hex dumps in this section were generated using this utility. A hex dump can be sent either to the screen or printer. When sent to the screen only hatf a page of the specified track and sector is displayed at one time to prevent scrolling. Bytes 0 - 127 ($00 - $7F) are displayed first followed by bytes 128 - 255 ($80 - $FF). Use this program for your own experimentation. The second program is DISPLAY A BLOCK AVAILABILITY MAP. It portrays the BAM in a two-dimensional representation. The diskette name, ID, DOS version, and blocks free are also displayed. The third program is VIRTUAL DIRECTORY. It displays a directory in its entirety including scratched files. Output can be directed to a printer by changing the OPEN 4,3 statement in line 440 to OPEN 4,4. The last program, DISPLAY A CHAIN, traces a file chain. The chain of sectors may be viewed on the screen or sent to the printer. The programming techniques that are used in these sample programs will be partially explained in later sections. Now that we've seen how the directory is kept, let's look at how the different types of files are actually stored on a diskette. We'll start by looking at a program file.
Byte 0 1 2 3 4-255
Purpose Track of the next block in this file Sector of the next block in this file Lo-byte of the load address Hi-byte of the load address The first 252 bytes of the program
The first pair of bytes are the pointer to the track and sector of the next block in the file. Technically, this is known as a "forward pointer." It points ahead to the next sector in the file. All Commodore files use this type of pointer. The second pair of bytes is the "load address" of the file in lo-byte/hi-byte form. They indicate where the program is to be loaded into memory. A BASIC program that was saved from a C64 will have a $01 and a $08 in these two locations. This indicates that the program is to be loaded into memory starting at memory location $0801 (remember it is in lo-byte/hi-byte form). In decimal notation this is memory location 2049 the start of BASIC on a C64.
48
Have you ever wondered about the significance of the ",1" in the command LOAD "name",8,l? It determines whether or not a program is "relocated" when it is loaded into memory. If you do not specify the ",1", the C64 will ignore the load address at the start of the file and load the program starting at memory location $0801 (2049). When the ",1" is present, the C64 (or VIC-20) will pay attention to the load address and load the program into memory starting at the location specified by bytes $02 and $03. The remaining sectors, except the last one, look like this:
TRACK UNK
SECTOR UNK
Byte 0 1 2-255
Purpose Track of the next block in this file Sector of the next block in this file The next 254 bytes of the program
The last block in a program file is special because: 1. It It is last only sector. 2. is the usually partially full. To signal the DOS that this is the last block, the first byte is set to $00. The first byte is normally the track link. Since there is no track 0, the DOS knows that this is the last sector in the file. The second byte indicates the position of the last byte that is part of the program file. Any bytes beyond this position are garbage. Diagrammatically, the last sector in a program file looks like this:
NULL $00
LAST BYTE
6ARBA6E
Byte 0 1
Purpose Null byte to indicate that this is the last sector Number of bytes to read from this sector (N)
2-N The last (N-2) bytes of the program (N + l)-255 Garbage Let's examine the program file "DIR" on your 1541TEST/DEMO disk. DIR appears in the directory on track 18, sector 04. The directory entry looks like this:
49
SECTOR 04 44 AO 00 00 49 AO 00 04 52 AO 00 OO .....DIR
01 AO AO 00
From the entry we see that "DIR" starts at track 16 ($10), sector 01 ($01) and that the file is four blocks long (4 + 0 * 256).
. . . . 00: 08: 10: 18: 00 FF 82 10 O l 44 49 52 ** ** .....DIR
AO AO AO AO AO AO AO AO AO AO AO AO AO 00 00 00 00 00 OO 00 OO OO 04 OO
* * * *
OD 2C 99 30 00 22 00 42 23 OO 2C 04 04 B1 C6 46 22 AA 35 22 29 3B 5A 3A 20 DE
: . 10000 . . . . . . 1q 8,0,"*0" .< #1 ,A*,B*.J #l,A *,B*.X.( ..#l,A*, B$...2.C . 0...< . . A*.."" . C..(A* ) F. . B*.."" . C . C . . (B *>.256..
m
1. II ..1
P I . . II . II .
;...z..#
S 1
50
. . . .
8B 33 00 2C B3 20
20 34 00 42 B1 99
42 29 05 24 C7 42
24 20 6E 3A 28 24
B3 A7 00 8B 33 3B
B1 20 A1 20 34 3A
C7 39 23 42 29 89
28 - B* ( 30 34> - 90 31 #1 24 , B * : . B * A7 - . . ( 3 4 ) . 31 .B*;:.1
Not very recognizable is it? Remember this is C64 internal BASIC not a BASIC listing. Bytes 0 and 1 are of interest. They are the track and sector link that point to the next block in the program file. In this case, they point to track 16 ($10), sector 11 ($OB). Since this is the first data block of the file, bytes 2 and 3 are also important. They are the load address. We can see that the load address is $0401 or 1025 decimal. This file was written on a PET. (The start of BASIC memory on the C64 is at $0801. The VIC-20 begins at $1001, $1201, or $0401 depending ont he amount of external memory.) DIR will require a straight relocating load, i.e., LOAD "DIR",8. Ifyou used a LOAD "DIR", 8,1 command, the program would be loaded into the screen RAM ofthe C64. NOTE: If you load this program properly, you urill NOT be able to get it to VERIFY correctly. The reason is that the internal BASIC links were changed when the program was relocated.
. OO: 10 OB 01 04 OD 04 04 00
* * * * * * * *
Let's follow the forward chain to track 16, sector 11 and take a look at the start of the second block in our file.
TRACK 16 00: 08: 10: SECTOR 11
10 02 31 30 00 l C 05 78 . . 1 0 OO A1 23 31 2C 42 24 3A . . # l , B * : 8B 20 42 24 B2 C7 28 33 . B * . . < 3
Nothing much of interest here. Let's chain to track 16 ($10), sector 02 ($02) and take a look at the start of the next block.
SECTOR 02
51
Now we're cooking. This is the last sector of the file. How can we tell? The track of the next block in the file is 0 ($00). But what about the sector link? It's a misnomer. The sector link in the last block is actually a byte count. It informs the DOS that only bytes 2 through 104 ($68) are important in this example. Recall that an end of file in BASIC is designated by three zeros in a row. An End-or-Identify (EOI) signal will be sent once byte 104 has been transferred across the serial bus. When the C64 receives this EOI signal, the status variable, ST, will be set to a value of 64. (Any further attempt to read a byte will cause the drive to time out.) Here's the tail end of our program. The three null bytes, ($00), at $66/7/8 are the last three bytes in our program file.
00: 08: lO: 18: 00 44 2D B2 24 41 20 46 51 07 22 30 20 OO 68 22 07 22 B2 24 34 28 22 50 53 30 31 8B 20 3C 2E 22 B2 30 8B 20 28 22 00 30 20 A7 28 22 3E 22 30 20 A7 8B 20 5E 31 41 20 8B 20 22 3E 30 41 20 20 A7 07 30 24 31 20 BO 20 22 OO 24 80 41 20 F7 30 B2 30 41 20 BO 20 3E B2 00 24 35 2A 00 22 00 24 41 20 A7 07 22 52 B2 30 89 OO ... A*." D" . 10. - . < ( . A* ."." . A %.">" . A$.">" . 4000.>. F(. A*." Q" . . . R . P ( . A*. "S" . 50
20:
00.
xx xx xx xx xx xx xx
lOlOO..
..*.
What about the rest of the block? Ignore it. It is garbage. The DOS does not zero out a buffer before it begins filling it with new information sent from the computer. As a result, the last block in a file, which is almost neverfiUedwith new information, is padded with whatever happened to be left in the buffer from a previous read or write operation. There are two exceptions to the rule, namely, the directory and relative files. A partial directory block is always padded with nulls ($00). Moreover, it always appears as a full block. Bytes 0 and 1 of the last directory block will contain a $00 and a $FF, respectively. Relative file structure will be explained shortly.
Byte 0 1 2-255
Purpose Track of the next block in this file Sector of the next block in this file 254 bytes of data
The last block in a sequential file is special for two reasons: 1. It last only sector. 2. It is is the usually partially full. To signal the DOS that this is the last block, the first byte is set to $00. The first byte is normally the track link. Since there is no track 0, the DOS knows that this is the last sector in the file. The second byte indicates the position of the last byte in the file. Any bytes beyond this position are garbage. Diagrammatically, the last sector in the file looks like this:
THE FINAL DATA BYTES IN YOUR SEQUENTIAL FILE 6ARBA6E
NULL $00
LAST BYTE
Purpose Null byte to indicate this is the last sector Position of the last byte in the file (N) The last N - 2 bytes of the sequential file Garbage
53
No sequential files appear on the 1541TEST/DEMO. (The file named SEQUENTIAL FILE is a program file demonstrating the sequential access method.) The C-64 DISK BONUS PACK does come with one sequential file on it. The file named " DIRECTORY " appears as a SEQ when displaying the directory. " DIRECTORY " can be found at track 18, sector 01 on the C-64 DISK BONUS PACK. Let's take a peek at the directory entry for this file:
TRACK 18 . . . . 20: 28: 30: 38: OO 44 59 OO 00 49 20 OO 81 52 20 OO 11 45 20 00 SECTOR 01 20 54 OO 00 20 4F 00 02 20 52 DIRECTOR OO Y OO
01 43 AO OO
DIRECTORY
20: 28: 30: 38:
oo 00 81 11 O l 20 20 20 ** ** ** 44 49 52 45 43 54 4F 52 DIRECTOR 59 20 20 20 AO 00 OO OO Y OO OO OO 00 OO OO 02 OO **
A sequential file is designated by an $81 in the directory. The first block of this file is stored on track 17 ($11), sector 1 ($01). We also see that " DIRECTORY " is two blocks long (2 + 0 * 256). Let's take a look at the first half of the starting data block.
TRACK 17 . . . . . . . . . . . . . . . . OO: 11 0 8 : 41 10: 54 18: 32 2 0 : 42 2 8 : 4D 3 0 : 4C 3 8 : OD 4 0 : 44 4 8 : 43 5 0 : OD 5 8 : 44 6 0 : 52 6 8 : 4F 7 0 : 54 7 8 : 4C OB 52 20 41 41 4F 45 42 20 41 43 49 20 4C OD 4C 43 54 20 OD 43 52 OD 49 42 4C 48 53 42 4F 43 36 36 45 20 31 4B 54 41 54 59 45 41 4B 4F 52 4F 34 SECTOR O l 20 20 34 34 50 54 52 20 45 44 47 43 54 54 59 44 53 4B 20 31 OD 41 4F 41 53 41 45 48 OD 45 2D 45 54 49 20 20 41 42 57 4E OD 52 20 41 43 53 41 4D - . C 6 4 ST ARTER KI T 64 2A.1541 BACKUP.A MORT TAB LE.ARROW - B I T S AN D BYTES. CALENDAR .CHANGE DISK.CHA R BOOT.C OLOR TES T.COPY-A LL64.DEM
34 52 36 35 55 20 52 53 54 4E 4E OD 4F 20 50 OD
Bytes 0 and 1 are the track and sector link (forward pointer). They inform us that the next data block can be found at track 17, sector 11. The remaining 254 bytes are data. The sequential data that appear here are in fact the disk name (C64 STARTER KIT), the cosmetic disk ID (64), and the file names found on the C-64 DISK BONUS PACK. It is interesting to note that a carriage return character ($0D) was used as a delimiter to separate record entries. Next we see:
TRACK 17 00 08 lO 18 20 28 30 38 40 48 50 58 60 68 70 78 80 88 90 98 AO A8 BO B8 CO C8 DO D8 E0 E8 FO F8 OO 45 20 OD 20 55 41 20 52 20 53 52 4F 49 49 OD 4E 54 AO 00 00 4F AO 00 OO 52 AO 00 OO 54 AO 00 86 45 2D 53 42 4E 50 2D 45 2D 4F 41 55 52 54 53 36 53 AO OO 00 50 AO 00 OO 54 AO 00 OO 47 AO 00 2D OD 20 4F 4F 44 OD 20 OD 20 55 59 4E 45 45 55 34 50 AO OO 82 59 AO 00 82 20 AO 00 82 41 AO 00 20 53 41 55 4D 20 53 47 53 50 4E 47 44 4E 20 50 2E 52 AO 00 07 20 AO 00 lD 54 AO OO 05 47 AO 00 SECTOR 41 55 49 44 OD 20 55 4E 55 4E 20 4E 2D 53 4F 52 31 54 OO 00 53 41 OO 00 41 42 OO 00 4D AO OO 00 4E 4E 45 20 53 43 4E 46 4E 47 2D OD 20 50 4F 4D OD 45 OO 05 4E 54 OO 33 4D 4C 00 27 4F AO OO 2D 11 4B 44 4E 2D 4F 4C 44 49 44 OD 20 53 53 52 54 4F 59 53 OO OO 4F 48 OO OO 4F 45 OO 00 52 AO OO 00 . . - YANK EE.SOUND - ALIEN .SOUND BOMB.SO UND - CL AP.SOUND - GUNFI RE.SOUND - PONG. SOUND RAYGUN.S OUND - S IREN.SPR ITE BOOT .SUPERMO N64.Vl.Y TSPRITES SNO OPY MATH 3. AMO RT TABLE
59 4F 4C 4E 42 2D 4F 55 4F 4F 44 55 20 OD 42 45 56 49 AO OO OO 4D AO OO 00 41 AO 00 02 45 AO OO
J
MOR TGAGE... -.
We can see from the above data block that this is the last sector in the chain. Byte 0 contains a zero indicating no forward track. Byte 1 then is a byte count ($86=134). Byte 134 is the last byte in our data f!le. Recall that the status variable (ST) will be set to 64 on the C64 side after byte 134 has been read.
80: 4E 36 34 2E 56 31 OD x>: N 6 4 - V 1 -
55
The remainder of the block has been padded ($87-$FF). The padding is clearly recognizable this time around. It has no rhyme or reason but it is still interesting to say the least. A portion of the C-64 DISK BONUS PACK directory itself was used to pad the remainder of the data block in question.
80 88 90 98 AO A8 BO B8 CO C8 DO D8 E0 E8 F0 F8 xx 54 AO 00 OO 4F AO 00 OO 52 AO 00 00 54 AO OO xx 53 AO 00 OO 50 AO OO 00 54 AO OO OO 47 AO 00 xx 50 AO 00 82 59 AO 00 82 20 AO 00 82 41 AO OO xx 52 AO OO 07 20 AO 00 lD 54 AO 00 05 47 AO 00 xx 49 AO OO OO 4D AO OO OO 41 AO 00 02 45 AO OO xx 54 OO OO 53 41 00 00 41 42 OO 00 4D AO 00 00 xx 45 OO 05 4E 54 OO 33 4D 4C OO 27 4F AO OO 2D 59 53 00 N64.Vl.Y TSPRITES
OO
.-
OO
OO
00
56
Remember that sequential data blocks have the following format: Byte 0 1 2-255 Purpose Track of the next block in this file Sector of the next block in this file 254 bytes of data
Diagrammatically, each block (side sector) in the side sector file looks like this:
TRACK UNK SECTOR UNK SIDE SECTOR NUMSER RECORD SIZE TRACK/SECTOR LINKS FOR 6 SIDE SECTORS TRACK/SECTOR LINKS FOR 120 DATA BLOCKS
Byte 0 1 2 3 4-15
Purpose Track of the next side sector Sector of the next side sector Side sector number Record length Track and sector list of the side sector file 4-5 Track and sector of side sector #0 6-7 Track and sector of side sector #1 8-9 Track and sector of side sector #2 10-11 Track and sector of side sector #3 12-13 Track and sector of side sector #4 14-15 Track and sector of side sector #5 Track and sector list of 120 data blocks 16-17 Track and sector of data block #1 18-19 Track and sector of data block #2 20-21 Track and sector of data block #3
16-256
254-255 Track and sector of data block #120 To help you make some sense out ofthis, let's begin with the directory entry for a relative file. Here's the start of the directory of a diskette that has a relative file stored on it.
57
TRACK 18 00: 08: 10: 18: 20: 28: 30: 38: 40: 48: 50: 58: 60: 68: 70: 78: oo 31 45 00 OO 32 45 00 OO 33 45 00 OO 20 AO 00 FF 4D AO OO OO 4D AO 00 00 4D AO OO OO 46 AO OO 81 41 AO OO 81 41 AO 00 81 41 AO OO 84 49 AO OO 11 47 AO 00 11 47 AO 00 11 47 AO 00 11 4C AO 00
OO 20 AO OO Ol 20 AO OO 02 20 AO 00 03 45 AO OO
"MAG FILE" will serve as our demo throughout this section. Let's examine its directory entry in detail from track 18, sector 1.
. . . 60: 68: 70: 78: 00 00 84 11 03 4D 41 47 MAG FILE.- F i l e t y p e a n d T /g l i n k
20 46 49 4C 45 AO AO AO AO AO AO AO AO 11 OD 96 00 00 00 OO 00 00 B4 01
** ** **
From the directory entry we can see that "MAG FILE" is a relative file. A relative file is indicated by an $84 as the file type. The track and sector pointers in the directory reveal that "MAG FILE" starts at track 17 ($11), sector 03 ($03). This is the sequential data file portion of the relative file. It is the beginning of our data.
70: AO AO AO AO AO 11 OD 96
** ** **
Side sector information follows the file name. The first side sector begins at track 17 ($11), sector 13 ($OD). In addition, we see our record length ($96=150). Each record in our sequential data file is 150 bytes long. This is fixed throughout the entire data file.
78:
OO OO 00 00 00 00 B4 O l
** **
58
Our sample relative file consumes a total of 436 blocks on the diskette (180 + 1 * 256). (There is still room for expansion.) We can determine the number of side sectors by simple divison. A side sector stores track and sector pointers for 120 data blocks of our sequential file. To determine the number of side sectors, simply divide the total number of blocks that appear in the directory entry by 120 and round up to the next higher integer: 436 / 120 = 3.6 ^ 4 Four side sectors are needed to keep track of this much data. To figure out how many records currently exist requires a little more arithmetic. First we have to subtract the number of side sectors from the total number of blocks. 436 - 4 = 432 Now we can determine the total number of data bytes currently in use by our sequential file. 432 * 254 = 109728 Why 254 as a multiplier? Remember that the first two bytes of any data block are forward track and sector pointers (256 - 2 = 254). We finish our set of calculations by dividing this total by the fixed record length. 109728 / 150 = 731.52 A total of 731 records exist at the current time in "MAG FILE." Let's examine the first side sector.
TRACK 17 oo 08 10 18 20 28 30 38 40 48 50 58 60 68 70 78 80 88 OC 06 11 11 11 11 11 10 10 10 10 lO OF OF OF OF OF OE 13 lO 03 05 07 09 OC 08 04 01 05 09 05 01 14 10 OC 11 OO 13 11 11 11 11 lO 10 10 10 10 10 OF OF OF OF OF OE 96 OF OE 10 12 14 OO 12 OE OB OF 13 OF OB 08 04 09 05 SECTOR 13 OD OO 04 06 08 OA OA 06 02 03 07 07 03 OO 12 OE 13 OF OC OO 11 11 11 11 10 10 10 lO 10 OF OF OF OF OF OE OE 13 00 OF 11 13 OB 14 lO OC OD 11 11 OD OA 06 02 07 03 SIDE SECTOR #0
11 OO 11 11 11 11 10 10 10 10 10 OF OF OF OF OF OF OE
. . . .
. . . . .. ... . . .
Forward pointer, SS #, size, and 6pairs of side sector pointers 1 2 0 pairs of data block pointers
59
90: 98: AO: A8: BO: B8: co: C8: DO: D8: EO: E8: FO: F8:
OE OE OE OE OD OD OD OD OD OD OC OC OC OC
OD OA 06 02 07 03 00 12 OE 13 OF OB 08 04
OE OE OE OE OD OD OD OD OD OC OC OC OC OC
01 14 10 OC 11 OD OA 06 02 07 03 OO 12 OE
OE OE OE OE OD OD OD OD OD OC OC OC OC OC
OB 08 04 09 05 Ol 14 10 OC 11 OD OA 06 02
OE OE OE OE OD OD OD OD OD OC OC OC OC OC
00 12 OE 13 OF OB 08 04 09 05 01 14 10 OC
Bytes 0 and 1 show us that the next side sector resides at track 12 ($OC), sector 19 ($13). Byte 2 informs us that this is side sector 0. A maximum of 6 side sectors are used by any one relative file. This is determined solely by the physical storage capacity of the diskette (664 blocks free after formatting divided by 120 track and sector pointers in a side sector equals 5.53 side sectors). Side sectors are numbered from 0 to 5. Byte 3 shows us the record size again (150 bytes). Bytes 5-15 are the track and sector locations of the six possible side sectors. They can be tabled as follows: BYTE 4- 5 6- 7 8- 9 10-11 12-13 14-15 SIDE SECTOR 0 1 2 3 4 5 TRACK - SECTOR 17 ($11) - 13 ($OD) 12 ($OC) - 19 ($13) 6 ($06) - 16 ($10) 19 ($13) - 15 ($OF) 0($00)- 0($00) 0($00)- 0($00)
We can see from the table above that side sectors 4 and 5 have not yet been allocated. Once our data file expands to encompass more than 480 and 600 sectors, respectively, they will be allocated, provided there is room on the diskette. The remaining 240 bytes are track and sector pointers to the first 120 blocks in the sequential file. From bytes 16 and 17 of side sector 0 we see that our data begins at track 17 ($11), sector 03 ($03). (This is the track and sector recorded in the directory itsetf.) Track 17, sector 03 chains to track 17 ($11), sector 14 ($0E) which chains to track 17 ($11), sector 4 ($04) and so on.
60
TRACK 17 - SECTOR 13
. . . . . . . . . . . . . . . . . . . . . . . . . . . io: 18 20 28 30 38 40 48 50 58 60 68 70 78 80 88 90 98 AO A8 BO B8 CO C8 DO D8 E0 E8 FO F8
SIDE SECTOR # 0
11 03 11 OE 11 04 11 OF
* * * *
11 11 11 11 lO lO 10 10 10 OF OF OF OF OF OE OE OE OE OE OD OD OD OD OD OD OC OC OC OC
05 07 09 OC 08 04 01 05 09 05 01 14 lO OC 11 OD OA 06 02 07 03 OO 12 OE 13 OF OB 08 04
11 11 11 lO 10 10 10 lO 10 OF OF OF OF OF OE OE OE OE OE OD OD OD OD OD OC OC OC OC OC
lO 12 14 00 12 OE OB OF 13 OF OB 08 04 09 05 01 14 10 OC 11 OD OA 06 02 07 03 OO 12 OE
11 11 11 10 lO lO 10 lO OF OF OF OF OF OF OE OE OE OE OE OD OD OD OD OD OC OC OC OC OC
06 08 OA OA 06 02 03 07 07 03 00 12 OE 13 OF OB 08 04 09 05 01 14 10 OC 11 OD OA 06 02
11 11 11 10 lO 10 lO lO OF OF OF OF OF OE OE OE OE OE OE OD OD OD OD OD OC OC OC OC OC
11 13 OB 14 10 OC OD 11 11 OD OA 06 02 07 03 OO 12 OE 13 OF OB 08 04 09 05 01 14 10 OC
SIDE SECTOR #1
61
OA OA 09 09 09 09 09 08 08 08 08 08 08 07 07 07 07 07 06 06 06
06 02 07 03 12 OE 13 OF OB 08 04 09 05 01 14 10 OC 11 OD OA
00
OA OA 09 09 09 09 09 08 08 08 08 08 07 07 07 07 07 07 06 06 06
10 OA 04 OC OA 09 11 09 05 OD 09 01 OA 09 14 06 09 10 02 09 OC 07 08 11 03 08 OD 0 0 08 OA 12 08 06 OE 08 02 13 07 07 OF 07 03 OB 07 00 08 07 12 04 07 OE 09 06 13 05 06 OF 01 06 OB 14 06 08
OA 09 09 09 09 09 09 08 08 08 08 08 07 07 07 07 07 06 06 06 06
OE 13 OF OB 08 04 09 05 01 14 OC OD OA 06 02 07 03 OO 12
10 11
62
02 02 01 01 01 Ol Ol 13 13
10 OC 11 OD OA 06 02 OO 02
02 02 01 01 01 Ol 01 13 13
04 09 05 Ol 14 lO OC OB OD
02 01 Ol 01 01 Ol 01 13 13
OE 13 OF OB 08 04 09 01 03
02 01 01 01 01 01 13 13 13
02 07 03 00 12 OE OA OC OE
SIDE SECTOR # 3
63
Hold it right there please. Bytes 0 and 1 should look familiar by now. Still thinking? (Hint: End of chain and a byte count.) . 00: OO 9 F 0 3 9 6 11 OD OC 1 3
* * * *
Byte 1 of side sector 3 shows a byte count of 159 ($9F). Recall that bytes 16-255 in a side sector are a list of track and sector pointers to 120 data blocks. As a result, bytes 158 and 159 must be interpreted together. They point to the last block in our sequential data file in this instance. The last block is stored on track 23 ($17), sector 12 ($OC). Notice too, that the remainder of the side sector is padded with nulls. The remaining 96 bytes are in limbo until our relative file is expanded. Bytes 160 and 161 will then point to the next track and sector of data and so on. When side sector 3 is full, a new side sector will be created. Bytes 0 and 1 of side sector 3 will then point to side sector 4. Bytes 12 and 13 in side sectors 0, 1, and 2 will also be updated to reflect the creation of side sector 4. Now let's take a brief glance at the sequential file itself.
TRACK 17 oo 08 10 18 20 28 30 38 40 48 50 58 60 68 70 78 80 88 90 98 AO A8 BO B8 C8 DO 11 4C 20 OD 52 4E OE 45 36 C3 OD 45 OD DO 4D 4D 4D OD OD 4F CD OD 41 45 41 20 D4 4D 41 C9 47 4E SECTOR 03 20 30 54 55 41 53 OD ..MAG F I LE. 709. 6..ITLE ..OMPUTE R..AGAZI NE..SSUE ..AGE..0 OD OD OO MMENT... 46 39 4C 54 5A 55 C3 49 OD 45 45 49 45 4F
OO OO OO OO
OO OO OO OO 00 OO OO OO OO OO OO 00
00 00
00 OO 00 OO OO 00
OO OO
OO OO OO OO
OO OO
00 20 2E 2E 2E 2E 2E oo
CO
OD OD OD 2E OD 2E OD OD 2E OD 2E OD OD 2E OD 2E OD OO OO OO 00 00 00 OO OO 00 00 00 00
31
OO 00 00 00 OO OO OO 00 OO OO 00 OO 00 OO OO 00 00 00 OO OO OO OO 00 00 00 oo 00 00 00 OO 00 00 0 0 3 5 30 20 OD 2E OD 2E OD 2E OD 2E OD 2E OD 2E OD 2E OD
2E OD 2E OD 00 00
47 37 49 50 47 53 45 54 00 00 OO 00 OO 00
OO OO OO OO OO OO 00 OO OO OO 00 OO OO 00 00
150
...
OO OO OO OO
64
. . . . -
OO oo 00 oo oo
00 oo 00 oo oo
00 oo oo oo oo
0 0 OO OO OO 0 0
00 00 00 OO 00
oo oo oo oo oo OO OO 0 0 o o o o oo oo oo oo oo
The block reveals a typical sequential file. Bytes 0 and 1 are the chain. The first data block links to track 17 ($11), sector 14 ($OE). The next 150 bytes (2 - 151) constitute our first record. Note that the unused bytes within a record are written as nulls ($00) by the DOS so the record is always a fixed length. The content of individual records will vary enormously. This is program dependent so the data block in question contains whatever data was specified by the program used. This particular record is from a free form data base. It was reserved to for management information by the main program and contains the following data: 1. 2. 3. 4. The name of our relative file ("MAG FILE"). The number of active records (709). The number of fields in use (6). The field titles (TITLE, COMPUTER, MAGAZINE, ISSUE, PAGE, COMMENT).
In the sequential data file portion of a relative file, the record length (record size) is constant. In this case, the records are all 150 bytes long. Record number 2 begins at byte 152 ($98) and will extend on into the next data block. Two reads would be required to fetch the entire contents ofthis record. The first 104 bytes ofthe record will be found here, but the remaining 46 are in the next block of the file. Here they are. TRACK 17 - SECTOR 14
oo 08 10 18 20 28 30 38 48 50 58 60 68 80 88 90 98 AO 11 OO 00 OO OO OO D3 4E 41 55 38 OD OD OD OD OO OO OO 00 OO 00 04 OO OO OO OO 00 4F 54 4C 54 33 2E 2E 2E 00 00 00 00 00 00 00 OO OO 00 00 OO OO 55 48 4C 45 OD OD OD OD 00 OO OO OO 00 OO OO 00 00 00 00 00 00 4E 45 OD OD 32 2E 2E 2E OO OO 00 OO 00 00 OO OO OO OO OO OO OO OO OO OO OO OO OO 4 4 20 53 49 C3 4F CA 4 1 36 OD OD 2E OD 2E OD 2E 00 00 OO OO 00 00 OO OO OO OO 00 OO OO 00 00 OO OO OO 00 OO D3 53 4D 4E 2E OD OD OD 00 OO 00 OO 00 OO OO OO OO OO OO OO 00 59 OD 50 20 OD 2E 2E 2E 00 OO OO OO 00 00 00
40
70 78
65
A8: BO: B8: CO: C8: DO: D8: EO: E8: FO: F8:
oo 00 oo oo 49 41 42 43 4D 4E 2E
00 OO 00 OO 54 4E 4C OD 50 20 OD
00 00 OO 00 49 53 45 41 55 38 OD
00 00 OO OO 4E 50 20 4C 54 33 2E
OO OO OO OO 47 4F C2 4C 45 OD OD
00 OO OO OO 20 52 41 OD OD 33 2E
OO 00 00 D7 D4 54 53 C3 CA 36 OD
OO OO OO 52 52 41 49 4F 41 OD 2E
Record number 2 is used again for management information by our data base. It simply contains the record length. One can see from the number of carriage returns ($0D) that while only 6 fields are in use, 21 were established by the main program. One can also see that a blank field from this data base is stored as a period ($2E = CHR$(46) = "."). Record number 3 begins at byte 48. It contains our first actual data. It would look like so: Title: Computer: Magazine: Issue: Page: Comment: Sound Synthesis All Compute (sic) Jan 83 26 (none)
Just out of curiosity let's examine the last two sectors of our sequential file chain as reported in bytes 156-159 of side sector 3. Why two sectors? Our fixed length of 150 bytes dictates this. (A fixed record length of 1, 2, 127, or 254 would not span a given sector. The maximum length of a relative record is 254 bytes. 254 is the only number evenly divisible by these factors. A record length of 1 or 2 would be rather impractical.)
TRACK 23 OO 08 10 18 20 28 30 38 40 48 50 58 60 68 70 78 17 OO OO OO OO 00 OO OO 00 OO OO OO OO 00 oo 00 OC OO 00 OO OO OO 00 oo oo oo 00 oo oo 00 oo oo 00 oo 00 oo oo 00 00 oo oo oo oo 00 oo 00 oo oo oo oo 00 oo oo 00 oo oo oo 00 oo 00 oo 00 00 oo oo 00 oo oo oo oo oo oo oo oo oo 00 oo 00 oo oo SECTOR 02 00 oo oo oo oo 00 00 oo oo oo 00 oo oo 00 oo 00 00 oo oo oo oo oo 00 00 oo oo oo oo oo 00 oo 00 oo oo oo oo oo oo oo oo oo oo oo 00 oo 00 00 00 . . . . . . . . . . . . . . . .
66
80: 88: 90: 98: AO: A8: BO: B8: co: C8: DO: D8: EO: E8: F0: F8:
oo oo 00 oo oo 00 oo 00 oo 00 00 oo oo oo 00 00
oo 00 00 oo oo 00 00 oo 00 oo 00 oo 00 oo 00 00
oo 00 00 00 00 oo 00 00 oo oo oo oo oo oo oo 00
oo 00 00 oo oo 00 00 oo 00 00 oo 00 00 00 00 00
FF oo 00 oo oo 00 oo 00 00 oo 00 oo oo 00 oo 00
00 00 oo oo 00 oo oo oo oo oo oo 00 oo 00 00 oo
oo 00 oo oo oo oo oo 00 oo oo oo oo 00 oo 00 oo
00 00 oo 00 oo oo oo 00 oo 00 oo oo oo oo 00 oo 12 oo 00 oo 00 oo oo 00 00 00 00 00 00 00 oo oo 00 oo oo 00 00 oo oo oo oo oo oo 00 oo 00 oo 00 oo
TRACK 23 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . oo 08 lO 18 20 28 30 38 40 48 50 58 60 68 70 78 80 88 90 98 AO A8 BO B8 CO C8 DO D8 EO E8 F0 F8 OO 00 00 00 OO OO 00 OO OO 00 00 OO OO OO OO oo 00 oo oo oo 00 00 oo oo 00 oo 00 oo oo 00 00 oo B1 oo oo oo oo oo 00 oo oo oo 00 00 oo oo oo 00 oo 00 00 00 00 00 oo oo oo oo oo oo oo 00 00 00 oo 00 oo oo oo oo oo oo 00 oo oo oo oo oo oo oo 00 oo oo oo 00 oo FF oo 00 oo 00 oo oo oo 00 oo 00 00 oo 00 oo 00 00 00 00 00 00 oo oo oo 00 00 oo 00 oo 00 00 00 00 00 00 oo 00 00 00 00 00 00 oo oo oo FF 00 00 oo oo 00 00 oo oo oo oo 00 00 oo 00 oo oo oo oo oo oo oo oo 00 00 oo oo 00 oo
SECTOR oo oo oo 00 00 00 00 00 oo 00 00 oo 00 oo 00 oo oo oo 00 oo 00 oo 00 oo oo oo oo oo 00 oo 00 oo oo oo 00 oo 00 oo oo 00 oo oo oo oo 00 00 00 oo oo 00 00 oo 00 00 00 oo 00 00 oo oo 00 oo oo oo
67
An analysis of the preceding two sectors will all but end our discussion on relative file structure. Bytes 2-131 of track 23, sector 2 are the overflow of a previous record. Bytes 132-255 of this same track and bytes 2-27 of track 23, sector 12 make up the next record. This record is empty, as indicated by a 255 ($FF) in the first byte and nulls in the remaining bytes. Track 23, sector 12 has no forward chain and a byte count of 177 ($B1). Our last record in the relative file ends at byte 177 (28-177). What is interesting is the padding beyond this point:
. . .
oo 00 00 oo 00 oo 00 oo oo 00 00 oo 00 oo oo 00
XX oo 00 00 00 oo 00
FF oo 00 oo 00 oo 00
00 oo 00 oo 00 oo 00 oo oo
00 oo oo oo 00 oo 00 oo 00
oo oo 00 oo 00 00
oo oo oo oo 00 oo oo 00 00 oo oo oo
oo oo oo oo
We would expect to find all nulls ($00). Byte 178 ($B2), however, shows an $FF, i.e., the start of a new record. The DOS is one step ahead of the game when expansion time rolls around. A partial record has already been created in this instance. The DOS need only calculate the difference between 255 and the byte count to determine the number of nulls that must follow to complete the record: 255 - 177 = 78 bytes already in existence It then takes the record size to figure out the padding needed: Total Record Length - Bytes in Existence = Nulls to Go 150 - 78 = 72 Slick! We will close our section on relative file structure by taking a brief look at how the computer, or you, can locate a particular relative record. Pick a number, any number. Record number 4 you say. No problem if you know the record length. First we find the appropriate side sector. 4 - 1 = 3 previous records 3 * 150 fixed length = 450th starting byte (i.e., 0 - 449 previous bytes) 450 / 254 = 1.7716535 INT (1.7716535) + 1 = pointer set 2
68
Pointer set 2 / 120 sets of pointers in a side sector = 0.01666667 INT (0.01666667) = side sector 0 Where in side sector 0 is it? Easy. Byte 14 + (pointer set 2 * 2 bytes in a pointer) = byte 18 Bytes 18 and 19 will contain the track and sector of our record. Where in the actual data block is it? A piece of cake. 1.7716535 - INT(1.7716535) = remainder .7716535 2 (skip over bytes 0 and 1) + (.7716535 * 254 bytes of data) = byte 198 Still a disbeliever? Check it out yourself in the preceding hex dumps of track 17, sector 13 and track 17, sector 14.
69
If a DEL file is structured like a sequential file, it may be opened in read mode using the following command: OPEN 2 , 8 , 2 , " F I L E NAME,DEL,R"
The DOS determines whether or not a file is locked by checking bit 6 of the file-type byte. If it is set (1), the file is locked. Even if a file has been locked, it may be renamed or copied using normal disk commands.
Conclusion
The material covered in this chapter is primarily of academic interest. However, do not attempt to recover a blown file unless you thoroughly understand the structure of the directory and how files are stored.
70
71
Memory-Read (M-R) Memory-Write (M-W) Block-Allocate (B-A) Block-Free (B-F) Memory-Execute (M-E) Block-Execute (B-E)
Peek bytes out of 1541 RAM or ROM. Poke bytes into 1541 RAM. Set bit in BAM to indicate a sector is in use. Set bit in BAM to indicate a sector is not in use. Execute a 6502 routine stored in 1541 RAM or ROM. Load and execute a 6502 routine in 1541 RAM.
More often than not, direct-access commands complement one another in actual use. For example, a sector can be read from disk using a U1 command, examined using a B-P or M-R command, altered using a B-P or M-R command, and rewritten to disk using a U2 command. The block-read (U1), buffer-pointer, and block-write (U2) comands are the easiest to comprehend and, as a result, the most widely used. The memory-read and memory-write commands represent a more sophisticated level of direct-access programming and are sometimes used in lieu of the buffer-pointer command. The block-allocate and block-free commands are used primarily for the maintenance of random access files. Random access files were the forerunner of relative files and are rarely used today. The memoryexecute command is used at the guru level of disk programming and requires a rudimentary knowledge of both machine language and the innards of the 1541 to implement. The block-execute command, while documented by Commodore, is almost never used. In order to use the commands mentioned above you will need to learn how to open a direct-access data channel. The format of a direct-access OPEN statement is:
SYNTAX: EXAMPLE: OPEN * i l e # , OPEN OPEN device#, channel#, "#"
2,8,2,"#" l,8,14,"#"
where file# device# channel# = the logical file number (1 to 127) = 8 = the secondary address of the associated open statement (2 to 14)
Opening a direct-access data channel establishes a communication link between the C64 and the 1541. In the first example, we opened logical file number 2 on the C64 side to device number 8 with a secondary address of 2 (channel number 2) on the 1541 side. The only time a channel number is ever referenced is as part of a direct-access command, e.g., a block-read command (U1). Data is always read from disk (GET# file#, INPUT# file#,) or written to disk (PRINT# file#,) by way of the logical file number of the direct-access OPEN statement NOT the channel number. The logical file number and the channel number do not have to match as they do in our first OPEN example. They are two separate entities. The logical file number which resides on the C64 side passes read or write commands to the channel number on the 1541 side. Any similarity
72
between the logical file number and the channel number is for mnemonic purposes only. The second example is a perfectly legal direct-access OPEN statement. In this instance, we opened logical file number 1 (GET#1, PRINT#1,) to device number 8 with a secondary address of 14 (channel number 14) on the 1541 side. Whether or not you use mnemonic OPEN statements is strictly a matter of personal preference. We will begin our tutorial on direct-access programming with a quick review of the 1541 format explained in Chapter 3. The table below outlines the range of track and sector numbers found on a diskette. Zone 1 2 3 4 Track 1 - 17 18 - 24 25 - 30 31 - 35 Sector Range Number of Sectors 0 - 20 21 0 - 18 19 0 - 17 18 0 - 16 17
NOTE: If you attempt to access a track less than 1, a track greater than 35, or a sector out of range for a given track, you will get a DOS error message number 66, ILLEGAL TRACK OR SECTOR.
"Ul:" "U1:
channel#; channel#,
drive#; drive#,
track; track,
PRINT#15,"Ul";2;0;i8;o where file# channel# drive# track sector = the logical file number of the command channel = the secondary address of the associated open statement = 0 = 1 to 35 = 0 to the range for a given track
73
After a given track and sector has been transferred to a buffer with a block-read command (U1), the buffer pointer is automatically left in position 255. Bytes 0-255 of the buffer are now accessible from the starting position, i.e., byte 0. The GET# command is normally used to retrieve one byte at a time from the buffer by way of the logical file number of the direct-access OPEN statement. The GET# command is used rather than INPUT# because the data may contain null bytes, carriage returns and/or line feeds, commas, colons, or other control characters. When using the GET# command you must remember to test each incoming byte for equality with the null string "". A null byte must be converted to CHR$(0) or an 7ILLEGAL QUANTITY ERROR will result when you try to find the ASCII value of the byte. (The GET# command fails to make the necessary conversion for you.) The ASCII value of a byte is used to check for control characters. These characters are misinterpreted by the INPUT# command. The following example reads the block from track 18, sector 0 (the BAM) into disk RAM and prints the contents to the screen.
lOO 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 260 270 280 290 300 REM BLOCK-READ (U1) OPEN 1 5 , 8 , 1 5 PRINT#15,"10" INPUT#15,EN*,EM*,ET*,ESS I F EN*<>"00"G0T0 290 OPEN 2 , 8 , 2 , " # " PRINT#15,"Ul"52;05l8;0 INPUT#15,EN*,EM*,ET*,ES* I F EN*<>"00"G0TQ 270 FOR 1=0 TO 255 GET#2,B* I F B$=""THEN B*=CHRS<0> A=ASC<B*> PRINT S T , I , A , I F A>31 AND A<96 THEN PRINT PRINT NEXT I CLOSE 2 INPUT#15,EN*,EM*,ETS,ES* CLOSE 15 END
B*,
Description Opens logical file number 15 (PRINT#15,) to device 8 with a secondary address of 15 (command channel). Initializes drive 0. Query the error channel. Opens logical file number 2 (GET#2,) to device 8 with a secondary address of 2 (channel number 2) letting the 1541 assign a buffer area. Reads the block from drive 0, track 18, sector 0 into channel 2 buffer area. Query the error channel. Begin loop to read 256 bytes.
74
200 210 220 230 240 250 260 270 280 290 300
Transfer a byte from channel 2 buffer area to C 64 memory by way of the GET# command (GET# logical file number not the channel number). Test for equality with the null string "". ASCII conversion of a byte. Print the status variable (ST), our loop counter, and the ASCII value of the byte. Print the byte if it's within normal ASCII range. Terminate comma tabulation. Increment loop counter. Close logical file number 2. Suppress the error light. Close logical file number 15. End.
An explanation of programming technique is in order here. Initialization (line 120) is done prior to opening a direct-access data channel (line 150). Initialization automatically shuts down all direct-access data channels (2 -14) that are open on the 1541 side. The command channel (15) is not affected. Logical files still remain open on the C64 side, however. Any attempt to access a data channel after initialization results in a 70, NO CHANNEL error. The DOS attempts to rewrite the BAM each time a direct-access channel is closed (line 270). If a diskette is either write protected or DOS protected, the BAM is not rewritten and the error light remains on until cleared. Fortunately, no damage has been done to the data on the diskette. The error light is quite distracting nevertheless. You can suppress the error light after closing a direct-access data channel simply by inputting the error number, message, track, and sector via the command channel (line 280). The alternate formats of the block-read command (U1) in line 160 are: PRINT#15,"Ul:"2;0;i8;o PRINT#15,"Ul:2,0,18,0" Although the block-read command (U1) comes in three basic flavors, line 160 uses the preferred format. It will be used in demonstration programs throughout the chapter for consistency. Alternate formats will appear in passing. Additionally, lines 210-220 are often combined into one BASIC statement for the sake of efficiency: A=ASC(B*+CHR*(0>) Recall that lines 210-220 are necessary because the GET# command does not interpret nulls correctly.
75
EXAMPLE: PRINT#15,"B-P";2;144
where file# channel# = the logical file number of the command channel = the secondary address of the associated open statement
byte position = 0 to 255 The following program displays a disk name by reading only bytes 144 to 159 from track 18, sector 0.
1 0 0 REM BUFFER-POINTER 110 OPEN 15,8,15
1 4 0 IF EN *<>"00 "G0T0 3 2 0
INPUT#15,EN*,EM*,ET*,ES*
150
170
OPEN
i60
PRiNT#i5,"Ui";2;o;i8;o
2,8,2,"#"
200
210 220 230 240 250 260 270 280 290 300 310 320 330
INPUT#15,EN*,EM*,ET*,ES$
FOR 1=1 TO 16 GET#2,B$ I F B*=""THEN B*=CHR*<0> A=ASC<B*> I F A>127 THEN A=A-128 I F A<32 OR A>95 THEN A=63 I F A=34 THEN A=63 DN*=DN*+CHR*<A> NEXT I PRINT"CDOWN>DISK NAME: " ; D N * CLOSE 2 INPUT#15,EN*,EM*,ET*,ES* CLOSE 15 END
Description Sets channel 2 pointer to position 144 in the buffer area. Concatenate (build) the disk name one byte at a time by jamming it within printable ASCII range.
76
"U2:" "U2:
channel#; channel#,
drive#; drive#,
track; track,
EXAMPLE: PRINT#15,"U2";2;o;18;o
where file# channel# drive# track sector = the logical file number of the command channel = the secondary address of the associated open statement = 0 = 1 to 35 = 0 to the range for a given track
The entire contents of a buffer are written to disk during the execution of a block-write command (U2). The position of the buffer-pointer is irrelevant. It is not referred to by the DOS during the execution of a block-write command (U2). The first program listed below allows a disk name to be changed using a block-write command (U2). The second example allows you to edit the cosmetic disk ID that appears in the BAM. NOTE: This program does not change the formatting ID that is embedded in the header block of every sector.
77
100 REM EDIT DISK NAME 110 F 0 R I = l T 0 1 6 120 PAD*=PAD*+CHR*<160> 130 NEXTI 140 PRINT"{CLR>EDIT DISK NAME - 1541" 150 PRINT"{DOWN>REMOVE <RVS>WRITE PROTEC T TAB<ROFF>" 160 PRINT"<DOWN>INSERT DISKETTE IN DRIVE
II
170 PRINT"{DOWN>PRESS CRVS>RETURNCROFF> TO CONTINUE" 180 G E T C * : I F C * = " " T H E N 1 8 0 190 IFC*< >CHR*(13)GOTO180 200 PRINT"OK" 210 0 P E N 1 5 , 8 , 1 5 220 P R I N T # 1 5 , " I 0 " 230 I N P U T # 1 5 , E N * , E M * , E T * , E S * 240 IFEN*="00"G0T0280 250 PRINT"CDOWN>"EN*", "EM*","ET*","ES* 260 CL0SE15 270 END 280 0 P E N 2 , 8 , 2 , " # " 290 P R I N T # 1 5 , " U l " ; 2 ; 0 ; l 8 ; 0 300 I N P U T # 1 5 , E N * , E M * , E T * , E S * 310 P R I N T # 1 5 , " B P " ; 2 % 2 320 GET#2,B* 330 IFB*=""THENB*=CHR*(0) 340 DOS=ASC(B*) 350 IFD0S=65G0T0390 360 PRINT"tD0WN>73,CBM DOS V 2 . 6 1 5 4 1 , 0 0 ,
00"
370 380 390 400 410 420 430 440 450 460 470 480 490 500 510
PRINT"CDOWN>{RVS>FAILEDCROFF>" G0T0720 PRINT#15,"BP"5 2 ; 1 4 4 F0RI=lT016 GET#2,B* IFB*=""THENB*=CHR*< 0 ) A=ASC(B*) IFA >127THENA=A-128 IFA< 320RA >95THENA=63 IFA=34THENA=63 ODN*=ODN*+CHR*(A) NEXTI PRINT"CDOWN>OLD DISK NAME: " ; O D N * INPUT"{DOWN>NEW DISK NAME";NDN* IFLEN(NDN*)< >OANDLEN(NDN*)<17G0T0530 (Y/N) YCLE
78
540 IFQ*< >"Y"6DT0720 550 NDN*=LEFT*(NDN*+PAD*,16) 560 P R I N T # 1 5 , " B - P " ; 2 ; 1 4 4 570 PRINT#2,NDN*; 580 P R i N T # i 5 , " U 2 " ; 2 ; o ; 1 8 ; 0 590 I N P U T # 1 5 , E N * , E M * , E T * , E S * 610 620 630 640 650 660 670 680 690 700 710 720 730 740 750
600
PRINT"<DOWN>"EN*", "EM*","ET*","ES* PRINT"CDOWN> <RVS>FAILED<ROFF>" G0T0720 CL0SE2 INPUT#15,EN*,EM*,ET*,ES* PRINT#15,"I0" INPUT#15,EN*,EM*,ET*,ES* CLQSE15 PRINT"CDOWN>DONE!" END REM CLOSE CL0SE2 INPUT#15,EN*,EM*,ET*,ES* CL0SE15 END
IFEN*="OO"G0T0640
Description Opens logical file number 2 (GET#2, PRINT#2,) to device 8 with a secondary address of 2 (channel number 2) letting the 1541 assign a buffer area. Query DOS version. Pad new diskette name. Reset channel 2 pointer to position 144. Overwrite existing disk name in channel 2 buffer area. Write channel 2 buffer to drive 0, track 18, sector 0. Update the BAM ($0700-$07FF) to reflect a disk name change.
The alternate formats of the block-write command (U2) in line 580 are:
PRINT#15,"U2:"2;o;i8;o FRINT#15,"U2:2,0,18,0"
100 REM EDIT DISK ID 110 PRINT"CCLR>EDIT DISK ID - 1541" 120 PRINT"CDOWN>REMOVE tRVS>WRITE PROTEC T TABCROFF>"
79
IN DRIVE
140 PRINT"tDOWN>PRESS {RVS>RETURN<ROFF> TO CONTINUE" 150 G E T C * : I F C * = " " T H E N 1 5 0 160 IFC*< >CHR*(13)GOTO150 170 PRINT"OK" 180 0 P E N 1 5 . 8 , 1 5 190 P R I N T # 1 5 , " 1 0 " 200 I N P U T # 1 5 , E N * , E M * , E T * , E S * 210 IFEN*="00"G0T0250 220 PRINT"CDOWN>"EN*", "EM*","ET*","ES* 230 CL0SE15 240 END 250 0 P E N 2 , 8 , 2 , " # " 260 P R I N T # 1 5 , " U i " ; 2 ; 0 ; i 8 ; o 270 I N P U T # 1 5 , E N * , E M * , E T * , E S * 280 P R I N T # 1 5 , " B - P " ; 2 5 2 290 GET#2,B* 300 IFB*=""THENB*=CHR*(0) 310 DOS=ASC(B*) 320 IFD0S=65G0T0360 330 PRINT"CD0WN>73,CBM DOS V 2 . 6 1 5 4 1 , 0 0 ,
00"
340 PRINT"CDOWN> iRVS>FAILED<ROFF>" 350 G0T0690 360 PRINT#15,"BP" 2 ; 1 6 2 370 F 0 R I = l T 0 2 380 GET#2,B* 390 IFB*=""THENB*=CHR*(0) 400 A=ASC(B*) 410 IFA >127THENA=A-128 420 IFA< 320RA >95THENA=63 430 IFA=34THENA=63 440 ODI*=ODI*+CHR*(A) 450 NEXTI 460 PRINT"tDOWN>OLD DISK I D : ";ODI* 470 INPUT"{DOWN>NEW DISK I D " ; N D I * 480 I F L E N ( N D I * ) < >OANDLEN< N D I * ) < 3G0T0500 490 G0T0690 500 INPUT"{DOWN3ARE YOU SURE ( Y / N ) YCLE FT 3 > " ; Q * 510 IFQ*<>"Y"G0T0690 520 N D I * = L E F T * < N D I * + C H R * ( 0 ) , 2 > 530 P R I N T # 1 5 , " B P " ; 2 ; 1 6 2 540 P R I N T # 2 , N D I * ; 550 P R I N T # 1 5 , " U 2 " ; 2 ; 0 ; 1 8 ; 0 560 I N P U T # 1 5 , E N * , E M * , E T * , E S * 570 I F E N * = " 0 0 " G 0 T 0 6 1 0 580 PRINT"{DOWN>"EN*", "EM*","ET*","ES*
80
590 600 610 620 630 640 650 660 670 680 690 700 710 720
PRINT"<D0WN>CRVS>FAILEDCROFF>" G0T0690 CLOSE2 INPUT#15,EN*,EM*,ET*,ES* PRINT#15,"I0" INPUT#15,EN*,EM*,ET*,ES* CL0SE15 PRINT"CDOWN>DONE!" END REN CLOSE CL0SE2 I NPLIT# 15, EN* , EM*, E T * , ES* CL0SE15 END
The alternate formats of the block-write command (U2) in line 550 are: PRINT#15,"U2:"2;o;i8;o PRINT#15,"U2:2,0,18,0"
CHR*<hi-
CHR*<hi-
81
where
file#
= the logical file number of the command channel = lo-byte of the memory address = hi-byte of the memory address = 1 to 255
The third parameter ofthe memory-read command, CHR$(# ofbytes), is undocumented by Commodore. The use ofthe third parameter is always optional. The default is CHR$(1), i.e., 1 byte. Typically a block-read command (U1) is issued prior to a memory-read command. A blockread command (U1) transfers the data that is recorded on a given track and sector to one offour pages (256 bytes) of RAM. A page of RAM is called a buffer. When you open a direct-access data channel to the 1541 with OPEN 2,8,2,"#", the DOS arbitrarily selects one buffer as a workspace for that channel. As long as you use the GET# file# command or the PRINT# file# command from the associated OPEN statement you do not need to know which buffer the DOS is using. The buffer in use is only important when you issue a memory-read command. You may tell the DOS which buffer area to use in the direct-access OPEN statement itsetf. The format for selecting a buffer is:
SYNTAX: OPEN f i l e # , device#, channel#, "# buffer#"
where buffer# = 0 to 3
The table below shows how the buffer areas are organized in the 1541.
Buffer Number
Address $0000 - $00FF $0100 - $01FF $0200 - $02FF $0300 - $03FF $0400 - $04FF $0500 - $05FF $0600 - $06FF $0700 - $07FF
Example Not available (ZERO PAGE) Not available (STACK) Not available (COMMAND BUFFER) OPEN 2,8,2,"#0" OPEN 2,8,2,"#1" OPEN 2,8,2,"#2" OPEN 2,8,2,"#3" Not available (BAM)
0 1 2 3
82
NOTE: Two or more direct-access data channels cannot share the same buffer area. If you attempt to open a direct-access data channel to a buffer already in use a 70, a NO CHANNEL error will result. The GET# command is used following a memory-read command to retrieve the contents of the buffer you selected. There is one major difference, however. Bytes are now fetched over the command channel not the logical file number of the "OPEN file#, device#, channel#, buffer#" statement. Bytes must still be tested for equality with the null string "" and converted to CHR$(0) if need be. The next program selects buffer #0 ($0300 - $03FF) as a workspace and does a blockread of track 18, sector 0. Bytes are returned to the C64 side from buffer #0 with memoryread and GET# commands, and printed to the screen.
100 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 260 270 280 290 300 310 REM TWO PARAMETER MEMORY-READ OPEN 1 5 , 8 , 1 5 PRINT#15,"10" INPUT#15,EN*,EM*,ET*,ES* I F EN*< >"0O"G0T0 300 OPEN 2 , 8 , 2 , " # 0 " PRINT#15,"Ul";2;0;18;0 INPUT#15,EN*,EM*,ET*,ES* I F EN*<>"00"G0T0 280 FOR 1=0 TO 255 PRINT#15,"M-R"CHR* <I)CHR* < 3 > GET#15,B* I F B*=""THEN B*=CHR*<0> A=ASC<B*> PRINT I , A , I F A>31 AND A<96 THEN PRINT B * , PRINT NEXT I CLOSE 2 INPUT#15,EN*,EM*,ET*,ES* CLOSE 15 END
Description Opens logical file number 2 to device 8 with a secondary address of 2 assigning buffer number 0 ($0300 - $03FF) as a workspace. Reads the block from drive 0, track 18, sector 0 into channel 2 buffer area ($0300 - $03FF). Begin loop to read 256 bytes ($0300 - $03FF). Indexed memory-read command ($0300 - $03FF). Transfer a byte from channel 2 buffer area to C64 memory via the command channel (GET#15,).
83
The alternate format of the standard memory-read command in line 200 is:
PRINT#15,"M-R:"CHR*<I>CHR*<3>
Please note that we deliberately omitted the third parameter of the memory-read command in the preceding example. The following example incorporates all three parameters of the memory-read command to read a disk name.
iOO 110 120 130 140 150 160 170 180 190 REM THREE PARAMETER MEMORY-READ OPEN 1 5 , 8 , 1 5 PRINT#15,"10" INPUT#15,EN*,EM*,ET*,ES* I F EN*<>"00"G0T0 320 OPEN 2 , 8 , 2 , " # 1 " PRiNT#i5,"ui";2;0;i8;0 INPUT#15,EN*,EM*,ET*,ES* I F EN*<>"00"G0T0 300 PRINT#15,"M-R"CHR*<144>CHR*<4)CHR*<1 FOR 1=1 TO 16 GET#15,B* I F B*=""THEN B*=CHR*<0) A=ASC<B*> I F A>127 THEN A = A - 1 2 8 I F A<32 OR A>95 THEN A=63 I F A=34 THEN A=63 DN*=DN*+CHR*(A> NEXT I PRINT"vDOWN>DISK NAME: " ; D N * CLOSE 2 INPUT#15,EN*,EM*,ET*,ES* CLOSE 15 END
6)
200 210 220 230 240 250 260 270 280 290 300 310 320 330
Description Opens logical file number 2 to device 8 with a secondary address of 2 assigning buffer number 1 ($0400 - $04FF) as a workspace. Reads the block from drive 0, track 18, sector 0 into channel 2 buffer area ($0400 - $04FF). Memory-read command ($0490 - $049F). Begin loop to read 16 characters. Transfer a byte from channel 2 buffer area to C64 memory over the command channel (GET#15,).
Inclusion of the third memory-read parameter means that we no longer have to issue a memory-read command to fetch each byte like we did in the first sample program. Instead, we establish a loop after the memory-read command to pull a byte in. (See lines
84
200-280 above.) The alternate format of the three parameter memory-read command in line 190 is:
PRINT#15,"M-R:"CHR*(144>CHR*(4)CHR*(16)
CHR*<hi-
EXAMPLE: PRINT#15,"M-W"CHR*(2)CHR*(5)CHR*<2)CHR*(1) CHR*<8) PR I NT# 15, " M-W " CHR$ (2 ) CHR* (5 ) CHR* ( 2 ) CHR* ( 1 ) D*
where file# lo-byte hi-byte # of bytes data = the logical file number of the command channel = lo-byte of the memory address = hi-byte of the memory address = 1 to 34 = a string variable or a CHR$ iteration
A total of 34 data bytes may be written with each issuance of a memory-write command. Typically only 8, 16, or 32 data bytes are sent out at one time in a loop as our buffer size (256 bytes) is evenly divisible by these factors. At the most sophisticated level of disk programming, machine language programs can be poked into RAM inside the 1541 with a memory-write command and then executed. (See Chapter 7 for actual programs of this nature.) In practice, however, one encounters limited use of the memory-write command. The following example demonstrates the use of the memory-write command. It allows you to change the load address of a program file. A routine of this nature would be used to aid in the disassembly of a program that normally loads into high memory (e.g., $8000-$BFFF) and is already occupied by a machine language monitor program (SUPE RMON64) or ROM.
85
100 REM EDIT LOAD ADDRESS 110 H*="0123456789ABCDEF" 120 PRINT"CCLR>EDIT LOAD ADDRESS -
1541"
130 PRINT" tDOWN3REMOVE CRVS>WRITE PROTEC T TAB<ROFF>" 140 PRINT"CDOWN>INSERT DISKETTE IN DRIVE
II
150 PRINT" {DOWN>PRESS <.RVS>RETURN<.ROFF3TO CONTINUE" 160 G E T C * : I F C * = " " T H E N 1 6 0 170 IFC*< >CHR* <13)G0T0160 180 PRINT"OK" 190 0 P E N 1 5 , 8 , 1 5 200 P R I N T # 1 5 , " I 0 " 210 I N P U T # 1 5 , E N * , E M * , E T * , E S * 220 IFEN*="OO"G0T0260 230 PRINT"CDOWN>"EN*", "EM*","ET*","ES* 240 CL0SE15 250 END 260 P R I N T # 1 5 , " M - R " C H R * < 1 ) C H R * ( 1 ) 270 GET#15,D0S* 280 IFDOS*=""THENDOS*=CHR*(0) 290 DOS=ASC(DOS*) 300 IFD0S=65G0T0330 310 PRINT"{D0WN>73,CBM DOS V 2 . 6 1 5 4 1 , 0 0 ,
00"
320 330 340 350 360 370 380 390 400 410 420 430 440 450 460 470 480 490 500 510 520 530
G0T0910 INPUT"{DOWN>FILENAME";F* I F L E N ( F * ) < >OANDLEN< F*> <17G0T0360 G0T0920 0PEN2,8, 2 , " 0 : " + F * + " , P , R " INPUT#15,EN*,EM*,ET*,ES* IFEN*="00"60T0400 G0T0940 PRINT#15,"M-R"CHR*(24)CHR*(0)CHR*(2) GET#15,T* T=ASC(T*+CHR*(0 >) GET#15,S* S=ASC< S * + C H R * ( 0 ) ) CL0SE2 INPUT#15,EN*,EM*,ET*,ES* IFEN*="00"G0T0490 G0T0900 0PEN2,8,2,"#2" PRINT#15,"Ul";2;o;T;S INPUT#15,EN*,EM*,ET*,ES* IFEN*="00"G0T0540 G0T0900
86
540 550 560 570 580 590 600 610 620 630 640 650
PRINT#15,"M-R"CHR*(2)CHR*(5)CHR*(2) GET#15,LOW* LOW=ASC(L0W*+CHR*(0)) GET#15,HIGH* HIGH=ASC(HIGH*+CHR*(0)) D=HIGH G0SUB1010 OLA*=HD* D=LOW G0SUB1010 OLA*=OLA*+HD* PRINT"CDOWN>OLD LOAD ADDRESS: " ; O L A *
660 INPUT"CDOWN>NEW LOAD ADDRESS";NLA* 670 IFLEN(NLA*)=4G0T0690 680 G0T0960 690 INPUT"CDOWN>ARE YOU SURE ( Y / N ) Y<LE FT 3 > " ; 0 * 700 I F 0 * < >"Y"G0T0960 710 H D * = R I 6 H T * ( N L A * , 2 ) 720 G0SUB1060 730 IFTME=1G0T0960 740 LOW=D 750 HD*=LEFT* <NLA*,2) 760 GOSUB1060 770 IFTME=*1G0T0960 780 HIGH=D 790 PRINT#15,"M-W"CHR* <2)CHR* < 5 ) C H R * ( 2 ) C HR*(LOW)CHR*(HIGH) 800 P R I N T # 1 5 , " U 2 " ; 2 ; O j T ; S 810 I N P U T # 1 5 , E N * , E M * , E T * , E S * 820 I F E N * = " 0 0 " G 0 T 0 8 4 0 830 G0T0940 840 CL0SE2 850 I N P U T # 1 5 , E N * , E M * , E T * , E S * 860 CL0SE15 870 PRINT"CDOWN>DONE!" 880 END 890 REM CLOSE 900 PRINT"{DOWN>"EN*", "EM*","ET*","ES* 910 PRINT"<DOWN>{RVS>FAILEDCROFF>" 920 CL0SE15 930 END 940 PRINT"{DOWN>"EN*", "EM*","ET*","ES* 950 PRINT" CDOWN> <.RVS>FAILED{ROFF> " 960 CL0SE2 970 I N P U T # 1 5 , E N * , E M * , E T * , E S * 980 CLOSE15 990 END 1000 REM DECIMAL TO HEXADECIMAL 1010 H = I N T ( D / 1 6 )
87
1020 1030 1040 1050 1060 1070 1080 1090 1 = 16 1100 1110 1120 1130 1140 1150 1160 1170 1180 1190 1200
L=D-<H*16) HD*=MID*<H*,H+1,1>+MID*(H$,L+1,1) RETURN REM HEXADECIMAL T0 DECIMAL TME=0 H=0 F0RI=lT016 IFLEFT$(HD$,1)=MID$(H$,I,1)THENH=I: NEXTI IFH=OTHENTME=1:GOTO1200 H=H-1 L=0 F0RI=lT016 IFRIGHT* < H D * , 1 ) = M I D * < H $ , 1 , 1 ) T H E N L = I NEXTI IFL=OTHENTME=1:GOTO1200 L=L-1 D=H*16+L RETURN
: 1 = 16
Line Range 260-320 330-350 360-390 400-440 450 490 500 540 550 570 590-640 660-700 710-780 790 800
Description Query DOS version ($0101). Input file ryime. Opens logical file number 2 to device 8 with a secondary address of 2 for a program read. Fetch file name track ($0018) and sector ($0019). Close logical file number 2. Reopens logical file number 2 to device 8 with a secondary address of 2 assigning buffer number 2 ($0500 $05FF) as a workspace. Reads the starting block of the filename from drive 0 as specified by $0018 and $0019 into channel 2 buffer area ($0500 - $05FF). Three parameter memory-read command to fetch two byte load address ($0502 - $0503). Fetch lo-byte of load address ($0502). Fetch hi-byte of load address ($0503). Decimal to hexadecimal conversion of load address. Input new load address. Hexadecimal to decimal conversion of new load address. Memory-write of new two byte load address ($0502 $0503). Write channel 2 buffer ($0500 - $05FF) to drive 0, track ($0018), sector ($0019).
88
EXAMPLE: PRINT#15,"B-A";o;1; 7
where file# drive# track sector = the logical file number of the command channel = 0 = 1 to 35 = 0 to the range for a given track
The following program allocates every sector on a diskette. Run this program on a test diskette.
lOO 110 120 130 140 150 160 170 180 190 REM BLOCK-ALLOCATE OPEN 1 5 , 8 , 1 5 PRINT#15,"10" INPUT#15,EN*,EM*,ET*,ES* I F E N * < > " 0 0 " 6 0 T 0 310 OPEN 2 , 8 , 2 , " # " T=1 S=0 PRINT#15,"B-A"5 0 ; T ; S INPUT#15,EN*,EM*,ET*,ES*
89
200 210 220 230 240 250 260 270 280 290 300 310 320 330 340 350 360 370
I F EN*="OO"GOTO 180 I F EN*<>"65"G0T0 330 BA=BA+1 PRINT T , S , B A T=VAL<ET*) I F T=0 GOTO 290 I F T=18 THEN T = 1 9 : S = 0 : G 0 T 0 180 S=VAL<ESS> GOTO 180 CLOSE 2 INPUT#15,EN*,EM*,ET*,ES* CLOSE 15 END PRINT"CDOWN>"EN$", "EM*","ET$","ES* CLOSE 2 INPUT#15,EN*,EM*,ET*,ES* CLOSE 15 END
Line Range 150 160 170 180 190 200 210 220 230 240 250 260 270 280 290 330-370
Description Open a direct-access channel. Initialize track to 1. Initialize sector to 0. Block-allocate command. Query error channel. The track and sector were not allocated. Something is amiss so bail out. Counter representing the number of sectors allocated in line 170. Print track, sector, counter. The sector just allocated already was but the DOS returns the next available track in the error message (65, NO BLOCK, track, sector). If the next available track is zero then all 683 blocks on the diskette have been allocated. Don't allocate the directory. The DOS returns the next available sector in the error message (65, NO BLOCK, track, sector). Allocate the next available track and sector. Close the direct-access channel. Error handler.
The opening of a direct-access channel Qine 150) is standard form. Why? Because the BAM is rewritten to a diskette when a direct-access data channel is closed fline 290).
90
In reality, though, the BAM is updated on the fly but very erratically. Thus, opening and closing a direct-access data channel is a good habit to get into. An ounce of prevention. . . By the way, what happens when you try to save to a full disk? Error 72, DISK FULL right? Would you believe error 67, ILLEGAL TRACK OR SECTOR,36,01? Track 36? That's right. An error 72 only occurs during normal write mode (i.e., not a direct-access write) where at least 1 free block exists at the outset or the directory is at its physical limit, i.e., 144 active file entries. A block remains allocated until a diskette is validated. Unless a given track and sector somehow chains to a directory entry its bit will be freed (1) during validation. (See the validate command in Chapter 2.) Caution must be taken to ensure that the block-allocate command does not allocate an unused sector in the directory. See line 260 above. Once a sector has been allocated in the directory, it is never deallocated by the DOS, even during a validate. An allocated directory sector can only be freed under software control. The following program makes use of the block-allocate command to certify a formatted diskette. A worst-case binary pattern is written to any sector not currently in use. Bad sectors, if any, are allocated in the BAM. However, these bad sectors will be deallocated if the diskette is ever validated. (Sorry, but that's the nature of the beast.)
100 REM CERTIFY A DISKETTE - 1541 l l O F0RI=lT032 120 NULL*=NULL*+CHR*(0) 130 WRITE*=WRITE*+CHR*<15> 140 NEXTI 150 DIMT7.(681>,S7.<681> 160 PRINT"{CLR> CERTIFY A DISK ETTE" 170 PRINT"CDOWN> {RVS>WAR NINGCROFF>" 180 PRINT"CDOWN>CRVS>RANDOM ACCESSCROFF> AND CRVS>DELCROFF> F I L E S WILL BE LOST" 190 PRINT"REMOVE *RVS>WRITE PROTECT TABC ROFF>" 200 PRINT"{DOWN>INSERT DISKETTE IN DRIVE ii 210 PRINT" {DOWN3-PRESS <.RVS3-RETURN<ROFF> TO CONTINUE" 220 G E T C * : I F C * = " " T H E N 2 2 0 230 IFC*< >CHR*(13)G0T0220 240 PRINT"OK" 250 0 P E N 1 5 , 8 , 15 260 P R I N T # 1 5 , " I O " 270 I N P U T # 1 5 , E N * , E M * , E T * , E S * 280 I F E N * = " 0 0 " G 0 T 0 3 3 0 290 PRINT"CDOWN>"EN*", "EM*","ET*","ES* 300 CL0SE15 310 END
91
320 330 ) 340 350 360 370 380 390 400 410 420
00"
REM BAM PRINT#15,"M-R"CHR$(0)CHR*<7>CHR$(192 F0RI=0T0191 GET#15,B* IFB$=""THENB$=CHR$(0) BAM$=BAM*+B$ NEXTI DOS=ASC(MID*(BAM$,3,1)) IFD0S=65G0T0460 CL0SE15 F'RINT" {DOWN3-73, CBM DOS V 2 . 6
1541,00,
430 PRINT"CDOWN> CRVS>FAILEDCROFF>" 440 END 450 REM BUFFER 460 1=0 470 F 0 R J = l T 0 8 480 PRINT#15,"MW"CHR* < I ) C H R * < 4 ) C H R $ ( 3 2 ) WRITE$ 490 I = I + 3 2 500 NEXTJ 510 T=1 520 S=0 530 C=0 540 A=0 550 P R I N T # 1 5 , " B - A " ; 0 ; T ; S 560 I N P U T # 1 5 , E N * , E M * , E T $ , E S $ 570 I F E N * = " 0 0 " G 0 T 0 6 2 0 580 T=VAL(ET*> 590 IFT=0ANDC=0G0T0760 600 IFT=0G0T0800 610 S=VAL<ES*> 620 T $ = R I G H T * < " 0 " + R I G H T $ ( S T R * < T ) , L E N ( S T R % (T) ) 1) , 2) 630 S * = R I G H T * ( " O " + R I G H T * ( S T R $ ( S ) , L E N ( S T R *<S>>-l>,2> 640 C=C+1 650 I FC= 1THENPRI NT " < UP > " 660 P R I N T # 1 5 , " B - A " ; 0 ; T ; S 670 PRINT"{HOME> xDOWN 6>{RVS>CERTIFYING< ROFF> TRACK " ; T * ; " - SECTOR " ; S * 680 PRINT"{DOWN>NUMBER OF SECTORS CERTIF IED :";c 690 PRINT"CDOWN>NUMBER OF BAD SECTORS AL LOCATED:";A 700 G0SUB1030 710 IFE=1G0T0550 720 A=A+1 730 T7.(A)=T
92
740 S7.(A)=S 750 G0T0550 760 CL0SE15 770 PRINT"CDOWN>ALL SECTORS HAVE BEEN AL LOCATED" 780 PRINT"CDOWN> CRVS>FAILEDCROFF>" 790 END 800 1=0 810 F 0 R J = l T 0 6 820 PRINT#15,"MW"CHR$ <I)CHR$ <4)CHR$(32) MID*<BAM*,1+1,32) 830 I = I + 3 2 840 NEXTJ 850 PRINT#15,"MW"CHR$ < 1 9 2 ) C H R $ ( 4 ) C H R $ ( 3 2)NULL$ 860 PRINT#15,"MW"CHR$(224)CHR$(4)CHR*(3 2)NULL$ 870 T = i 8 880 S=0 890 G0SUB1030 900 P R I N T # 1 5 . " 1 0 " 910 I N P U T # 1 5 , E N $ , E M $ , E T * , E S $ 920 IFA< >0G0T0960 930 CL0SE15 940 PRINT"{DOWN>NO BAD SECTORS!" 950 END 960 FORI=lTOA 970 PRINT#15, " B - A " ; 0 ; T7. ( I ) ; S7. ( I ) 980 NEXTI 990 CLOSE15 lOOO PRINT"CDOWN>DONE!" 1010 END 1020 REM SEEK 1030 J0B=176 1040 G0SUB1120 1050 IFE=lGOTOlOSO 1060 RETURN 1070 REM WRITE 1080 J0B=144 1090 G0SUB1120 l l O O RETURN 1110 REM JOB QUEUE 1120 TRY=0 1130 P R I N T # 1 5 , " M - W " C H R * ( 8 ) C H R $ ( 0 ) C H R * <2) CHR*<T)CHR*<S) 1140 P R I N T # 1 5 , " M - W " C H R * < 1 ) C H R * < 0 ) C H R $ ( 1 ) CHR*<JOB> 1150 TRY=TRY+1 1160 P R I N T # 1 5 , " M - R " C H R $ ( 1 ) C H R $ ( 0 ) 1170 GET#15,E$
93
1180 I F E * = " " T H E N E * = C H R * ( 0 ) 1190 E=ASC<E$> 1200 IFTRY=5OOG0TO122O 1210 I F E >127GOTO1150 1220 RETURN
Line Range 330-380 390-440 460-500 510-540 550 700 710 720-740 800-890 960-980
Description Store the BAM ($0700 - $07A0). Query DOS version. Write worst-case binary pattern to buffer at $0400. Initialize track, sector, and counters. Block-allocate command. Write worst-case binary pattern at $0400 - $04FF to a deallocated track and sector. Query error channel. Error array. Restore the BAM. Allocate any bad sectors in error array.
Lines 330-380 and 800-890 compensate for a bug in the block-allocate command. (See Chapter 9 for the lowdown.) Lines 330-380 store an image of the BAM in C64 RAM. The BAM is restored in lines 800-890. Lines 1020-1230 will be explained in detail in Chapter 6 on intermediate disk programming techniques.
EXAMPLE: PRINT#15,"BF";o;1;7
94
where file# drive# track sector = the logical file number of the command channel - 0 = 1 to 35 = 0 to the range for a given track
The following program deallocates every sector on a diskette. Run this program on a test diskette. 100 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 260 270 280 REM BLOCK-FREE OPEN 1 5 , 8 , 1 5 PRINT#15,"10" INPUT#15,EN*,EM*,ET*,ES* I F EN*<>"00"G0T0 2 6 0 OPEN 2 , 8 , 2 , " # " FOR T=1 TO 3 5 I F T = 1 8 GOTO 2 4 0 NS=20+2*<T>17> + <T>24) + <T>30> FOR S = 0 TO NS PRINT#15,"B-F";0;T;S BF=BF+1 PRINT T , S , B F NEXT B NEXT T CLOSE 2 INPUT#15,EN*,EM*,ET$,ES* CLOSE 1 5 END
Line Range 150 160 170 180 190 200 210 220 250
Description Open a direct-access channel. Begin loop for tracks 1 to 35. Don't deallocate the directory. Calculate sector range. Begin loop for sectors 0 to sector range. Block-free command. Counter to indicate number of blocks freed. Print track, sector, counter. Close the direct-access channel.
The alternate format of the block-free command in line 200 is: PRINT#15,"B-F:";0;T;S
95
The opening and closing ofa direct-access channel is essential if the block-free command is to work correctly. Experimentation in freeing a full diskette reveals that tracks 34 and 35 still remain allocated if this procedure is not followed.
"M-E:"
CHR$<lo-byte>
CHR*<hi-
where file# lo-byte hi-byte = the logical file number of the command channel = lo-byte of the RAM or ROM address = hi-byte of the RAM or ROM address
Machine language programs are poked into 1541 RAM with the memory-write command. The following primitive program pokes a single RTS instruction to RAM and executes it.
100 REM MEMORY-EXECUTE 110 OPEN 1 5 , 8 , 1 5 120 PRINT#15,"M-W"CHR*(O)CHR*<6> C H R * ( 1 ) C HR*<96> 130 P R I N T # 1 5 , " M - E " C H R * < 0 ) C H R * ( 6 ) 140 CLOSE15 150 END
96
More sophisticated coding is available in Chapter 7. In addition, refer to Chapter 9 for pertinent information about the execution of standard ROM routines.
ALTERNATE: PRINT# f i l e # , "BHE: "; channel#; d r i v e # ; track; sector PRINT# * i l e # , "B-E: channel#, d r i v e # , trackj sector" EXAMPLE: PRlNT#l5,"B-E";2;0; l ; 0
where file# channel# drive# track sector = the logical file number of the command channel = the secondary address of the associated open statement = 0 = 1 to 35 = 0 to the range for a given track
The block-execute command could be used in a diagnostic routine but it is difficult to visualize any other advantage that this command has over a normal memory-execute command. The following program demonstrates one of the few block-execute commands you will probably ever see. Qights, camera, action!) Run this program using a test diskette.
lOO REM BLOCK-EXECUTE 110 OPEN 1 5 , 8 , 1 5 120 P R I N T # 1 5 , " I 0 " 130 I N P U T # 1 5 , E N S , E M * , E T * , E S * 140 I F EN*<>"OO"GOTO 250
97
150 OPEN 2 , 8 , 2 , " # 3 " 160 P R I N T # 1 5 , " U l " ; 2 ; 0 ; l ; 0 170 I N P U T # 1 5 , E N * , E M $ , E T * , E S * 180 I F EN*<>"00"G0T0 220 190 PRINT#15,"M-W"CHR$<0>CHR*(6)CHR$(1)C HR*<96> 2 0 0 PRiNT#15,"U2";2;0;1;0 210 PRINT#15,"M-W"CHR*<0> CHR*<6> CHR* <1)C HR*<O> 220 P R I N T # 1 5 , " B - E " ; 2 ; o ; l ; O 230 CLOSE 2 240 I N P U T # 1 5 , E N * , E M $ , E T * , E S * 250 CLOSE 15 260 END
Description Open a direct-access channel specifying buffer number 1 ($0600 - $06FF). Block-read of track 1, sector 0 ($0600 - $06FF). Write 1 byte ($60) to RAM at $0600. Block-write to track 1, sector 0 ($0600 - $06FF). Just to keep us honest. Block-execute of track 1, sector 0 ($0600 - $06FF).
98
on track 15 will return only 15 bytes before sending an erroneous End-Or-Identify (EOI). The C64 status variable (ST) is set to 64 and any further attempt to access the buffer merely returns the same sequence of bytes over and over and over again. Moreover, the byte in position 0 can only be accessed when the buffer-pointer is reset to position 0 in line 190. See for yourself. 100 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 260 270 280 290 300 310 REM BLOCK-READ <BHR> OPEN 15,8,15 PRINT#15,"I0" INPUT#15,EN*,EM*,ET*,ES* IF EN*<>"00"G0T0 300 OPEN 2,8,2,"#" PRINT#15,"B-R"5 2;0;18;0 INF'UT#15,EN*,EM*,ET*,ES* IF EN*<>"00"G0T0 280 PRINT#15,"B-P";2;0 FOR I=0 TO 255 GET#2,B* IF B*=""THEN B*=CHR*<0> A=ASC<B*> PRINT ST,I,A, IF A>31 AND A<96 THEN PRINT B*, PRINT NEXT I CLOSE 2 INPUT#15,EN*,EM*,ET*,ES* CLOSE 15 END
What's even more problematic is the situation that occurs when you do a block-read (B-R) of a track and sector that was rewritten by the block-write command (B-W) which is discussed below. The EOI occurs in connection with the ASCII value of the Oth byte of the sector that was read. Byte 0 contains the value of the buffer-pointer position at the time the block was written with a block-write command (B-W). The forward track reference that was originally there, has been destroyed. The ASCII value of the Oth byte determines how many characters you can access before the EOI occurs. Run the block-read (B-R) program listed above against track 1, sector 0 after you've done the block-write (B-W) experiment listed below on a test disk. Change the track number in line 160 from an 18 to a 1 like this: 160 PRINT#15,"B-R";2;0;1;0 After further experimentation on your own, you should have little trouble understanding why the U1 command replaces the block-read command (B-R). Not only do user manuals continue to promote the use of the block-read command (B-R), but they also either ignore the U1 command altogether or simply mention it in passing without even a hint on how to use it.
99
Block-Write (B-W) RecaU that we also neglected to mention the block-write command (B-W) which we replaced with the U2 command. When you write a block with the block-write command (B-W) a different kind of dilemma occurs. Bytes 1 through 255 of the buffer are recorded on diskette correctly but the last position of the buffer-pointer is written to the Oth byte of the sector (the location of the forward track pointer). If it's any consolation, the data is still intact. Too bad the link has been destroyed. Run the following block-write program on a test diskette.
lOO 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 260 270 REM BLOCK-WRITE <B-W> OPEN 1 5 , 8 , 1 5 PRINT#15,"I0" INPUT#15,EN*,EM*,ET*,ES* I F EN*<>"00"G0T0 260 OPEN 2 , 8 , 2 , " # " PRlNT#l5,"Ui";2;0;i;o INPUT#15,EN*,EM*,ET*,ES* I F EN*<>"00"G0T0 240 FOR 1=0 TO 255 F'RINT#2,CHR*<I>; NEXT I PRINT#15,"B-P";2;6 PRINT#15,"B-W";2;0;l;o CLOSE 2 INPUT#15,EN*,EM*,ET*,ES* CLOSE 15 END
Now run the original block-read (U1) program that we wrote using this diskette. Be sure to change the track in line 160 from an 18 to a 1 as follows:
160 PRlNT#l5,"Ul";2;0;l;0
If all goes according to our diabolical plan, byte 0 will contain a 5 which is exactly where our buffer-pointer ended up. We arbitrarily set it to position 6 in line 220 above and 256 bytes later it wraps around to position 5. (Remember that bytes are numbered from 0 to 255 in a buffer area.) Now change the U1 to a B-R in line 160 and run the program again. This time, only 5 bytes can be accessed before an EOI signal is returned. UJ and UlCommodore has traditionally had a warm reset buried somewhere in ROM on every piece of hardware they have manufactured to date. The UJ command is to the 1541 what a SYS 64738 is to the C64, a warm reset. Or rather, that is what it's supposed to be. The issuance of a UJ command is supposed to reset the 1541. Instead, it hangs the 1541.
100
Press the RUN/STOP key and RESTORE key in tandem to regain control ofthe C64 after typing in this one liner in immediate mode.
OPEN 1 5 , 8 , 1 5 , " U J " : CLOSE15
The same thing is true for the UI- command although Commodore can't really be held responsible here. The UI- command was implemented to set the 1541 to VIC-20 speed, not to take the C64 out to lunch. U3 - U9 The VIC-15U1 User's Manual outlines 7 USER commands that perform a jump to a particular location in RAM. These USER commands and their respectivejump addresses are: User Number U3 (UC) U4 (UD) U5 (UE) U6 (UF) U7 (UG) U8 (UH) U9 (UI) Jump Address $0500 $0503 $0506 $0509 $050C $050F $FFFA
These jump locations are not quite as mystifying as they appear at first glance. Let's modify our simplistic memory-execute program.
100 REM U3 i l O OPEN 1 5 , 8 , 1 5 120 P R I N T # 1 5 , " M W " C H R * ( 0 ) C H R * ( 5 ) C H R * ( 1 ) C HR*(96) 130 P R I N T # 1 5 , " U 3 " 140 CLOSE15 150 END
One should be able to discern that any of the first six USER commands, U3 - U8, could double for a memory-execute command. It is very difficult to understand why Commodore included six jumps to the $0500 page ftuffer number 2). Moreover, the U9 command jumps to $FFA which is a word table pointing to the NMI vector. U9 is an alternate reset that bypasses the power-on diagonstics.
101
CHAPTER 6
The hexadecimal and decimal equivalents for eachjob request as seen by the FDC are: Job Code $80 (128) $90 (144) $A0 (160) $B0 (176) $C0 (192) $D0 (208) $E0 (224) Description READ WRITE VERIFY SEEK BUMP JUMP EXECUTE
If the FDC finds a job request in the job queue, it attempts to carry it out. Once the job is complete or aborted the FDC replaces the job code with an error code. The error codes returned by the FDC to the IP are listed below. The IP error codes and their respective error messages are what you see when you read the error channel.
103
FDC Code $01 (1) $02 (2) $03 (3) $04 (4) $05 (5) $07(7) $08 (8) $09 (9) $0B (11)
IP Code 0 20 21 22 23 25 26 27 29
Error Message OK READ ERROR (header block not found) READ ERROR (no synccharacter) READ ERROR (data block not present) READ ERROR (checksum error in data block) WRITE ERROR (write-verify error) WRITE PROTECT ON READ ERROR (checksum error in header block) READ ERROR (disk ID mismatch)
A more detailed description of each of these error messages can be found in Chapter 7. Suppose that we want to read the contents of a given track and sector. The command initiated on the C64 side is parsed by the IP. If the syntax is correct, it is broken down into a job code, a track, and a sector. Depending upon what buffer has been assigned, the job code is poked into the corresponding job queue table location. The track and sector for the job are poked into the corresponding header table locations. The buffers and their corresponding job queue and header table addresses are outlined below: Buffer Address $0000 - $00FF $0100 - $01FF $0200 - $03FF $0300 - $03FF $0400 - $04FF $0500 - $05FF $0600 - $06FF $0700 - $07FF Job Track Sector
#0 #1 #2 #3
Not available (ZERO PAGE) Not available (STACK) Not available (COMMAND BUFFER) $0000 $0006 $0007 $0001 $0008 $0009 $0002 $000A $000B $0003 $000C $000D Not available (BAM)
For example, a block-read command (U1) issued by the C64 to read the contents of track 18, sector 0 into buffer number 0 ($0300-$03FF) is checked for a syntax error and then broken down by the IP. In time, the FDC will find an $80 (128) at address $0000 in the job queue table, a $12 (18) at address $0006 in the header table, and a $00 (0) at address $0007 in the header table. Armed with that information, the FDC will attempt to seek (find) the track and read the sector. Upon successful completion of the read, the contents of the sector will be transferred to buffer number 0 ($0300-$03FF) and a $01 (1) will be returned by the FDC to address $0000. (If the job request could not be completed for some reason, the job request would be aborted and the corresponding error code would be stored at address $0000 instead.) Interrogation of the error channel will transfer the IP counterpart of the FDC error code, the English message, the track
104
number, and the sector number to the C64 side. If the job request was successful (00, OK,OO,OO), the contents of the track and sector could then be retrieved from the buffer at $0300 - $03FF using a GET# command as described in the previous chapter. What happens, though, if we bypass the drive's parser routine and attempt to work the FDC directly ourselves? We thought you'd never ask. Grand and glorious schemes become possibilities, and that's what intermediate direct-access programming is all about. Armed with a lookup table of job codes, a map of the 1541's buffer areas, a track, a sector, and a lookup table of error codes, the FDC is at your beck and call. Tired of those horrendous grating noises when your drive errs out? Well wish no more. The drive does not do a bump (the root of all evil) to reinitialize when you are working the job queue directly. What more could you ask for? We know. The code, right? The following program works the job queue directly to read the block from track 18, sector 0 into buffer number 0 ($0300 - $03FF) and prints the contents to the screen. Sound vaguely familiar? It should. It's a modification of the first program we wrote under beginning direct-access programming.
lOO REM JOB QUEUE READ 110 OPEN 1 5 , 8 , 1 5 120 P R I N T # 1 5 , " I O " 130 I N P U T # 1 5 , E N * , E M * , E T * , E S * 140 I F EN*<>"00"G0T0 340 150 REM SEEK 160 T=18 170 S=O 180 J0B=176 190 GOSUB 370 200 I F E<>1 GOTO 340 210 REM READ 220 J0B=128 230 GOSUB 370 240 I F E<>1 GOTO 340 250 FOR 1=0 TO 255 260 P R I N T # 1 5 , " M - R " C H R * ( I ) C H R * ( 3 ) 270 G E T # 1 5 , B * 280 I F B*=""THEN B$=CHR*<0> 290 A=ASC<B*> 300 PRINT S T , I , A , 310 I F A>31 AND A<96 THEN PRINT B * , 320 PRINT 330 NEXT I 340 CLOSE 15 350 END 360 REM JOB QUEUE 370 TRY=0 380 PRINT#15,"M-W"CHR* < 6 ) C H R $ ( 0 ) C H R * ( 2 ) C HR*<T>CHR*<S> 390 PRINT#15,"M-W"CHR*<0>CHR*<0>CHR*<1>C
105
HR*<JOB) 400 TRY=TRY+1 410 P R I N T # 1 5 , " M - R " C H R * ( 0 ) C H R * ( 0 ) 420 G E T # 1 5 , E * 430 I F E*=""THEN EtM=CHR*<0> 440 E=ASC<E*> 450 I F TRY=500 GOTO 470 460 I F E>127 GOTO 400 470 RETURN
Line Range Main Program 110 120-140 160 170 180-190 200 220-230 240 250 260 270 280 290 300 310 320 330 340 350 Subroutine 370 380 390 400-460 470
Description
Open the command channel. Initialize drive. Initialize track to 18. Initialize sector to 0. SEEK track 18. Query FDC error code. READ sector 0 on track 18 into buffer number 0 ($0300-$03FF). Query FDC error code. Begin loop to read 256 bytes ($0300-$03FF). Two parameter memory-read. Transfer a byte from buffer number 0 to C64 memory by way of the command channel (GET#15,). Test for equality with the null string "". ASCII conversion of a byte. Print the status variable (ST), our loop counter, and the ASCII value of the byte. Print the byte if it's within printable ASCII range. Terminate comma tabulation. Increment loop counter. Close the command channel. End.
Initialize try counter. Stuff the track and sector numbers into buffer number 0's header table ($0006-$0007). Stuff job code number into buffer number 0's job queue table ($0000). Wait for FDC to complete the job. Return with FDC error code in hand.
The good news is that working the job queue is not quite as complex as it at first appears. The subroutine in lines 370-470 is the very heart of the matter. We simply stuff
106
our track and sector into the header table, our job code into the job queue table, and wait until the FDC has completed the operation. Keep in mind that this example was using buffer number 0 ($0300-$03FF). The corresponding header table and job queue table addresses were $0006 for the track, $0007 for the sector, and $0000 for the job code. Please note that every job code is greater than 127. (Bit 7 is deliberately set high (1).) Recall that when the FDC has completed a job, the job code is replaced with an error code. All error codes are less than 128. (Bit 7 is deliberately set low (0).) Line 460 waits until bit 7 of the job code is set low (0) by the FDC. If bit 7 is high (1), the FDC is still working so we must continue to wait (line 410). Error handling is a bit out ofthe ordinary too but not all that hard to comprehend either. An FDC error code of 1 means the job was completed successfully. Any other number indicates an error. You will also note a simple hierarchy of jobs in the program listing. Before we can read a sector (line 220) we must always find the track first Qine 180). Now are you ready for this one? Initialization is not necessary at all when working the job queue directly. Lines 120-140 were included as a force of habit. Applications like reading damaged or DOS protected diskettes may dictate that we do not initialize. Now for the bad news.
WARNING
Read this passage carefully.Then read it again for good measure.Experience is a hard teacher test first, lesson afterward. 1. You must remember at all times when working the job queue that you have directly bypassed the parser routine. This is extremely dangerous because you have in effect killed all protection built into the 1541 itself. Let us explain. If by some poor misfortune you elect to do a read on track 99, the FDC doesn't know any better and takes off in search of track 99. You can physically lock the read/write head if it accidentally steps beyond its normal boundaries, i.e., a track less than 1 or a track greater than 35. No damage is done to the 1541 itself but if thepower-onsequence doesn't return the head to center you will have to disassemble the drive and reposition the head manually. Exceeding the sector range for a given track is no problem, however. The drive will eventually give up trying to find a sector out of range and report an FDC error 2 (an IP 20 error). Tracks are a pain in the stepper motor, however. 2. You must keep your header table locations and your job queue table locations straight in relation to the buffer number you are working. If they are not in agreement, the drive will go off into never-never land. The FDC will either attempt to work a nonexistent job code or seek a track and sector out of bounds. Remember the FDC will do exactly what you tell it to do. You are at the helm at all times. At the minimum, you will have to power off the drive to regain control. Again, no physical damage has been done to the 1541 but you may have to reposition the read/write head yourself. We know from experience.
107
3. You should always monitor the job yourself. The try counter in line 450 is a stopgap measure. Five hundred wait cycles seems like an exaggerated figure here. However, you must give the drive adequate time to find a desired track and settle down before performing ajob. If for some reason it cannot complete the job, it usually aborts and returns an error code on its own. If it doesn't, something is amiss and a try counter may trap it. (You might have to power off the drive to restore the status quo.) A try counter is a little like workman's compensation. Don't work the job queue without it. Now, read these three paragraphs a second time. The following program works the job queue directly to read track 18, sector 0 into buffer number 1 ($0400-$04FF). The disk name is returned with a three parameter memoryread of bytes 144-159 ($0490-$049F). It's another oldie but goodie.
lOO REM JOB QUEUE READ - DISK NAME l l O OPEN 1 5 , 8 , 1 5 120 P R I N T # 1 5 , " I O " 130 I N P U T # 1 5 , E N * , E M 4 , E T * , E S * 140 I F EN*< >"00"G0T0 360 150 REM SEEK 160 T=18 170 S=0 180 JOB=176 190 GOSUB 390 200 I F E<>1 GOTO 360 210 REM READ 220 J0B=128 230 GOSUB 390 240 I F E<>1 GOTO 360 250 PRINT#15,"MR"CHR$(144)CHR$(4)CHR$(1 6) 260 FOR 1=1 TO 16 270 GET#15,B* 280 I F B*=""THEN B*=CHR*<0) 290 A=ASC<B*> 300 I F A>127 THEN A = A - 1 2 8 310 I F A<32 OR A>95 THEN A=63 320 I F A=34 THEN A=63 330 DN*=DN*+CHR*<A> 340 NEXT I 350 PRINT"{DOWN>DISK NAME: " ; D N * 360 CLOSE 15 370 END 380 REM JOB QUEUE 390 TRY=O 400 PRINT#15,"M-W"CHR*(8)CHR*<0)CHR*<2> C HR*<T>CHR*<S> 410 PRINT#15,"M-W"CHR*<1>CHR*(0>CHR$<1>C
108
HR$<JOB> 420 TRY=TRY+1 430 PRINT#15,"M-R"CHR*<1>CHR*<0> 440 G E T # 1 5 , E * 450 I F E*=""THEN E$=CHR*<0) 460 E=ASC(E*> 470 I F TRY=500 GOTO 490 480 I F E>127 GOTO 420 490 RETURN
Line Range
Description
120-140 160 170 180-190 200 220-230 240 250 260-340 390 400 410 420-480 490
Force of habit. Initialize track to 18. Initialize sector to 0. SEEK track 18. Query FDC error code. READ sector 0 on track 18 into buffer number 1 ($0400-$04FF). Query FDC error code. Three parameter memory-read ($0490-$049F). Concatenate the disk name one byte at a time by jamming it within printable ASCII range. Initialize try counter. Stuff the track and sector number into buffer number l's header table ($0008-$0009). Stuff the job code number into buffer number l's job queue table ($0001). Wait for FDC to complete the job. Return with FDC error code in hand.
Not much new here except the buffer in use. Let's review the key memory addresses for working buffer number 1 ($0400-$04FF): BUFFER NUMBER 1 TRACK NUMBER SECTOR NUMBER JOB CODE = = = = $0400 - $04FF $0008 (HEADER TABLE) $0009 (HEADER TABLE) $0001 (JOB QUEUE TABLE)
While we're at it, we might as well review the order of jobs for the sake of posterity. First SEEK a track. Then READ a sector. The next program incorporates four FDC job codes, namely a SEEK, a READ, a WRITE, and indirectly a VERIFY. This routine is a modification of the edit disk name program found in the previous chapter. Keep in mind that we are working buffer number 2 here ($0500-$05FF). The header table addresses are $000A for the track and $000B for the sector. The job codes themselves will be poked into location $0002 in the job queue table.
109
100 REM JOB QUEUE READ/WRITE - EDIT DISK NAME 110 FOR 1=1 TO 16 120 PAD*=PAD*+CHR*(160 > 130 NEXT I 140 PRINT"xCLR>EDIT DISK NAME - 1541" 150 PRINT"{DOWN>REMOVE CRVS>WRITE PROTEC T TAB<ROFF>" 160 PRINT"tDOWN>INSERT DISKETTE IN DRIVE li 170 PRINT"CDOWN3 PRESS tRVS > RETURN < ROFF > TO CONTINUE" l S O GET C * : I F C*=""THEN ISO 190 I F C*< >CHR*(13)GOTO 180 200 PRINT"OK" 210 OPEN 1 5 , 8 , 1 5 220 P R I N T # 1 5 , " I O " 230 I N P U T # 1 5 , E N * , E M * , E T * , E S * 240 I F EN*="OO"GOTO 290 250 PRINT"{DOWN>"EN*", "EM*","ET*","ES* 260 CLOSE 15 270 END 280 REM SEEK 290 T=18 300 S=0 310 JOB=176 320 GOSUB 660 330 REM READ 340 J0B=128 350 GOSUB 660 360 P R I N T # 1 5 , " M - R " C H R * <144)CHR*(5)CHR* <1
6)
370 FOR 1=1 TO 16 380 GET#15,B* 390 I F B*=""THEN B*=CHR*(0> 400 A=ASC(B*) 410 I F A>127 THEN A = A - 1 2 8 420 I F A<32 OR A>95 THEN A=63 430 I F A=34 THEN A=63 440 ODN*=ODN*+CHR*(A) 450 NEXT I 460 PRINT"vDOWN>OLD DISK NAME: ";ODN* 470 INPUT"{DOWN>NEW DISK NAME";NDN* 480 I F L E N ( N D N * ) 0 0 AND LEN(NDN*)<17 GOT 0 500 490 GOTO 630 500 INPUT" {DOWN3-ARE YOU SURE ( Y / N ) YCLE FT 3 > " ; Q * 510 I F Q*< >"Y"GOTO 630 520 NDN*=LEFT*(NDN*+PAD*,16 >
110
530 PRINT#15,"M-W"CHR$ <144)CHR$(5)CHR$(1 6)NDN$ 540 REM WRITE 550 J0B=144 560 GOSUB 660 570 P R I N T # 1 5 , " I O " 580 I N P U T # 1 5 , E N * , E M $ , E T * , E S * 590 CLOSE 15 600 PRINT"CDOWN>DONE!" 610 END 620 REM CLOSE 630 CLOSE 15 640 END 650 REM JOB QUEUE 660 TRY=0 670 P R I N T # 1 5 , " M - W " C H R * ( 1 0 ) C H R $ ( O ) C H R $ ( 2 ) CHR*(T)CHR*<S) 680 P R I N T # 1 5 , " M - W " C H R * < 2 ) C H R * ( O ) C H R $ ( 1 ) C HR*(JOB) 690 TRY=TRY+1 700 P R I N T # 1 5 , " M - R " C H R * ( 2 ) C H R * ( 0 ) 710 G E T # 1 5 , E * 720 I F E*=""THEN E*=CHR*<0) 730 E=ASC(E*) 740 I F TRY=500 GOTO 780 750 I F E>127 GOTO 690 760 I F E=1 THEN RETURN 770 REM ERROR HANDLER 780 E T $ = R I G H T * ( S T R * < T ) , L E N < S T R $ ( T ) ) - 1 ) 790 I F T<10 THEN E T * = " O " + E T * SOO E S $ = R I G H T * ( S T R * ( S ) , L E N ( S T R * ( S ) ) - 1 ) 810 I F S<10 THEN E S * = " 0 " + E S * 820 I F E>1 AND E<12 THEN EN*=RIGHT$<STR* ( E + 1 8 ) , 2 ) : G O T O 840 830 E N * = " 0 2 " : E M * = " 7 T I M E OUT":GOTO 860 840 I F E=7 OR E=8 THEN EM*="WRITE ERROR" :GOTO 860 850 EM*="READ ERROR" 860 PRINT"CDOWN>"EN$", "EM*","ET*","ES 870 PRINT"CDOWN> CRVS>FAILED<ROFF>" 880 CLOSE 15 890 END
Description SEEK track 18. READ contents of sector 0 from track 18 into buffer number 2 ($0500-$05FF). WRITE buffer number 2 ($0500-$05FF) to track 18, sector 0. Error handler. 111
Lines 100 to 530 should be setf explanatory by now. Lines 540-560 are equivalent to a block-write command (U2). To write a sector via the job queue we stuff the track and sector in the header table and a $90 (144) into the job queue table and let her rip. The error handler, however, is of interest. The conversion from FDC code to IP code is quite easy. We simply add 18 to the FDC error code (line 820). Note that we try to restrict all errors within a range of 20 to 29. An FDC error code of 0 or greater than 11 is indicative that something went radically wrong. Line 820 arbitrarily reports a ?TIME OUT in this situation. Speaking from experience, the job just plainly didn't get done. A time out occurs very rarely, unless of course, one is inspecting a damaged or DOS-protected diskette. Line 840 is another highlight. An FDC WRITE ($90) automatically flips to an FDC VERIFY ($A0) to compare the contents of the buffer against the sector just written. Kind of neat, isn't it? If the buffer and the sector do not match, we see an FDC error 7, i.e., an IP error number 25, WRITE ERROR. Since a VERIFY is done automatically by the FDC, we will not elaborate any further on this particular job code. The job code for a BUMP is a $C0 (192). Why anybody would ever want to implement this job request is beyond us. A subtle difference exists between the remaining two job codes, a JUMP ($D0) and an EXECUTE ($E0). A JUMP executes a machine language routine poked into RAM. No more, no less. Like a BUMP job, it is seldom used. The program that moves the read/write head in Chapter 9 is the only place where we have ever found a practical use for it. An EXECUTE ($E0) is the Rolls Royce ofjob codes, however. Before a machine language routine is executed, the FDC makes sure that: 1. The drive is up to speed. 2. The read/write head is on the right track. 3. The read/write head has settled. The FDC cannot be interrupted when performing an EXECUTE job. Once the FDC starts to EXECUTE the machine language routine, control is not returned to the IP until the routine is completed. A runaway routine cannot be debugged even with BRK instructions. You must power down the 1541 and try to second guess the side effects of the routine to determine what went wrong. NOTE: The FDC does not automatically return an error code when the routine is completed. It is the programmer's responsibility to change the job code in the job queue table from an EXECUTE ($E0) to an $01 at the end of the routine. If this is not done, the FDC will find the same EXECUTE request on its next scan of the job queue and re-run the routine. Infinite regression! Most of the programs in Chapter 7 make use of the EXECUTE job code in one form or another. Therefore, example programs will be given there.
112
CHAPTER 7
DOS PROTECTION
7.1 Commodore's Data Encoding Scheme
Before we can enter the netherworld of DOS protection you have to possess a thorough understanding of how the 1541 records a sector on a diskette. Any given sector is divided into two contiguous parts, a header block and a data block. For clarity sake let's review the parts of a sector discussed in Chapter 3. Header Block (16 8-bit bytes) Number of Bytes Description Sync Character Header Block Identifier ($08) Header Block Checksum Sector Number Track Number ID LO ID HI Off Bytes ($0F) Header Gap ($55)
Description Sync Character Data Block Identifier ($07) Data Bytes Data Block Checksum Off Bytes ($00) Tail Gap ($55)
1 256 1 2 Variable
The 1541 writes a track on the surface of a diskette as one continuous bit stream. There are no demagnetized zones between sectors on a track to delineate where one sector ends and another one begins. Instead, Commodore relies upon synchronization characters
113
for reference marks. A DOS 2.6 sync mark can be defined as five 8-bit $FF's written in succession to disk. Note that a sync mark is recorded at the front end of each header block and each data block. To differentiate a sync mark from a normal data byte, the 1541 writes to diskette in two modes, a sync mode and a normal write mode. To appreciate the uniqueness of a sync mark we must first look at how a normal data byte is recorded. During normal write mode each 8-bit byte is encoded into 10 bits before it is written to disk. Commodore calls this encoding scheme binary to GCR (Group Code Recording) conversion. The conversion technique itself is quite straightforward. Each 8-bit byte is separated into two 4-bit nybbles, a high nybble and a low nybble. For example, the binary representation of $12 (18) is %00010010. The breakdown of this 8-bit byte into its two 4-bit nybbles is depicted below: Hexadecimal $12 (18) Binary 00010010 High Nybble 0001xxxx Low Nybble xxxx0010
Mathematically speaking, a 4-bit nybble can be decoded into any one of 16 different decimal values ranging from 0 (all bits turned off) to 15 (all bits turned on) as follows: Bit Number Power of 2 Weight 3 3 8 2 2 4 1 1 2 0 0 1
Hence, the 1541's GCR lookup table contains just sixteen 4-bit nybble equivalencies: Hexadecimal $0(0) $1 (1) $2(2) $3(3) $4(4) $5(5) $6(6) $7(7) $8(8) $9(9) $A (10) $B (11) $C (12) $D (13) $E (14) $F (15) Binary 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 GCR 01010 01011 10010 10011 01110 01111 10110 10111 01001 11001 11010 11011 01101 11101 11110 10101
Using the binary to GCR lookup table above, let's walk through the necessary steps to convert a $12 (18) to GCR form.
114
STEP 1. Hexadecimal to Binary Conversion $12 (18) = 00010010 STEP 2. High Nybble to GCR Conversion 0001xxxx = $1 (1) = 01011 STEP 3. Low Nybble to GCR Conversion xxxx0010 = $2 (2) = 10010 STEP 4. GCR Concatenation 01011 + 10010 = 0101110010 Two things should stand out when scrutinizing the 1541's binary to GCR lookup table. 1. No combination of any two 5-bit GCR bytes will ever yield 10 consecutive on bits (ls) which is used as the sync mark. Binary to GCR conversion eliminates all likelihood that a permutation of normal data bytes can ever be mistaken by the read/write electronics for a sync mark. 2. No more than two consecutive off bits (0s) appear in any given 10-bit GCR byte or combination of GCR bytes. This latter constraint was imposed for accuracy when clocking bits back into the 1541 during a read. (See Chapter 9 for additional information.) This brings us full circle to what actually differentiates a sync mark from a normal data byte. Simply put, a sync mark is 10 or more on bits (ls) recorded in succession. Only one normal data byte, an $FF (%11111111), can even begin to fill the shoes of a sync mark. During normal write mode, however, an $FF would take the following GCR form, 1010110101. Enter sync mode. When the 1541 writes an $FF in sync mode no binary to GCR conversion is done. A single $FF is only eight consecutive on bits and falls short of the ten consecutive on bits needed to create a sync character. To remedy this, Commodore writes five consecutive 8-bit $FFs to disk. This records 40 on bits (ls) in succession. the overkill is intentional on the DOS's part. Commodore is trying to guarantee that the 1541 will never have any trouble finding a sync mark during subsequent reads/writes to a diskette. Four 8-bit data bytes are converted to four 10-bit GCR bytes at a time by the 1541 DOS. RAM is only an 8-bit storage device though. This hardware limitation prevents a 10-bit GCR byte from being stored in a single memory location. Four 10-bit GCR bytes total 40 bits a number evenly divisible by our overriding 8-bit constraint. Commodore subdivides the 40 GCR bits into five 8-bit bytes to solve this dilemma. This explains why four 8-bit data bytes are converted to GCR form at a time. The following step by step example demonstrates how this bit manipulation is performed by the DOS. STEP 1. Four 8-bit Data Bytes $08 $10 $00 $12
115
STEP 2. Hexadecimal to Binary Conversion 1. Binary Equivalents $08 $10 $00 $12 00001000 00010000 00000000 00010010 STEP 3. Binary to GCR Conversion 1. Four 8-bit Data Bytes 00001000 00010000 00000000 00010010 2. High and Low Nybbles 0000 1000 0001 0000 0000 0000 0001 0010 3. High and Low Nybble GCR Equivalents 01010 01001 01011 01010 01010 01010 01011 10010 4. Four 10-bit GCR Bytes 0101001001 0101101010 0101001010 0101110010 STEP 4. 10-bit GCR to 8-bit GCR Conversion 1. Concatenate Four 10-bit GCR Bytes 0101001001010110101001010010100101110010 2. Five 8-bit Subdivisions 01010010 01010110 10100101 00101001 01110010 STEP 5. Binary to Hexadecimal Conversion 1. Hexadecimal Equivalents 01010010 01010110 10100101 00101001 01110010 $52 $56 $A5 $29 $72 STEP 6. Four 8-bit Data Bytes are Recorded as Five 8-bit GCR Bytes $08 $10 $00 $12 are recorded as $52 $56 $A5 $29 $72
Four normal 8-bit bytes are always written to diskette as five 8-bit GCR bytes by the DOS. The 1541 converts these same five 8-bit GCR bytes back to four normal 8-bit bytes during a read. The steps outlined above still apply but they are performed in the reverse order. (The appendix contains various mathematical conversion routines for your use.) In light of the above discussion, we need to recalculate the number of bytes that are actually recorded in a sector. We stated in Chapter 3 that a header block was comprised of eight 8-bit bytes excluding the header gap. This is recorded on the diskette as ten 8-bit GCR bytes. The formula for determining the actual number of bytes that are recorded is: Number of 8-bit GCR Bytes Recorded = (Number of 8-bit Data Bytes/4) * 5
116
Similarly, a data block consisting of 260 8-bit bytes is written to disk as 325 8-bit GCR bytes. Lest we forget, each sync mark is five 8-bit bytes. We must also remember to add in the header gap which is held constant at eight bytes. (Header gap bytes ($55) are not converted to GCR form and serve only to separate the header block from the data block.) An entire sector is recorded as 353 bytes not 256 data bytes. Data Bytes Sync Character ($FF) Header Block Header Gap ($55) Sync Character ($FF) Data Block * No binary to GCR conversion. We deliberately excluded the inter-sector (tail) gap in calculating the number of bytes in a given sector. Why? Because the tail gap is never referenced again by the DOS once formatting is complete. During formatting the Floppy Disk Controller (FDC) erases a track by writing 10240 overlapping 8-bit $FFs. Once a track has been erased the FDC writes 2400 8-bit $FFs (%11111111) followed by 2400 8-bit $55s (%01010101). The intent is to wrap around the circumference of the track with a clearly discernable on/off pattern ofbytes. The FDC then counts to see how many sync ($FF) and nonsync ($55) bytes were actually written to the track. From this count the FDC subtracts the total number of bytes that the entire range of sectors in a given zone will use. The remainder is then divided by the number of sectors in that zone to determine the size of the tail gap. The algorithm is analogous to cutting a pie. The tail gap varies not only between tracks due to a decrease in both circumference and the sector range but between disk drives as well, due to varying motor speeds. A stopgap measure is incorporated into the algorithm for the latter reason. If a tail gap is not computed to be at least four bytes in length formatting will fail and an error will be reported. In general, the length of the tail gaps fall into the ranges tabled below: Zone 1 2 3 4 Tracks 1 - 17 18 - 24 25 - 30 31 - 35 Number of Sectors 21 19 18 17 Variable Tail Gap 4 -7 9 - 12 5-8 4 -8 5* 8 8* 5* 260 GCR Bytes 5 10 8 5 325
Note that the values given above do not apply to the highest numbered sector on a track. The gap between this sector and sector 0 is usually much longer. We have seen tail gaps in excess of 100 bytes here. Also note that a header block is never rewritten after formatting is complete. The data block of a sector, including the sync character, is completely rewritten every time data is written to that sector. The eight byte header gap is counted off by the DOS to determine where to start writing the data block.
117
7.2 Checksums
The only remaining concern we have at this time is how we compute a checksum. Unlike tape storage where a program file is recorded twice in succession, data is recorded on diskette only once. In other words, there is no cyclic redundancy. Checksum comes to the rescue. A single byte checksum or hashtotal is used by the DOS to determine whether or not an error occurred during a read of a header block or a data block. A checksum is derived by Exclusive-ORing (EOR) bytes together. Two bytes are EORed together at one time by comparing their respective bits. The four possible EOR bit combinations are shown in the following truth table. EOR Truth Table 0 EOR 0 = 0 0 EOR 1 = 1 1 EOR 0 = 1 1 EOR 1 = 0 A header block checksum is the EOR of: the sector number, the track number, the ID LO, and the ID HI. (These four bytes serve to differentiate sectors from one another on a diskette.) A data block checksum is the EOR of all 256 8-bit data bytes in a sector. Recall that a data block normally consists of a forward track and sector pointer plus 254 data bytes. Please note that bytes are EORed by the DOS prior to their GCR conversion. The following example demonstrates how a header block checksum is calculated. The algorithm for calculating a data block checksum is identical, only longer. Hexadecimal Sector Number Track Number ID LO ID HI
$00 (0) $12 (18)
STEP 1. Initialization EOR $00 (0) With Sector Number $00 = 00000000 Sector Number ($00) = 00000000 00000000 STEP 2. EOR With Track Number 00000000 Track Number ($12) = 00010010 00010010
118
STEP 3. EOR With ID LO 00010010 ID LO ($58) = 01011000 01001010 STEP 4. EOR With ID HI 01001010 ID HI ($5A) = 01011010 00010000 STEP 5. Binary to Hexadecimal Conversion 00010000
$10 (16)
The checksum for $00, $12, $58, and $5A is thus $10 (16). This checksum just happens to be the header block checksum for track 18, sector 0 on the 1541TEST/DEMO. In addition, the binary to GCR conversion tour presented earlier was for the first four bytes ($08 $10 $00 $12) of the same header block.
Error Message No Sync Character Header Block Not Found Checksum Error in Header Block Disk ID Mismatch Header Block Not Found Data Block Not Present Checksum Error in Data Block OK
119
WRITE ERRORS FDC Job Request WRITE WRITE WRITE WRITE VERIFY FDC Error Code $0B (11)
$08 (8)
IP Error Code 73 29 26 25 0
Error Message DOS Mismatch Disk ID Mismatch Write Protect On Write-Verify Error OK
Each error is described in greater detail below. 21 READ ERROR (NO SYNC CHARACTER) The FDC could not find a sync mark (10 or more consecutive on bits) on a given track within a prescribed 20 millisecond time limit. A time out has occurred. 20 READ ERROR (HEADER BLOCK NOT FOUND) The FDC could not find a GCR header block identifier ($52) after 90 attempts. The FDC did a seek to a track and found a sync character. The FDC then read the first GCR byte immediately following it. This GCR byte was compared against a GCR $52 ($08). The comparison failed and the try counter was decremented. The FDC waited for another sync character and tried again. Ninety attempts were made. 27 READ ERROR (CHECKSUM ERROR IN HEADER BLOCK) The FDC found a header block on that track. This header block was read into RAM and the GCR bytes were converted back to their original binary form. The FDC then EORed the sector number, the track number, the ID LO, and the ID HI together. This independent checksum was EORed against the actual checksum found in the header block itself. If the result of the EOR was not equal zero, the checksums were not equal. The comparison failed and the FDC returned a $09 to the error handler. 29 READ ERROR (DISK ID MISMATCH) The IDs recorded in the header block found above did not match the master copy of the disk id's stored in $0012 and $0013. These zero page memory addresses are normally updated from track 18 during initialization of a diskette. Note that they also can be updated by a seek to a track from the job queue. 20 READ ERROR (HEADER BLOCK NOT FOUND) A GCR image of the header was created using the sector number, the track number, and the master disk IDs. The FDC attempted to find a header on this track that matched the GCR image in RAM for that sector. Ninety attempts were made before this error was reported.
120
22 READ ERROR (DATA BLOCK NOT PRESENT) The header block for a given track and sector passed the previous five tests with flying colors. The FDC found the data block sync mark and read the next 325 GCR bytes into RAM. These GCR bytes were converted back into 260 8-bit binary bytes. The first decoded 8-bit byte was compared against a preset data block identifier at $0047 and failed to match. Note this zero page memory address normally contains a $07. 23 READ ERROR (CHECKSUM ERROR IN DATA BLOCK) An independent checksum was calculated for the 256 byte data block converted above. This checksum did not match the actual checksum read from the diskette.
00, OK,OO,OO
Nothing wrong here. 73 DOS MISMATCH (CBM DOS V2.6 1541) An attempt was made to write to a diskette with a non-compatible format. The DOS version stored at location $0101 was not a $41. This memory address is normally updated during initialization by reading byte 2 from track 18, sector 0. 29 READ ERROR (DISK ID MISMATCH) Same as 29 READ ERROR above but conflicting id's were found during a write attempt rather than a read. Repeated occurrance of this error on a standard diskette is indicative of a seating problem or a slow-burning alignment problem. 26 WRITE PROTECT ON An attempt was made to write to a diskette while the write protect switch is depressed. Remove the write protect tab from the write protect notch. 25 WRITE-VERIFY ERROR The contents of the data just written to a sector did not match the data in RAM when they were read back. This was probably caused by a flaw on the surface of the diskette. The end result was an unclosed file. Validate the diskette to decorrupt the BAM. (See Chapter 2.)
00, OK,OO,OO
Looking good.
121
These four programs tend to complement one another quite well in actual use. Their uses and limitations are discussed below. INTERROGATE FORMATTING ID'S returns the embedded disk ID for each track using a SEEK. Recall that working the job queue prevents the dreaded BUMP. A seek to a track is deemed successful by the FDC if at least one intact sector can be found. The header of said sector is stored in zero page from $0016-$001A. The ASCII equivalents of the ID HI ($0016) and ID L0 ($0017) are read and printed to the CRT if the SEEK was good. At a glance one can determine if a protected diskette has a blown track or if it has been formatted with multiple ID's. This latter scheme is less commonly used to date. This program will not report the integrity of each individual sector. We have other routines for that task. There is one severe drawback to this program as it stands. Occasionally the FDC gets hung up on a track. The SEEK may continue to attempt to find a sync mark without timing out. (You must power off the 1541 to recover from this situation.) Experimentation in interrogating unformatted diskettes has produced the same effect. We surmise that the track in question was passed over during high-speed duplication. The FDC may in fact be homing in on a residual bit pattern left over from the manufacturer's certification process. The program has a built-in fail-safe mechanism for this very reason. Please take note: Lines 110-140 establish an active track array. All tracks are presumed active at the onset 0ine 130). Line 240 tests the integrity of the track prior to a seek. If a track is inactive (its flag equals 0) the track is bypassed and the program will work from start to finish. Should the need arise simply patch in a line that reads: 145 T(track number)=0 145 T(17)=0, for example. If it's any comfort at all, a loader cannot check the integrity of said track either. The sole function of such a track is to discourage prying eyes.
122
INTERROGATE A TRACK scans a single track using thejob queue. The track is found with a SEEK and then the integrity of each sector is verified with a READ. IP error codes are returned to the screen. No BUMP occurs. The routine may occasionally provide erroneous information. This is a major shortcoming of a READ from the queue. Certain errors are returned clean as a whistle (22, 23, 27). A partially formatted track (mid-track 21 error) or a smattering of 20 errors tend to throw the FDC into an absolute tizzy. Make note of this. Repeated runs of the same track often return a different error pattern. Errors tend to accumulate when a BUMP is overridden. Solution? See the following paragraph. SHAKE, RATTLE, AND ROLL also scans a single track by using a U1 command rather than a direct READ from the job queue. The track is still found by a SEEK, however, to prevent 29 errors in the event that multiple formatting played a part in the protection scheme. A 29 error is not an error per se. It is merely a stumbling block. A U1 without a SEEK to a multiple-formatted diskette will report a DISK ID MISMATCH. Information can be stored on a track with a different ID. A loader will retrieve it by the same method we're using here. Errors will force a BUMP so use discretion. Please note that a full track of 21 errors, 23 errors, or 27 errors does not need to be read with this routine. After you analyze a track, write the errors down and file your notes away for archival needs. Your 1541 will love you for it. INTERROGATE A DISKETTE is the lazy man's routine. It scans an entire diskette reporting only bad sectors to the screen. The program is essentially INTERROGATE A TRACK in a loop. Note that you may have to patch around a track to map the entire diskette. See the example patch above.
Historically speaking, the 21 error (full track) and the 29 error appeared on the scene concurrently. At the present time, a full track 21 error and a single sector 23 error are the predominant errors used to corrupt a diskette. These same two errors are also the easiest to duplicate. The last entry, partial formatting of a track, is the new kid on the block.
123
The following 13 programs can be used to duplicate a multitude of errors on a diskette. They are: File Name 21 ERROR DESTROY A SECTOR 23A ERROR 23B ERROR 23M ERROR 20 ERROR 20M ERROR 27M ERROR 22A ERROR 22B ERROR FORMAT A DISKETTE BACKUP COPY * Creates an exact duplicate of a bad sector. Source listings for the machine language routines in these programs are included as a courtesy to the more advanced reader. The BASIC drivers themselves are nondescript and will not be explained in depth. It is assummed that the reader has digested the sections on beginning and intermediate direct-access programming in Chapters 5 and 6. Algorithms will be briefly mentioned along with any new techniques and/or limitations that apply. Error Number 21 20, 21 23 23 23 20 20 27 22 22 29 Error Range FULL TRACK SINGLE SECTOR SINGLE SECTOR *SINGLE SECTOR FULL TRACK SINGLE SECTOR FULL TRACK FULL TRACK SINGLE SECTOR *SINGLE SECTOR MULTIPLE FORMATTING ID'S SINGLE DRIVE BACKUP SINGLE FILE COPY
;Q*
124
190 I N P U T # 1 5 , E N * , E M * , E T * , E S * 200 I F E N $ = " 0 0 " 6 0 T 0 2 5 0 210 PRINT"{DOWN>"EN*", "EM$","ET$","ES* 220 CL0SE15 230 END 240 REM SEEK 250 J0B=176 260 G0SUB400 270 F0RI=0T023 280 READD 290 D*=D*+CHR*<D> 300 NEXTI 310 PRINT#15,"MW"CHR$(0)CHR$(4)CHR$(24) D* 320 REM EXECUTE 330 PRINT"CDOWN>{RVS>DESTROYING<ROFF> TR ACK"5T 340 J0B=224 350 G0SUB400 360 PRINT"tDOWN>DONE!" 370 CL0SE15 380 END 390 REM JOB QUEUE 400 TRY=0 410 P R I N T # 1 5 , " M - W " C H R * ( 8 ) C H R * ( 0 ) C H R $ < 2 > C HR*<T>CHR$<0> 420 PRINT#15,"M-W"CHR*<1>CHR*<0>CHR*<1>C HR*<JOB> 430 TRY=TRY+1 440 P R I N T # 1 5 , " M - R " C H R $ < 1 ) C H R * ( 0 ) 450 GET#15,E$ 460 IFE*=""THENE*=CHR* < 0) 470 E=ASC<E$> 480 IFTRY=500G0T0510 490 I F E >127G0T0430 500 RETURN 510 CL0SE15 520 PRINT"{DOWN><RVS>FAILEDCROFF>" 530 END 540 REM 21 ERROR 550 DATA 32,163,253.169, 48, 1, 85,141, 1, 28 32
32,201,253,
76,105,249,234
125
*FDA3 > ENABLE WRITE #*55 5 NQN SYNC BYTE *1C01 #*FF #$48 *FDC9 5 WRITE 18432 NON
Full Track 21 Error Source Annotation This routine borrows from FORMT ($FAC7). Prior to formatting a track, the FDC erases it with sync marks ($FDA3). Experimentation has shown that an RTS from this ROM entry point would create a track of all 20 errors. Thus we are forced to trace the FORMT routine a little farther. The subroutine WRTNUM ($FDC3) writes either sync or nonsync bytes. By entering six bytes into this routine we can establish the number ofbytes it writes. A JSR to $FE00 is necessary to re-enable read mode. Otherwise the write head is left on and it will erase everything in its path. Note that we LDA #$01, the FDC error code for OK, and JMP to the error handler at $F969 to exit.
126
160 NS=20+2* < T >17) + < T >24) + (T >30 > 170 IFS< OORS >NSTHENEND 180 INPUT"{DOWN>ARE YOU SURE Y{LEFT 3>" ;Q$ 190 IFQ$< >"Y"THEMEND 200 0 P E N 1 5 , 8 , 1 5 210 P R I N T # 1 5 , " I 0 " 220 I N P U T # 1 5 , E N * , E M $ , E T $ , E S * 230 IFEN$="OO"G0T0280 240 P R I N T " C D O W N V ' E N * " , " E M * " , " E T * " , " E S $ 250 CL0SE15 260 END 270 REM SEEK 280 IFS=OTHENS=NS:B0T0300 290 S = S - 1 300 J0B=176 310 G0SUB570 320 REM READ 330 J0B=128 340 G0SUB570 350 F0RJ=0T07 360 F0RI=0T07 370 READD 380 D$(J)=D$<J)+CHR$<D> 390 NEXTI 400 NEXTJ 410 1=0 420 F0RJ=0T07 430 P R I N T # 1 5 , " M - W " C H R $ ( I ) C H R * ( 5 ) C H R $ ( 8 ) D $(J) 440 I = I + 8 450 NEXTJ 460 REM EXECUTE 470 PRINT#15,"MW"CHR$ <2> CHR$ <0)CHR$(1)C HR$(224) 480 P R I N T # 1 5 , " M - R " C H R * ( 2 ) C H R $ ( 0 ) 490 GET#15,E$ 500 IFE$=""THENE*=CHR$(0) 510 E=ASC(E*> 520 I F E >127G0T0480 530 CL0SE15 540 PRINT"xDOWNJDONE!" 550 END 560 REM JOB QUEUE 570 TRY=0 580 P R I N T # 1 5 , " M - W " C H R * ( 8 ) C H R $ ( 0 ) C H R * < 4 > C HR$<T>CHR*<S>CHR*(T)CHR*<S> 590 PRINT#15,"MW"CHR$(1)CHR$(0)CHR$(1)C HR$<JOB) 600 TRY=TRY+1
127
610 620 630 640 650 660 670 680 690 700 710 720 730 740 750 760 770 780 790
PRINT#15,"M-R"CHR*<1>CHR*<0> GET#15,E* IFE*=""THENE*=CHR*<0> E=ASC<E*> IFTRY=500G0T0680 I F E >127G0T0600 IFE=1THENRETURN CL0SE15 PRINT"CDDWN> <RVS>FAILED<ROFF>" END REM DESTROY A SECTOR DATA 3 2 , 1 6 , 2 4 5 , 3 2 , 8 6 , 2 4 5 , 1 6 2 , 0 DATA 8 0 , 2 5 4 , 1 8 4 , 2 0 2 , 2 0 8 , 2 5 0 , 1 6 2 , 69 DATA 8 0 , 2 5 4 , 1 8 4 , 2 0 2 , 2 0 8 , 2 5 0 , 1 6 9 , 2 5 5 DATA141, 3 , 2 8 , 1 7 3 , 12. 2 8 , 4 1 , 31 DATA 9 , 1 9 2 , 1 4 1 , 12, 2 8 , 1 6 2 , 0,169 DATA 8 5 , 8 0 , 2 5 4 , 1 8 4 , 1 4 1 , 1, 2 8 , 2 0 2 DATA208,247, 8 0 , 2 5 4 , 3 2 , 0,254,169 DATA 1, 76,105,249,234,234,234,234
220 ;
128
380 390 400 410 420 430 440 450 460 470 480 490 500 510 520 530 540 550 560 570
STA *1C03 LDA $lCOC; ENABLE WRITE MODE AND # $ l F ORA #$C0 STA *lCOC ; LDX # * 0 0 LDA # * 5 5 WRITE1 BVC WRITE1 CLV STA *1C01 DEX BNE WRITE1 ; WRITE2 BVC WRITE2 ; JSR *FEOO ; ENABLE READ MODE ; LDA # * 0 1 JMP $F969
Single Sector 21 Error Source Annotation This routine finds the preceding sector and syncs up to its data block (lines 200-210). Lines 250-350 wait out 325 GCR bytes. We flip to write in lines 370-420 and write out 256 non-sync bytes. This overwrites both sync marks of the sector that was input. This routine will create a 20 error on a single sector as it stands. By serendipity, it has a unique side effect. If two consecutive sectors are destroyed we get a 21 error on both of them. The FDC times out trying to find one or the other or both. Caution must be used when spanning a sector range. To duplicate the following scheme we must destroy sector 0 f!rst followed by sectors 20, 19, and 18 respectively.
Sector
Error Number 21 OK 21
o
1 - 17
18 - 20
Repeat. This routine will not create a 21 error on a single sector per se. Two consecutive sectors must be destroyed.
129
130
540 REM JOB QUEUE 550 TRY=0 560 PRINT#15,"M-W"CHR$<8)CHR$ <0>CHR$(4)C HR*(T)CHR*(S)CHR*(T)CHR*(S) 570 PRINT#15,"M-W"CHR$(1)CHR$ <0)CHR$(1> C HR*<JOB> 580 TRY=TRY+1 590 P R I N T # 1 5 , " M - R " C H R $ ( 1 ) C H R $ ( 0 ) 600 GET#15,E$ 610 IFE$=""THENE$=CHR$(0) 620 E=ASC<E$> 630 IFTRY=5OOGOTO66O 640 I F E >127G0T0580 650 RETURN 660 CL0SE15 670 PRINT"CDOWN>{RVS3FAILED{ROFF>" 680 END 690 REM 23 ERROR 700 DATA 169, 4,133, 58, 8, 49,165, 58,170.232 32, 16
32,143,247,
9,192,141, 5,141, 1,
28,184
80,254,184,202,208,250,160,187 0, 1, 80,254,184,141, 0, 4, 1 80
28,200,208,244,1B5, 1,
28,200,208,244 5,133
0,254,169,
76,105,249,234,234
131
160 .OPT P , 0 2 170 ; 180 * = * 0 5 0 0 190 ; 200 LDA #*04 210 STA * 3 1 220 ; 230 LDA *3A 240 TAX 250 INX CHECKSUM 260 TXA 270 STA *3A
INCREMENT
280 ;
; ;
310 ; 320 LDX # * 0 8 330 WAITGAP BVC WAITGAP i WAIT OUT G AP 340 CLV 350 DEX 360 BNE WAITGAP 370 ; 380 LDA # * F F ; ENABLE WRI TE 390 STA *1C03 400 LDA *lCOC 410 AND # * l F 420 ORA #*C0 430 STA *lCOC 440 LDA # * F F 450 LDX # * 0 5 460 STA *1C01 470 CLV 480 WRITESYNC BVC WRITESYNC 490 CLV 500 DEX 510 BNE WRITESYNC 520 ; 530 LDY #$BB 540 OVERFLOW LDA * 0 1 0 0 , Y 5 WRITE OUT OVERFLOW BUFFER 550 WAIT1 BVC WAIT1 560 CLV 570 STA *1C01 580 INY 590 BNE OVERFLOW
132
600 BUFFER LDA * 0 4 0 0 , Y BUFFER 610 WAIT2 BVC WAIT2 620 CLV 630 STA *1C01 640 INY 650 BNE BUFFER 660 WAIT3 BVC WAIT3 670 ; 680 JSR *FEOO D 690 ; 700 LDA # * 0 5 710 STA 31 720 LDA # * 0 1 730 JMP * F 9 6 9
WRITE OUT
5 ENABLE REA
Single Sector 23 Error Source Annotation This routine borrows from WRIGHT ($F56E). Our entry point is 12 bytes into the routine. This bypasses the write protect test and the computation of the checksum. The driver routine reads the sector into $0400-$04FF. Lines 200-210 of the source listing set the indirect buffer pointer to this workspace. The checksum is next incremented at $003A. Buffer number 1 is converted to GCR form. Recall that 260 data bytes are converted into 325 8-bit GCR bytes. More than one buffer is used to store the GCR image. The first 69 GCR bytes are stored in an overflow buffer at $OlBB-$OlFF. The remaining 256 bytes are found at $0400-$04FF. We sync up to the appropriate sector in line 300, count off the eight byte header gap, and flip to write mode. Five $FFs are then written to disk (the sync mark) followed first by the overflow buffer and then the regular buffer. We restore the indirect buffer pointer at $0031 to a $05 andjump to the error handler with a $01 in hand.
133
160 NS=20+2*(T>17) + (T>24) + (T>30) 170 IFS< OORS >NSTHENEND 180 INPUT"{DOWN>ARE YOU SURE YtLEFT
190 IFQ*< >"Y"THENEND 200 0 P E N 1 5 , 8 , 1 5 210 P R I N T # 1 5 . " I 0 " 220 I N P U T # 1 5 , E N * , E M * , E T * , E S * 230 I F E N * = " 0 0 " G 0 T 0 2 8 0 240 PRINT"iDOWN>"EN*", "EM*","ET*","ES* 250 CL0SE15 260 END 270 REM SEEK 280 J0B=176 290 G0SUB650 300 REM READ 310 J0B=128 320 G0SUB650 330 CL0SE15 340 PRINT"{DOWN>INSERT CLONE IN DRIVE" 350 PRINT"tDOWN>PRESS CRVS> RETURN ROFF> TO CONTINUE" 360 G E T C * : I F C * = " " T H E N 3 6 0 370 I F C * < > C H R * ( 1 3 ) G 0 T 0 3 6 0 380 PRINT"OK" 390 0 P E N 1 5 , 8 , 1 5 400 REM SEEK 410 J0B=176 420 G0SUB650 430 F0RJ=0T010 440 F0RI=0T07 450 READD 460 D * < J ) = D * ( J ) + C H R * ( D ) 470 NEXTI 480 NEXTJ 490 1=0 500 F0RJ=0T010 510 P R I N T # 1 5 . " M - W " C H R * ( I ) C H R * ( 5 ) C H R * ( 8 ) D *<J) 520 I = I + 8 530 NEXTJ 540 REM EXECUTE 550 P R I N T # 1 5 , " M - W " C H R * ( 2 ) C H R * ( 0 ) C H R * ( 1 ) C HR*(224) 560 PRINT#15,"MR"CHR*(2)CHR*(0) 570 G E T # 1 5 , E * 580 I F E * = " " T H E N E * = C H R * ( 0 ) 590 E=ASC(E*) 600 I F E >127G0T0560 610 CL0SE15
;Q*
3>"
134
620 PRINT"CD0WN>D0NE!" 630 END 640 REM JOB QUEUE 650 TRY=0 660 P R I N T # 1 5 , " M - W " C H R $ ( 8 ) C H R * ( 0 ) C H R * < 4 > C HR* <T)CHR*<S>CHR*(T> CHR*<S> 670 P R I N T # 1 5 , " M - W " C H R $ ( 1 ) C H R $ ( 0 ) C H R $ ( 1 ) C HR*(JOB> 680 TRY=TRY+1 690 PRINT#15,"MR"CHR$(1)CHR$(0) 700 G E T # 1 5 , E * 710 IFE$=""THENE*=CHR*(0) 720 E=ASC(E*> 730 IFTRY=500GOT0760 740 I F E >127G0T0680 750 RETURN 760 PRINT"{DOWN>FAILED" 770 CL0SE15 780 END 790 REM DUPLICATE A SECTOR 800 DATA 169, 4 , 1 3 3 , 49, 3 2 , 1 4 3 , 2 4 7 , 32 810 DATA 16,245,162, 8, 80,254,184,202 3, 28,173 12 28
820 DATA 2 0 8 , 2 5 0 , 1 6 9 , 2 5 5 , 1 4 1 , 830 DATA 840 DATA 12, 28, 41, 31,
9,192,141, 5,141, 1,
28,169,255,162,
80,254,184,202,208,250,160 0, 1, 80,254,184,141 0, 4
28,200,208,244,185, 1,
28,200,208 5
0,254,169,
76,105,249,234
135
220 ;
250 ; 260 LDX #$08 270 WAITGAP BVC WAITGAP AP 280 CLV 290 DEX 300 BNE WAITGAP 310 ; 320 LDA #$FF TE 330 STA $1C03 340 LDA $lCOC 350 AND # $ l F 360 ORA #$C0 370 STA $lCOC 380 LDA #$FF 390 LDX #$05 400 STA $1C01 410 CLV 420 WRITESYNC BVC WRITESYNC 430 CLV 440 DEX 450 BNE WRITESYNC 460 ; 470 LDY #$BB 480 OVERFLOW LDA $ 0 1 0 0 , Y OVERFLOW BUFFER 490 WAIT1 BVC WAIT1 500 CLV 510 STA $1C01 520 INY 530 BNE OVERFLOW 540 BUFFER LDA $ 0 4 0 0 , Y BUFFER 550 WAIT2 BVC WAIT2 560 CLV 570 STA $1C01 580 INY 590 BNE BUFFER
WAIT OUT G
ENABLE WRI
WRITE OUT
WRITE OUT
136
WAIT3 BVC WAIT3 ; JSR *FE00 ; LDA STA LDA JMP #*05 $31 #01 *F969
ENABLE REA
Duplicate a Single Sector 23 Error Source Annotation Identical to the 23A.PAL file with one exception. The checksum is left intact after a corrupted data block is read from the master using the job queue. The sector is stored at $0400-$04FF and the checksum at $003A. The checksum is not recalculated or incremented. The entire sector and its checksum are rewritten to the clone.
1541"
PRINT"{DOWN>INSERT CLONE IN DRIVE" INPUT" CDOWN3-DESTROY TRACK";T IFT<1ORT>35THENEND INPUT"{DOWN>ARE YOU SURE Y<LEFT 3J" IFQ*<>"Y"THENEND OPEN15,8,15 PRINT#15,"I0" INPUT#15,EN*,EM*,ET*,ES* IFEN*="00"G0T0260 PRINT"<DOWN>"EN*", "EM*","ET*","ES* CLOSE15 END REM SEEK J0B=176 G0SUB580 NS=20+2*(T>17)+(T>24)+(T>30) FORS=OTONS REM READ J0B=128
137
320 GDSUB5B0 330 IFS>0G0T0460 340 F0RJ=0T011 350 F0RI=0T07 360 READD 370 D*(J)=D*<J>+CHR*(D> 380 NEXTI 390 NEXTJ 400 1=0 410 F0RJ=0T011 420 PRINT#15,"M-W"CHR$(I)CHR*<5)CHR*(8)D *<J> 430 I=I+8 440 NEXTJ 450 REM EXECUTE 460 PRINT"CHOME>{DOWN 8J<RVS>DESTR0YINGC ROFF> TRACK"T"- SECTOR"S 470 PRINT#15,"MW"CHR$ <2)CHR*<0>CHR*(1)C HR*<224> 480 PRINT#15,"M-R"CHR$<2)CHR*<0> 490 GET#15,E$ 500 IFE*=""THENE*=CHR*(0) 510 E=ASC<E*> 520 IFE >127G0T0480 530 NEXTS 540 CL0SE15 550 PRINT"CHOME> {DOWN 8>D0NE!
II
560 END 570 REM JOB QUEUE 580 TRY=0 590 PRINT#15. "M-W"CHR<M8>CHR*(0>CHR$<4)C HR* < T)CHR*(S)CHR*(T)CHR*(S) 600 PRINT#15,"M-W"CHR*<1>CHR*<0>CHR*(1)C HR$<JOB) 610 TRY=TRY+1 620 PRINT#15,"M-R"CHR*(1)CHR*(0) 630 GET#15,E* 640 IFE$=""THENE*=CHR*(0) 650 E=ASC<E$> 660 IFTRY=500G0T0690 670 IFE >127G0T0610 680 RETURN 690 CLOSE15 700 PRINT"CDOWN> CRVS>FAILEDtROFFJ" 710 END 720 REM 23 ERROR 730 DATA 169, 4,133, 49,165, 58,170,232
138
8, 80,254,184,202,208 3, 28,173, 12
80,254,184,202,208,250,160,187 0, 1, 80,254,184,141, 0, 1
28,200,208,244,185,
4, 80
820 DATA 254,184,141, 830 DATA 840 DATA 80,254, 32, 49,169,
1,133,
2, 76,117,249
FULL TRACK 23 ERROR SOURCE LISTING 100 REM 23M.PAL 110 REM 120 0PEN2,8,2,"@0:23M.B,P,W" 130 REM 140 SYS40960 150 ; 160 .OPT P,02 170 ; 180 *= $0500 190 ; 200 LDA #*04 210 STA $31 220 ; 230 LDA $3A 240 TAX INCREMENT CHE 250 INX CKSUM 260 TXA 270 STA $3A 280 ; ; CONVERT TO GC 290 JSR $F78F R 5 FIND HEADER 300 JSR $F510 310 ; 320 LDX #*08 330 WAITGAP BVC WAITGAP ; WAIT OUT GAP 340 CLV 350 DEX
139
360 BNE WAITGAP 370 ; 380 LDA #FF ; ENABLE WRITE 390 STA 1C03 400 LDA lCOC 410 AND #lF 420 ORA #C0 430 STA lCOC 440 LDA #FF 450 LDX #05 460 STA 1C01 470 CLV 480 WRITESYNC BVC WRITESYNC 490 CLV 500 DEX 510 BNE WRITESYNC 520 ; 530 LDY #BB 540 OVERFLOW LDA $0100,Y ; WRITE OUT OVE RFLOW BUFFER 550 WAIT1 BVC WAIT1 560 CLV 570 STA $1C01 580 INY 590 BNE OVERFLOW 600 BUFFER LDA *0400,Y WRITE OUT BUF FER 610 WAIT2 BVC WAIT2 620 CLV 630 STA 1C01 640 INY 650 BNE BUFFER 660 WAIT3 BVC WAIT3 670 ; 680 JSR FE00 ; ENABLE READ 690 ; 700 LDA #05 710 STA 31 720 LDA #01 730 STA 02 740 JMP F975 Full Track 23 Error Source Annotation See the annotation for 23A.PAL. The BASIC driver loops to do all sectors on a given track.
140
;Q*
190 IFQ*<>"Y"THENEND 200 OPEN15,8,15 210 PRINT#15,"10" 220 INPUT#15,EN*,EM*,ET*,ES* 230 IFEN*="00"G0T0280 240 PRINT"<DOWN>"EN*", "EM*","ET*","ES* 250 CL0SE15 260 END 270 REM SEEK 280 IFS=OTHENS=NS:G0T0300 290 S=S-1 300 J0B=176 310 G0SUB570 320 REM READ 330 J0B=128 340 G0SUB570 350 F0RJ=0T011 360 F0RI=0T07 370 READD 380 D*<J>=D*<J)+CHR*<D> 390 NEXTI 400 NEXTJ 410 1=0 420 F0RJ=0T011 430 PRINT#15,"M-W"CHR* <I)CHR*(5)CHR*(8> D *(J) 440 I=I+8 450 NEXTJ 460 REM EXECUTE 470 PRINT#15,"M-W"CHR*(2)CHR* <0> CHR*(1)C HR*(224) 141
480 PRINT#15,"M-R"CHR$(2)CHR*(0) 490 GET#15,E4 500 IFE$=""THENE$=CHR$<0) 510 E=ASC(E$> 520 IFE >127G0T0480 530 CL0SE15 540 PRINT"{DOWN>DONE!" 550 END 560 REM JOB QUEUE 570 TRY=0 580 PRINT#15,"MW"CHR$ < 8)CHR$(0)CHR$(4)C HR$<T>CHR*(S)CHR*<T)CHR*(S) 590 PRINT315,"M-W"CHR*<1)CHR*<0)CHR$(1)C HR$<JOB) 600 TRY=TRY+1 610 PRINT#15,"M-R"CHR$(1)CHR$(0) 620 GET#15,E$ 630 IFE$=""THENE*=CHR$(0) 640 E=ASC<E*> 650 IFTRY=500G0T0680 660 IFE >127G0T0600 670 IFE=1THENRETURN 680 CL0SE15 690 PRINT"CDOWN>{RVS>FAILED<ROFF>" 700 END 710 REM 20 ERROR 720 DATA 32, 16,245, 32, 86,245,160, 20 12,136,136
730 DATA 165, 25,201, 18,144, 740 DATA 201, 25,144, 750 DATA
6,136,201, 31,144 6 0
780 DATA 133, 26, 32, 52,249, 32, 86,245 790 DATA 169,255,141, 800 DATA 810 DATA 820 DATA 830 DATA 41, 31, 3, 28,173, 12, 28
0,254,169,
142
SINGLE SECTOR 20 ERROR SOURCE LISTING 100 110 120 130 140 150 160 170 180 190 200 REM 20.PAL REM 0PEN2,8,2,"@0:20.B,P,W" REM SYS40960 ; .QPT P,02 ; *= $0500 ; JSR $F510 ; FIND HEADER BLOC ; FIND DATA BLOCK
210 JSR $F556 220 ; 230 LDY #$14 240 LDA $19 250 CMP #$12 260 BCC ZONE 270 DEY 280 DEY 290 CMP #$19 300 BCC ZONE 310 DEY 320 CMP #$lF 330 BCC ZONE 340 DEY 350 ZONE INC $18 360 CMP $18 370 BCC HEADER 380 BEQ HEADER 390 LDA #$00 400 STA $19 410 ; 420 HEADER LDA #$00 430 EOR $16 440 EOR $17 450 EOR $18 460 EOR $19 470 STA $lA 480 ; 490 JSR $F934 # IMAGE 500 JSR $F556 K 510 LDA #$FF 520 STA $1C03 530 LDA $lCOC 540 AND #$lF
143
550 560 570 580 590 600 610 620 630 640 650 660 670 680 690 700
ORA #$C0 STA *lCOC LDX #*00 WRITE LDA WAIT1 BVC CLV STA *1C01 INX CPX #*08 BNE WRITE WAIT 2 BVC 9 JSR $FEOO 5 LDA #*01 JMP $F969
; READ MODE
Single Sector 20 Error Source Annotation This routine represents a hatfhearted attempt to rewrite a header. It is dependent upon the preceding sector being intact. Lines 200-210 sync up to the preceding header and data block. Lines 230-400 calculate the next sector in the zone. A header image for the sector is created in RAM at $0024-$002C. We sync up one more time which positions us to the start of the header block we want to destroy. We flip to write mode and rewrite the header. We are coming in just a shade too slow and create enough noise at the end of the sync mark to destroy the actual header block identifier. (Tweaking the internal clock reveals that the header was completely rewritten.) If the tail gap was a constant length our task would be analogous to rewriting a sector where the FDC syncs up to a header block, reads the header, and counts off eight bytes. We would similarly sync up to a data block, count off 325 GCR bytes, then count off the tail gap, and flip to write mode. However, it is virtually impossible to gauge the length of the tail gap, so we're stuck. Rest assured, though. It still gets the job done.
;Q*
144
170 IFQ*< >"Y"THENEND 180 0PEN15,8,15 190 PRINT#15,"I0" 200 INPUT#15,EN*,EM*,ET*,ES* 210 IFEN*="00"G0T0260 220 PRINT"CDOWN>"EN*", "EM*","ET*","ES* 230 CLDSE15 240 END 250 REM SEEK 260 NS=20+2*(T>17)+(T>24)+(T>30) 270 S=NS 280 J0B=176 290 G0SUB580 300 F0RI=0T023 310 READD 320 D*=D*+CHR*(D) 330 I*=I*+CHR*(O) 340 NEXTI 350 PRINT#15,"M-W"CHR$ <0)CHR*(6)CHR*(24) D* 360 REM EXECUTE 370 PRINT"{DOWN>{RVS>DESTROYING{ROFFJ TR ACK";T 380 J0B=224 390 G0SUB580 400 PRINT#15,"M-W"CHR*(0)CHR*(6)CHR*(24)
I*
410 F0RJ=0T024 420 F0RI=0T07 430 READD 440 D*(J)=D*(J)+CHR*(D) 450 NEXTI 460 NEXTJ 470 1=0 480 F0RJ=0T024 490 PRINT#15,"MW"CHR*(I)CHR*(4)CHR*(8)D *(J) 500 I=I+8 510 NEXTJ 520 REM EXECUTE 530 PRINT#15,"M-E"CHR*(0)CHR*(4) 540 CLOSE15 550 PRINT"CDOWN>DONE!" 560 END 570 REM JOB QUEUE 580 TRY=0 590 PRINT#15,"M-W"CHR*(12)CHR*(0)CHR*(2) CHR*(T)CHR*(S) 600 PRINT#15,"M-W"CHR*(3)CHR*(0)CHR*(1)C HR*(JOB)
145
610 620 630 640 650 660 670 680 690 700 710 720 730
TRY=TRY+1 PRINT#15,"M-R"CHR*<3)CHR*(0> GET#15,E$ IFE$=""THENE$=CHR$(0) E=ASC(E*> IFTRY=500G0T0690 IFE >127G0T0610 RETURN CL0SE15 PRINT"CDOWN> CRVS>FAILEDCROFF>" END REM 21 ERROR DATA 32.163,253,169, 85,141, 1, 28
iJ J, 32 740 DATA 162 ,255 , 160, 48, 32,201 ,ncT 750 DATA 0 ,254 , 169, 1 , 76, 105,249 ,234
760 REM 20M ERROR 770 DATA169, 0, 133, 127, 166, 12, 134, 81 780 DATA134, 128, 166, 13, 232, 134, 67, 169 8, 141, 38 790 DATA 1, 141, 32, 6 169, 0 800 DATA 6, 169, 0, 141, 40. 6, 32, ~r 810 DATA193, 162, 0, 0, 169, 9, 157, rr v J 820 DATA232, 232, 173, 40, 6, 157. 0, 0, 830 DATA232, 165, 81, 157, 3, 232, 169 ~r O 840 DATA 3, 232, 157, 0, 0, 157, 0, 850 DATA232, 169, 15, 157, 0, 3, 232, 157 ^l X . 860 DATA 0, 3, 232, 169, 0, 93, 250, 'icr 4 O 870 DATA 93, ^*jl , 2, 93, 253 *- 9 93, 252, 880 DATA 2, 157, 249, 2, 238, 40, t>, 173 890 DATA 40, 6, 197, 67, 208, 189, 138, 72 900 DATA169, 75, 141, 0, 1, 138 5, 162, 0 910 DATA157, 0, 5, 232, 208, 250, 169, 920 DATA133, 48, 169, 3, 133, 49, 32, 48 930 DATA254, 104, 168, 136, 32, 229, 253, 32 5, 133, 49, 32, 233 940 DATA245, 253, 169, 950 DATA245, 133, 58, 32, 143, 247, 169, 35 960 DATA133, 81, 169, 169, 141, O, 6, 169 5, 141, 970 DATA 2 1, 6, 169, 133, 141, 980 DATA 6, 169, 49, 141, 3, 6, 169, 76 990 DATA141, 4, 6, 169, 170, 141, 6 5 1000 DATA169I ocrn: , 141, , 6 , 169,224 , 133 6. 1010 DATA 3,165, 3, 48,252, 76,148,193
146
FULL TRACK 20 ERROR SOURCE LISTING 100 110 120 130 140 150 160 170 180 190 REM 20M.PAL REM 0PEN2,8,2,"@0:20M.B,P,W" REM SYS40960 .OPT P,02
m
9 *= *G400
210 ;* INITIALIZATION *
220
200
270 280 290 300 310 320 330 340 350 360 370 380 390 400 410 420 430 440 450 460 470 480 490 500 510 520 530 540 550 560 570
LDA STA LDX STX STX LDX INX STX LDA STA LDA STA LDA STA
4
#$00 $7F $OC 51 $80 $OD $43 #$01 $0620 #$08 $0626 #$00 $0628
JSR $C100
*
;* CREATE HEADERS * LDX #$00 HEADER LDA #$09 STA $0300.X INX INX LDA $0628 STA $0300.X INX LDA $51 STA $0300,X INX LDA #$00 STA $0300,X INX STA $0300,X INX LDA #$OF HBID
CHECKSUM SECTOR
147
; GAP ; GAP
630 LDA #$00 ; COMPUTE CHECKSUM 640 EOR $02FA,X 650 EOR $02FB,X 660 EOR $02FC,X 670 EOR $02FD.X 680 STA $02F9,X 690 ; 700 INC $0628 710 LDA $0628 720 CMP $43 730 BNE HEADER 740 ; 750 TXA 760 PHA 770 ; 780 ;* CREATE DATA * 790 ; 800 LDA #$4B ; 1541 FORMAT 810 STA $0500 820 LDX #$01 ; 1541 FORMAT 830 TXA 840 DATA STA $0500,X 850 INX 860 BNE DATA 870 ; 880 ;* CONVERT TO GCR * 890 ; 900 LDA #$00 910 STA $30 920 LDA #$03 930 STA $31 940 JSR $FE30 950 PLA 960 TAY 970 DEY 980 JSR $FDE5 990 JSR $FDF5 1000 LDA #$05 1010 STA $31 1020 JSR $F5E9 1030 STA $3A 1040 JSR $F78F 1050 ; 1060 ;* JUMP INSTRUCTION * 1070 ;
148
108C) LDA #$23 1090 STA $51 llOO 5 1110 LDA #$A9 1120 STA $0600 1130 LDA #$05 1140 STA $0601 1150 LDA #$85 1160 STA $0602 1170 LDA #$31 1180 STA $0603 1190 LDA #$4C 1200 STA $0604 1210 LDA #$AA 1220 STA $0605 1230 LDA #$FC 1240 STA $0606 1250 5 1260 LDA #$E0 1270 STA $03 1280 5 1290 WAIT LDA $03 1300 BMI WAIT 1310 5 1320 JMP $C194 Full Track 20 Error Source Annotation This routine has a real surprise in store. Initialization in lines 220-290 sets the drive number to 0 ($007F) rather than rely on a default. The track is read from the header table location $000C and stored at $0051. (Recall that the driver set up the header table.) This memory location normally contains an $FF at powerup to let the drive know that formatting has not yet begun. We must reset it to the active track, or the drive will do a BUMP to track one to start the format. Similarly, we read the sector range from $000D, incremented this number to obtain a sector total for the track, and stored it at $0043. Line 300 is our try counter. Normally the drive makes 10 attempts to format a single track. We either get it right the first time or give up. (The driver erases the track as a safeguard.) We cannot allow the FDC to reattempt to format the track because it will bypass our machine language routine and re-enter the standard ROM routine. Lines 310-330 arbitrarily sets the tail gap to eight bytes in length. This avoids duplicating 245 bytes of code from $FBlD to $FC12. RAM is at a dire premium and we have neither the overhead nor the desire. Next we turn on the LED for cosmetic purposes Qine 370) and build our header table and a dummy data block Qines 410-860). We incremented the data block identifier in line 420. Binary to GCR conversion is done in lines 900-1040. Now for the jump instruction. First we reset the track number to 35 Qines 1080-1090) to let the FDC think that this is the last track of a normal format. Why? We will be passing control to a standard ROM routine in a minute and will let the FDC execute it. In other words, we are going to work the 6502 in both IP and FDC modes. Formatting is done as a single job; one
149
track at a time. When a track is formatted the FDC looks at $0051 to see if 35 tracks have been done. If not, it increments $0051 and does the next track as another discrete job. The IP is going to wait for the FDC to reformat the track and then retake control. We store the indirect buffer pointer to our data block buffer and a jump to $FCAA at $0600. This ensures that the data block will not be lost in the ensuing shuffle. We then set up the job queue for an execute of buffer number 3 ($0600) and away we go. The IP monitors the FDC while it is reformatting the track. (Not only that, but the FDC will verify the track to ensure that it was reformatted incorrectly!) When bit seven of the job code ($E0) goes low, the IP wrestles control away from the FDC and jumps to ENDCMD ($C194) to terminate the routine. DOS ist gut!
;Q$
170 180 190 200 210 220 230 240 250 260 270 280 290 300 310 320 330 340 350 D* 360
150
I*
410 F0RJ=0T025 420 FORI=0T07 430 READD 440 D*<J>=D*<J)+CHR*(D> 450 NEXTI 460 NEXTJ 470 1=0 480 F0RJ=0T025 490 PRINT#15,"MW"CHR$(I)CHR$(4)CHR$ <8)D $<J> 500 I=I+8 510 NEXTJ 520 REM EXECUTE 530 PRINT#15."M-E"CHR*<0>CHR*<4> 540 CL0SE15 550 PRINT"<DOWN>DONE!" 560 END 570 REM JOB QUEUE 580 TRY=0 590 PRINT#15,"M-W"CHR*<12)CHR*<0)CHR*<2> CHR*<T>CHR*<S) 600 PRINT#15,"M-W"CHR*<3>CHR*(0>CHR*<l)C HR* <JOB) 610 TRY=TRY+1 620 PRINT#15,"M-R"CHR*(3)CHR*(0) 630 GET#15,E* 640 IFE*=""THENE*=CHR*(0 > 650 E=ASC(E*> 660 IFTRY=50060T0690 670 IFE >127G0T0610 680 RETURN 690 CLOSE15 700 PRINT"CDOWN>{RVS>FAILED<ROFFJ" 710 END 720 REM 21 ERROR 730 DATA 32,163,253,169, 85,141, 1, 28
760 REM 27M ERROR 770 DATA169, 0,133,127,166, 12,134, 81 780 DATA134,128,166, 13,232,134, 67,169
151
790 DATA 1,141, 32, 6,169, 8,141, 38 800 DATA 6,169, 0,141, 40, 6, 32, 0 810 DATA193,162, 0,169, 8,157, 0, 3 820 DATA232,232,173, 40, 6,157, 0, 3 830 DATA232,165, 81,157, 0, 3,232,169 840 DATA 0,157, O, 3,232,157, 0, 3 850 DATA232,169, 15.157, 0, 3,232,157 860 DATA 0, 3,232,169, 0, 93,250, 2 870 DATA 93,251, 2, 93,252, 2, 93,253 880 DATA 2,157,249, 2,254,249, 2,238 890 DATA 40, 6,173, 40, 6,197, 67,208 900 DATA186,138, 72,169, 75,141, 0, 5 910 DATA162, 1,138,157, 0, 5,232,208 920 DATA250,169, 0,133, 48,169, 3,133 930 DATA 49, 32, 48,254,104,168,136, 32 940 DATA229,253, 32,245,253,169, 5,133 950 DATA 49, 32,233,245,133, 58, 32,143 960 DATA247,169, 35,133, 81,169,169,141 970 DATA 0, 6,169, 5,141, 1, 6,169 980 DATA133,141, 2, 6,169, 49,141, 3 990 DATA 6,169, 76,141, 4, 6,169,170 1000 DATA141, 5, 6,169,252,141, 6, 6 1010 DATA169,224,133, 1020 DATA 3,165, 3, 48,252
76,148,193,234,234,234,234,234
FULL TRACK 27 SOURCE LISTING 100 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 260 270 280 290 300 REM 27M.PAL REM 0PEN2,8,2,"@0:27M.B,P,W" REM SYS40960 ; .OPT P,02 ; *= $0400 ; ;* INITIALIZATION * ; LDA #$00 STA $7F LDX $OC STX $51 STX $80 LDX $OD INX STX $43 LDA #$01
152
310 320 330 340 350 360 370 380 390 400 410 420 430 440 450 460 470 480 490 500 510 520 530 540 550 560 570 580 590
620
JSR $C100
* CREATE HEADERS *
LDX #$00 HBID HEADER # LDA #$08 STA $0300,X INX INX ; CHECKSUM LDA $0628 STA $0300,X ; SECTOR INX LDA $51 STA $0300,X ; TRACK INX LDA #$00 STA $0300,X ; IDL INX STA $0300,X ; IDH INX LDA #$OF STA $0300,X ; GAP INX 600 STA $0300,X ; GAP 610 INX 630 LDA #$00 640 EOR $02FA,X 650 EOR $02FB,X 660 EOR $02FC,X 670 EOR $02FD,X 680 STA $02F9,X 690 700 INC $02F9,X 710 720 730 740 750 760 770 780 790 5 INC LDA CMP BNE ; TXA PHA ; $0628 $0628 $43 HEADER COMPUTE CHECKSUM
INCREMENT CHECKSUM
153
800 ;* CREATE DATA * 810 ; 820 LDA #$4B ; 1541 FORMAT 830 STA $0500 840 LDX #$01 ; 1541 FORMAT 850 TXA 860 DATA STA $0500.X 870 INX 880 BNE DATA 890 ; 900 ;* CONVERT TO 6CR * 910 ; 920 LDA #00 930 STA $30 940 LDA #$03 950 STA $31 960 JSR $FE30 970 PLA 980 TAY 990 DEY 1000 JSR $FDE5 1010 JSR $FDF5 1020 LDA #$05 1030 STA $31 1040 JSR $F5E9 1050 STA $3A 1060 JSR $F78F 1070 ; 1080 ;* JUMP INSTRUCTION * 1090 ; 1100 LDA #$23 1110 STA $51 1120 ; 1130 LDA #$A9 1140 STA $0600 1150 LDA #$05 1160 STA $0601 1170 LDA #$85 1180 STA $0602 1190 LDA #$31 1200 STA $0603 1210 LDA #$4C 1220 STA $0604 1230 LDA #$AA 1240 STA $0605 1250 LDA #$FC 1260 STA $0606 1270 ; 1280 LDA #$E0 1290 STA $03
154
Full Track 27 Error Source Annotation See the annotation for 20M.PAL. The only major difference is in line 700 above. Note the header block identifier ($08) in line 420 is left alone.
155
370 G0SUB440 380 PRINT#15,"M-W"CHR$(71)CHR*(0)CHR*(1) CHR*(7> 390 IFE< >1G0T0550 400 CL0SE15 410 PRINT"CDOWN>DONE!" 420 END 430 REM JOB OUEUE 440 TRY=0 450 PRINT#15,"M-W"CHR$(8)CHR$(0)CHR$(2)C HR*(T)CHR$(S) 460 PRINT#15,"M-W"CHR*(1)CHR*(0)CHR*(1)C HR*(JOB> 470 TRY=TRY+1 480 PRINT#15,"M-R"CHR*(1)CHR*(0) 490 GET#15,E$ 500 IFE$=""THENE$=CHR*(0) 510 E=ASC(E*) 520 IFTRY=500G0T0540 530 IFE >127G0T0470 540 RETURN 550 CLOSE15 560 PRINT"<DOWN> CRVS>FAILEDCROFF>" 570 END SINGLE SECTOR 22 ERROR SOURCE LISTING None. Line 340 in the program creates a single sector 22 error by decrementing the data block identifier. Line 380 restores the status quo.
156
180 190 200 210 220 230 240 250 260 270 280 290 300 310 320 330 340 350 360
II
IFQ*< >"Y"THENEMD 0PEN15,8,15 PRINT#15,"I0" INPUT#15,EN*.EM*,ET*,ES$ IFEN$="00"G0T0270 PRINT"<DOWN>"EN$", "EM*","ETV,"ES* CLOSE15 END REM SEEK J0B=176 G0SUB550 REM READ J0B=128 G0SUB550 PRINT#15,"M-R"CHR*(56)CHR* < 0) GET#15,D* IFD$=""THEND$=CHR*(0) CL0SE15 PRINT" {DOWN3-REMOVE MASTER FROM DRIVE
370 PRINT"INSERT CLONE IN DRIVE" 380 PRINT"PRESS CRVS> RETURN{ROFF> TO CON TINUE" 390 GETC*:IFC$=""THEN390 400 IFC*< >CHR*(13)G0T0390 410 PRINT"OK" 420 0PEN15,8,15 430 REM SEEK 440 J0B=176 450 G0SUB550 460 PRINT#15,"MW"CHR$(71)CHR$(0)CHR$(1) D* 470 REM WRITE 480 J0B=144 490 G0SUB550 500 PRINT#15,"M-W"CHR$<71)CHft$<0)CHR*<l) CHR*(7> 510 CL0SE15 520 PRINT"C DOWN} DONE!" 530 END 540 REM JOB QUEUE 550 TRY=0 560 PRINF#15,"MW"CHR$(8)CHR$(0)CHR$(2)C HR*<T)CHR*<S> 570 PRINT#15,"M-W"CHR*(1)CHR*<0)CHR*(1)C HR*<JOB> 580 TRY=TRY+1 590 PRINT#15,"M-R"CHR*(1)CHR*(0) 600 GET#15,E$ 610 IFE*=""THENE*=CHR$ C 0)
157
620 E=ASC(E*> 630 IFTRY=50060T0660 640 IFE >127G0TQ580 650 RETURN 660 PRINT#15,"M-W"CHR*<71)CHR$<0>CHR$<1> CHR*<7> 670 CL0SE15 680 PRINT"<DOWN> iRVSJFAILED<ROFF}" 690 END DUPLICATE A SINGLE SECTOR 22 ERROR SOURCE LISTING None. Line 320 in the program reads the data block identifier from the master. Lines 460-490 duplicate the error on the clone. Line 500 puts our house back in order.
158
390 PRINT"{DOWN>INSERT CRVS>BLANKCROFF> IN DRIVE" 400 G0SUB910 410 0PEN15,8.15 420 F0RJ=0T06 430 F0RI=0T07 440 READD 450 D*<J)=D*<J)+CHR*(D) 460 NEXTI 470 NEXTJ 480 1=0 490 F0RJ=0T06 500 FRINT#15,"M-W"CHR$<I)CHR$<4)CHR$(8)D *<J) 510 I=I+8 520 NEXTJ 530 F0RI=lT035 540 PRINT#15,"M-W"CHR$(49+1)CHR$ <4)CHR$(
1)L$(I)
550 PRINT#15."MW"CHR$(84+I)CHR$(4)CHR*(
1)H*<I)
560 NEXTI 570 REM EXECUTE 580 PRINT"{DOWN3{RVS3FGRMATTING{ROFF> DI SKETTE" 590 PRINT#15,"M-E"CHR*<0>CHR*<4> 600 INPUT#15,EN*,EM$.ET$,ES$ 610 T=18 620 S=0 630 J0B=176 640 G0SUB970 650 J0B=128 660 G0SUB970 670 PRINT#15,"MW"CHR$(0)CHR$ <4)CHR$(3)C HR$(18)CHR*(1)CHR*(65) 680 J0B=144 690 G0SUB970 700 S=1 710 J0B=128 720 G0SUB970 730 PRINT#15."M-W"CHR$<0)CHR*<4>CHR$<2)C HR$<0)CHR$<255) 740 J0B=144 750 G0SUB970
159
760 CL0SE15 770 0PEN15,8,15 780 PRINT#15,"NO:1541 FORMAT" 790 INPUT#15,EN*,EM*,ET$.ES* 800 S=0 810 J0B=128 820 G0SUB970 830 PRINT#15,"M-W"CHR$(162)CHR$(4)CHR$(2 )CHR*(50)CHR*(54) 840 J0B=144 850 G0SUB970 860 PRINT#15,"M-W"CHR$(162)CHR$(7)CHR*(2 )CHR$(50>CHR*(54) 870 CL0SE15 880 PRINT"<DOWN>DONE!" 890 END 900 REM DELAY 910 PRINT"{DOWN>PRESS CRVS>RETURNCROFF3 TO CONTINUE" 920 GETC*:IFC*=""THEN920 930 IFC$< >CHR$(13)G0T0920 940 PRINT"OK" 950 RETURN 960 REM JOB QUEUE 970 TRY=0 980 PRINT#15,"M-W"CHR*(8)CHR*(0)CHR$(2)C HR$(T)CHR*(S) 990 PRINT#15,"M-W"CHR*(1)CHR*(0)CHR*(1)C HR*(JOB) 1000 TRY=TRY+1 1010 PRINT#15,"MR"CHR$(1)CHR*(0) 1020 6ET#15,E$ 1030 IFE$=""THENE*=CHR*(0) 1040 E=ASC(E*) 1050 IFTRY=500G0T01070 1060 IFE >127G0T01000 1070 RETURN 1080 REM NEW 1090 DATA169. 0,133,127, 32, O, 0,193,169 1
6,169,199,141, 2,
6,169,250,141,
6,169,224 4,133
1120 DATA133,
MULTIPLE ID FORMATTING SOURCE LISTING 100 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 260 270 280 290 300 310 320 330 340 350 360 370 380 390 400 410 420 430 440 450 460 470 480 490 500 510 REM FAD.PAL REM 0PEN2,8,2,"@0:FAD.B,P,W" REM SYS40960 ; .OPT P,02 ; *= $0400 IDL = $0431 IDH = IDL+35 ; LDA #$00 STA $7F ; DRIVE NUMBER ; JSR $C100 5 LED ; LDA #$4C ; JUMP TO $FAC7 STA $0600 LDA #$C7 STA $0601 LDA #$FA STA $0602 ; LDA #$E0 STA $03 ; TABLE LDY $51 ; TRACK NUMBER ; LDA IDL,Y ; ID LO STA $13 ; LDA IDH,Y ; ID HI STA $12 ; CPY #$23 ; TRACK 35 BNE TABLE ; WAIT LDA $03 BMI WAIT ; JMP $C194
Multiple ID Formatting Source Annotation This is a modification of the standard formatting routine, NEW ($EEOD). Embedded IDs are read from each track on the master and tabled in 1541 RAM starting at $0431
161
by the driver. The appropriate ID for each track is stored as the master disk ID ($12/3) by the IP before control is passed to the FDC to format a track. After a track is formatted, the IP retakes control, finds the next ID in the table, stores it at $12/3, and passes control back to the FDC. Because we do not have a NO:DISK NAME,ID command in the command buffer, we cannot use the later portions ofthe standard formatting routine to create the BAM and directory. Lines 670-780 of the driver clean up afterward.
162
430 P0KE252,(RAM/256) 440 R=R+(NS+1) 450 G0T0620 460 REM READ 470 FORS=OTONS 480 G0SUB1300 490 PRINT"vHOME> <DOWN 7>{RVS>READINGCROF F> TRACK "T$" - SECTOR "S$ 500 J0B=128 510 G0SUB1190 520 IFE=lG0T0550 530 R=R+1 540 IFE<>4ANDE<>5G0T05S0 550 SYS49165 560 C=1 570 POKERW,1 580 RW=RW+1 590 RAM=RAM+256 600 P0KE252,(RAM/256) 610 NEXTS 620 NEXTT 630 CL0SE15 640 IFC=OGOTO1010 650 PRINT"vCLRj1541 BACKUP" 660 PRINT" CDOWNi- INSERT CLONE IN DRIVE" 670 G0SUB1110 680 0PEN15,8,15 690 RW=8448 700 RAM=8704 710 P0KE252,34 720 REM SEEK 730 FORT=SRWTOERW 740 NS=20+2*(T>17)+(T>24)+(T>30) 750 J0B=176 760 G0SUB1190 770 IFE=1G0T0820 780 RAM=RAM+(256*(NS+1)) 790 W=W+(NS+1) 800 G0T0990 810 REM WRITE 820 IFT(T)=1G0T0870 830 RW=RW+(NS+1) 840 RAM=RAM+(256*(NS+1)) 850 P0KE252,(RAM/256) 860 G0T0990 870 FORS=OTONS 880 IFPEEK(RW)=0G0T0950 890 GOSUB1300 900 PRINT"CHOME>CDOWN 73{RVS3WRITINGCROF F> TRACK "T$" - SECTOR "S*
163
910 SYS49228 920 J0B=144 930 G0SUB1190 940 IFE< >1THENW=W+1 950 RW=RW+1 960 RAM=RAM+256 970 P0KE252,(RAM/256) 9B0 NEXTS 990 NEXTT 1000 CL0SE15 1010 IFERW< >35G0T0210 1020 PRINT"{HOME> tDOWN 2>READ ERRORS :"R
II II
II
1040 PRINT" " 1050 PRINT"DONE!" 1060 PRINT" 1070 P0KE56,160 1080 CLR 1090 END 1100 REM DELAY 1110 PRINT"<DOWNJPRESS {RVS>RETURNtROFF> TO CONTINUE" 1120 IFC=OANDSRW< >1GOTO1160 1130 GETC*:IFC*< >""THEN1130 1140 GETC$:IFC$=""THEN1140 1150 IFC$<>CHR$(13)GOTO1140 1160 PRINT"OK" 1170 RETURN llSO REM JOB QUEUE 1190 TRY=0 1200 PRINT#15,"M-W"CHR$(8)CHR*(0)CHR*(2) CHR$(T)CHR*(S) 1210 PRINT#15,"M-W"CHR*(1)CHR*(0)CHR$(1) CHR$(JOB) 1220 TRY=TRY+1 1230 PRINT#15,"M-R"CHR$(1)CHR$<0) 1240 GET#15,E$ 1250 E=ASC(E*+CHR*(0)) 1260 IFTRY=500G0T01280 1270 IFE >127G0T01220 1280 RETURN 1290 REM STR*(T,S) 1300 T$=RIGHT*("0"+RIGHT*(STR*(T),LEN(ST R*(T))-l),2) 1310 S*=RIGHT*("0"+RIGHT*(STR*(S),LEN(ST R*(S))-l),2) 1320 RETURN 1330 REM *COOO
164
1340 DATA 77, 45, 82, 1350 DATA 45, 87, 1360 DATA251, 141, 0,
0,
4, 32, 169,
1380 DATA192, 96, 162, 15, 32, 201, 255, 162 1390 DATA 0, 189, 0,192, 32, 210, 255, 232
1400 DATA224,
1420 DATA255, 145, 251,200,192, 129, 208, 246 1430 DATA 32, 204, 255, 96,169, 0, 141, 10
1440 DATA192, 240, 11,173, 10, 192, 24, 105 1450 DATA 32, 141, 10,192,240, 47, 162, 15 1460 DATA 32, 201, 255,162, 0, 189, 7, 192
1490 DATA251, 32, 210,255,200, 192, 32, 208 1500 DATA246, 169, 13, 32,210, 255, 32, 204 1510 DATA255, 169, 0,240,198, 96, 234, 234
1520 REM TRACK 1530 DATAl,6,7,12! , 13, 17, 18,24 ? 2 3 , 3 0 , 3 1,3 5 1541 BACKUP SOURCE LISTING 100 110 120 130 140 150 160 170 REM BACKUP.PAL REM QPEN 2,8,2,"@0:M.B,P,W" REM SYS40960 ; .OPT P,02 ;
165
220 ; RAM LOCATIONS USED 230 ; 240 POINT = $OOFB ;POINTER TO READ/WRITE PAGE 250 ; 260 ; ROM ROUTINES USED 270 ; 280 CHKOUT = *FFC9 ;OPEN CHANNEL FOR OUT PUT 290 CHROUT = $FFD2 ;OUTPUT A CHARACTER 300 CLRCHN = $FFCC ;CLEAR ALL CHANNELS 310 CHKIN = *FFC6 ;OPEN CHANNEL FOR INP UT CHRIN = *FF&F ; INPUT A CHfiRACTER 330 J 340 ; DISK M-R & MW COMMANDS 350 5 360 MR .ASC "M-R" 370 .BYTE *00,*04,*FF,$80 380 390 MW .ASC "MW" 400 TEMP .BYTE $00,$04,*20 410 n 420 430 ;* ; * READ FROM DISK ROUTINES * * 440 450 ; M-R ENTRY POINT 460 470 LDA #*00 480 STA POINT ;POINT TO FIRST HALF 490 STA MR+3 jASK FOR FIRST HALF 500 JSR READIT ;READ FIRST HALF 510 1 520 LDA #*80 530 STA POINT 5POINT TO SECOND HALF ;ASK FOR SECOND HALF 540 STA MR+3 550 JSR READIT yREAD SECOND HALF % 560 570 RTS ;RETURN TO BASIC 580 590 ; SUBROUTINE TO READ IN HALF PAGE
M
600
610 READIT LDX #*OF ;PREPARE CHANNEL 1! FOR OUTPUT 620 JSR CHKOUT 630
166
640 650 660 670 680 690 700 710 720 730 UT 740 750 760 770 780 790 800 810 820 830 840 850
860 880
LDX #*00 L00P1 LDA MR,X ;SEND M-R COMMAND JSR CHROUT INX CPX #$07 BNE L00P1 ; JSR CLRCHN ; CLEAR THE CHANNEL ; LDX #$OF ;PREPARE CHANNEL 15 FOR INP JSR CHKIN ; LDY #$00 L00P2 JSR CHRIN STA (POINT),Y INY CPY #$81 BNE L00P2 ; JSR CLRCHN ; CLEAR THE CHANNEL RTS ;END OF READ HALF PAGE ;
;* ;* * *
870 ;*
890 ; FIRST MW ENTRY POINT 900 ; 910 MRITE LDA #$00 5lNITIALIZE PART PAGE POINTER 920 STA TEMP 930 BEQ ENTER 940 ; 950 L00P3 LDA TEMP 960 CLC 970 ADC #$20 980 STA TEMP 990 BEQ DONE
1000 ;
1010 ENTER LDX #$OF ;PREPARE CHANNEL 15 FOR OUTPUT 1020 JSR CHKOUT 1030 ; 1040 LDX #$00 1050 L00P4 LDA MW,X ;SEND "M-W LO HI $20
ll
167
llOO 1110 BE 1120 1130 1140 1150 1160 TERS 1170 1180 1190 1200 1210 1220 1230 1240 1250 1260 1270 1280 1290
LDY #*00 5 L00P5 LDA (POINT),Y ;SEND 32 CHARAC JSR INY CPY BNE m LDA JSR JSR
DONE RTS
1541 Backup Source Annotation The BASIC driver reads a sector from the master diskette into 1541 RAM using the job queue. The contents ofthe RAM are transferred into the C64 with a machine language memory-read. After a pass is complete, the clone is inserted into the drive. A machine language memory-write command is then used to transfer the bytes back to 1541 RAM. The BASIC drive writes the buffer out to the diskette using the job queue. The above routine illustrates how to do memory-read and memory-write commands in machine language. It is interesting to note that reading 256 bytes from 1541 RAM appears to take amost ten times as long as writing 256 bytes to 1541 RAM. However, the C64 internal clock is not reliable at all while performing I/O to the disk drive. Bypassing a bad track can be done anywhere between lines 200-340 if necessary. Any of the previous 11 routines may be used to recreate any errors that you found on the master diskette after a backup is made.
168
1541 COPY 100 REM 1541 COPY 110 P0KE56,16 120 CLR 130 P0KE251,0 140 P0KE252,16 150 P0KE253,0 160 P0KE254,16 170 F0RI=lT072 180 READD 190 P0KE49151+I,D 200 NEXTI 210 PRINT"{CLR>1541 COPY" 220 PRINT"CDOWNJINSERT MASTER IN DRIVE" 230 G0SUB750 240 G0SUB810 250 INPUT"{DOWN>FILENAME";F$ 260 IFLEN(F*)< >OANDLEN(F* > <17G0T0280 270 G0T01000 280 INPUT"tDOWN>FILE TYPE (DSPU) PCLEFT 3>";T* 290 IFT$="D"ORT$="S"ORT$="P"ORT*="U"GOTO 310 300 GOTOlOOO 310 RW$="R" 320 G0SUB890 330 SYS49152 340 CL0SE2 350 INPUT#15.EN$,EM*,ET$,ES$ 360 IFEN$="00"G0T0380 370 GOT0850 380 CL0SE15 390 PRINT"CDOWN>INSERT CLONE IN DRIVE" 400 G0SUB750 410 G0SUB810 420 PRINT#15,"M-R"CHR$(1)CHR$(1) 430 GET#15,D$ 440 D=ASC(D*+CHR*(0)) 450 IFD=6560T0490 460 PRINT"CD0WN>73,CBM DOS V2.6 1541,00.
00"
169
540 C=L+ (H*256> 550 S=PEEK(252)+((PEEK(253)-16)*256) 560 B=INT((S/254)+.5) 570 IFCB >=0G0T0600 580 PRINT"{DOWN3 72,DISK FULL,00,00" 590 G0T0710 600 RW*="W" 610 G0SUB890 620 SYS49182 630 CL0SE2 640 INPUT#15,EN*,EM$,ET$,ES$ 650 PRINT"<DOWN>DONE!" 660 CL0SE15 670 P0KE56,160 680 CLR 690 END 700 REM CLOSE 710 CL0SE15 720 PRINT"CDOWN> CRVS>FAILEDCROFF>" 730 G0T0670 740 REM DELAY 750 PRINT"CDOWN>PRESS {RVSJRETURN<ROFF> TO CONTINUE" 760 GETC$:IFC$=""THEN760 770 IFC*< >CHR$(13)G0T0760 780 PRINT"OK" 790 RETURN 800 REM INITIALIZATION 810 0PEN15,8,15 820 PRINT#15,"10" 830 INPUT#15,EN*,EM*,ET*,ES* 840 IFEN$="00"THENRETURN 850 PRINT"<DOWN>"EN*", "EM*","ET*","ES* 860 CL0SE15 870 G0T0670 880 REM FILE NOT FOUND - FILE EXISTS 890 0PEN2,8,2,"0:"+F*+","+T*+","+RW* 900 INPUT#15,EN*,EM*,ET*.ES* 910 IFEN*="00"THENRETURN 920 CL0SE2 930 PRINT"<DOWN>"EN*", "EM*","ET*","ES* 940 PRINT" {DOWN> {RVS>FAILED<ROFF> " 950 INPUT#15,EN*,EM*,ET*,ES* 960 CLOSE15 970 G0T0670 980 REM LOAD - SAVE 990 DATA162, 2, 32,198,255.160, 0. 32 1000 DATA228,255,145,251, 32,183,255, 41 1010 DATA 64,208, 8,200,208,241,230,252
170
5,192,132,251,
32,204,255 0
2, 32,201,255,160,
32,210,255,196.251,240 76, 38
8,200,208,244,230,254,
1060 DATA192,165,254,197,252,208,242,132 1070 DATA253, 32,204,255, COPY A FILE SOURCE LISTING 100 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 260 270 280 290 300 310 320 330 340 350 360 370 380 390 400 410 420 430 440 450 REM COPY.PAL REM 0PEN2,8,2,"@0:COPY.B,P,W" REM SYS40960 ; .OPT P,02 ; *= *C000 ; ; LOAD ; LDX #*02 JSR $FFC6 ; 0PEN2,8,2 ; LOAD LDY #*00 READ JSR *FFE4 ; IN STA ($FB>,Y JSR *FFB7 ; READST AND #64 BNE READY INY BNE READ INC *FC JMP LOAD ; READY STY *FB JSR *FFCC ; CL0SE2 RTS ; ; SAVE ; LDX #$02 JSR *FFC9 ; 0PEN2,8,2 ; SAVE LDY #$00 96.234.234,234
171
460 470 480 490 500 510 520 530 540 550 560 570 580 590 600 610
WRITE LDA <*FD>,Y ? OUT JSR $FFD2 CPY $FB BEQ BREAK C0NT INY BNE WRITE INC *FE JMP SAVE ; BREAK LDA *FE CMP *FC BNE CONT ; STY $FD JSR *FFCC ; CL0SE2 RTS
Copy a File Source Annotation This routine emulates a LOAD and SAVE from machine language.
Conclusion
In conclusion, we hope that this chapter has taken some of the mystery out of DOS protection schemes. We encourage serious readers to study the program listings carefully. The programming techniques employed are perhaps the most sophisticated applications of Commodore's direct-access commands that you will ever see.
172
CHAPTER 8
173
Group - Sector
1 - 18,1
2 3 4 5
6 - 18,16
11 - 18,14 12 - 18,17
17 - 18,15
18 - 18,18
STEP 2. Load and run the EDIT TRACK & SECTOR program on the diskette with the scratched file. When asked for the track and sector, enter track 18 and the sector number you read from the table. When prompted for the starting byte, enter 00 if the scratched f!le entry was one of the first four files in the group. Enter an 80 if the scratched file was displayed among the last four in the group. STEP 3. When the hex dump of the half-sector is displayed, cursor over to the third column of hexadecimal numbers on the display. Next locate the name of the file in the ASCII display on the right-hand side of the screen. Move the cursor down until it is on the same line as the start of the file name. If you have done things correctly you should be on a row labeled with a $00, $20, $40, $60, $80, $A0, $C0, or $E0. The byte under the cursor should be a 00. This is the file-type byte. The 00 indicates a scratched file. Type over the 00 value with the value that corresponds to the correct file type as indicated below. File Type PRG SEQ REL USR DEL Value 82 81 84 83 80
STEP 4. Hold down the SHIFT key and press the CLR/HOME key. This will terminate the edit mode. When asked whether to rewrite this track and sector, press Y and the modified sector will be written to the diskette in a few seconds. STEP 5. Load and list the directory to see if the file name now appears. If it does not, you made a mistake and things may have gone from bad to worse. Hopefully, the file will be listed. STEP 6. VALIDATE the diskette by entering in direct mode: OPEN 15,8, 15, "VC>":CL0SE15 If the drive stops and the error light is not flashing, everything has gone according to plan and the file has been recovered successfully. (If the VALIDATE command failed, see sections 8.2 and 8.3.)
174
NOTE: It is a good idea to practice these steps on a test diskette before you attempt to recover your lost Accounts Receivable! To do this: SAVE a file to disk, SCRATCH it, and follow the steps outlined above.
Appendix C contains two programs that are useful in trying to recover a sector that has a soft error. However, recovery cannot be guaranteed in all cases. These two programs are RECOVER TRACK & SECTOR and LAZARUS. The first program attempts to rewrite a damaged sector. LAZARUS will attempt to resurrect an entire diskette. The latter program returns a status report of the number of read errors encountered. It also reports the number of write errors that occurred. A write error indicates that a soft error encountered along the way was actually a hard error in disguise. Sorry about that.
175
STEP 1. Load and run the VALIDATE A DISKETTE program contained in Appendix C. This program emulates the VALIDATE command from BASIC. It will chain through each active file entry in the directory and highlight a bad file without aborting. STEP 2. Load and run FIND A FILE. This program will return the track and sector locations of where the file resides in the directory as well as where it starts on the diskette. The directory track and sector is extraneous information for our present purpose. Note only the starting track and sector. STEP 3. Load and run DISPLAY A CHAIN. This program requires you to input a track and sector. Input the starting track and sector obtained in step 2. The program will chain through all forward track and sectors on the diskette from this entry point until an error is encountered. (If the error is a soft error, STOP! Do not pass GO. Go directly to section 8.2.) Ignore the sector where the error was encountered. The file is virtually lost from that point on. (Recall that the link has been destroyed.) Make note of the last successful track and sector displayed. STEP 4. Load and run EDIT TRACK & SECTOR. You will want to input the track and sector obtained in step 3. The starting byte is always 00. Change the first two bytes to 00 and FF, respectively. Rewrite the sector when prompted to do so. You have in effect severed the forward track and sector link described in Chapter 4. This allows you to manipulate the front end of the file. It is the only portion of the file that is clearly intact. If it is a BASIC PRGfile,the internal BASIC links have been destroyed. You can restore the links on the C64 with a machine language monitor or on the diskette with the EDIT TRACK & SECTOR program. Ifyou do not restore the BASIC links, the C64 will crash as soon as you attempt to edit the last line of the program. Using EDIT TRACK & SECTOR, call up the sector that was just rewritten. You will have to inspect both halfpages of the block. Look for the last 00 byte in the page. Change the two bytes that immediately follow it to a 00 00 also. Note the position of the last 00 byte edited in hexadecimal. If you are in the second-half of the block, rewrite the sector and then recall the first-half. Change the forward sector pointer to the hexadecimal position of the last 00 byte you changed. Rewrite the sector a final time. You will now be able to load, list, and edit the program. Hopefully, you will remember to save it to a different diskette this time. If it was a SEQ file, the recovered data is intact. You will have to read it into C64 RAM and rewrite it to another file. If you do not know how to manipulate a sequential file contact someone who does.
176
177
8, 8,
2, 2,
"file "file
name,S,R" name,P,R"
(SEQfile) (PRGfile)
To read an unclosed file substitute, an M for the R in the OPEN statement like this: SYNTAX: OPEN 2 ? OPEN 2 ,
8, 8,
2, 2,
"file "file
name,S,M" name,P,M"
(SEQfile) (PRGfile)
The file can now be read into the C64 and stored in RAM. There is one problem, though. You will have to display the incoming data bytes because an EOI will not be returned by the disk drive. Note that the last sector written to the diskette will contain an erroneous forward track and sector pointer. As a result, there is no realistic way to determine when you have read beyond the actual contents of the unclosed file itself. Watch the incoming data bytes carefully. Your read program should have an embedded breakpoint. When you think you've captured all of the data bytes, rewrite them to another diskette. Once you have the data safely stored on another diskette, use the techniques described at the end of Section 8.3 to restore the internal BASIC links if it was a PRG file.
Don't forget to VALIDATE the diskette which has the unclosed file in the directory while youVe at it. Recall that scratching an unclosed file poisons the BAM.
Next, load and list the directory. If your diskette contained more than eight active files, all but the first eight files will be displayed on the screen. (The first eight files have been lost for now.) Do not attempt to VALIDATE the diskette because the directory sectors will not be reallocated. Copy all of the remaining files onto a new diskette.
If the first eight files are very important, you can attempt to recover them as well. However, it will not be easy! You must find the starting track and sector locations of these files yourseLf through a process of elimination. Begin by making a grid with a space for each sector on the diskette like this:
178
TRACK
Next, VALIDATE the original diskette and then load and run the program DISPLAY A BLOCK AVAILABILITY MAP listed in Appendix C. Working from the display on the CRT, indicate on your chart which sectors are in use by other files. Once you have done this, you should see a blank area centered around track 18. This is where you lost files reside. Now, load and run the DISPLAY A CHAIN program. The first file probably starts on track 17, sector 0. Record the chain displayed to the screen on your chart. Once you have recorded the first chain, begin looking for the next one. It probably begins on an open space on track 17 or, if the first chain was a long one, on track 19, sector 0. Work outward from track 18 until you have located all eight missing files. Once you have the starting track and sector locations for the files, use the EDIT TRACK & SECTOR program to reconstruct track 18, sector 1. The tables and hex dumps from Chapter 4 can be used as a guide. Be sure to substitute the starting track and sector locations that you found and not the ones in this manual. Now copy the eight files onto another disk. Once this is done, take a break and meditate on the virtues of archival backups!
New
If you are reading this section in desperation, relax. It is already too late. However, if it dawns on you in the future that you are holding a blank diskette in your hand while the master that you were going to backup is being reformatted, don't PANIC! Attempt to regain your composure and pop the drive door open. At this point you don't care what the 1541 User's Manual says about opening the drive door when the red activity indicator is on. You are losing one full track every time you hear the stepper motor click. Next attempt to make a backup copy of the diskette using the 1541 BACKUP program listed on page 162. (Please, try to remember which diskette you want to format this time.) Recall that formatting works from the outermost track (track 1) to the innermost
179
track (track 35). If you threw the door in time track 18 will still be intact and so will most of your files. The DOS works outwards from track 18 when writing to a diskette. The outermost tracks were probably never in use. Now load and run the VALIDATE A DISKETTE program to assess the damage. Oftentimes all files are recovered.
Conclusion
In short, recovering a damaged diskette is more art than science. The utilities that we have presented here can prove invaluable in time of need. When all is said and done, however, it is much easier to create errors than to pick up the pieces afterward.
180
CHAPTER 9
$ 0 0 0 0 $ 0 1 0 0
$0200
2K of RAM $1800 Job queue, constants, pointers & work area $180F Stacks, work areas and overflow buffer Command buffer & work $1C00 $1C0F Data buffer #0
$0300 $0400 Data buffer #1 $0500 Data buffer #2 $0600 Data buffer #3 $0700 Buffer for BAM $0800
$ C 1 0 0
$F259
DOS in 15.8K of ROM Communications and file management Disk controller routines
$FFFF
181
The 6502 in the 1541 alternates between two modes of operation: Interface Processor (IP) mode and Floppy Disk Controller (FDC) mode. The 6502 switches to its FDC mode approximately every 10 milliseconds. The switch is made in response to an interrupt (IRQ) generated by one of the 6522 timers. The main IRQ handling routine checks to see if the IRQ was generated by the timer. If it was, the 6502 begins to execute the FDC routines. Once in FDC mode the interrupt signal is disabled and the 6502 remains in FDC mode until any jobs it has to do are completed. If the interrupt signal was not disabled, it might disrupt a read or write job.
Test zero page RAM Do checksum test of ROM's Test remainder of RAM Initialize I/O chips Set up buffer tables Set up buffer pointers JSR to inititialize FDC Initialize serial bus
b) Main IP Idle Loop Whenever the drive is inactive and the 6502 is in IP mode, the 6502 executes the code from $EBE7 to $EC9D looking for something to do.
182
OVERVIEW OF IP MODE IDLE LOOP ($EBE7-$EC9D) Yes Parse and execute the waiting command JSR PARSXQ ($C146) Service the attention request JSR ATNSRV ($E85B) Turn on the drive active LED Flash the drive active LED
Is the command-waiting flag ($0255) set? No Is the attention pending flag ($0255) set? No Is there a file open? No Is the error flag set? No JMP to start of loop |
Yes
Yes Yes
c) ComputerDisk Drive Communications The routines that handle communication on the serial bus are localized in one small area of ROM, from $E853 to $EA6E. The entry points for the major routines are summarized below. Entry $E853 $E85B $E909 $E9C9 $EA2E Routine ATNIRQ ATNSRV TALK ACPTR LISTEN Function An IRQ is generated when the computer sets the ATN line of the serial bus low. Branch to here from IRQ handler to set attention pending flag. Service an ATN signal on the serial bus Send data out on the serial bus Accept one byte of data from the serial bus Accept incoming data bytes from the serial bus
d) Execution of Disk Commands When the computer sends the 1541 a disk command, such as NEW, VERIFY, or SCRATCH, the command is stored temporarily in the command buffer ($0200-$0229) and the command pending flag ($0255) is set. The next time the 6502 works its way though the IP idle loop ($EBE7-$EC9D) it finds that the command pending flag has been set. It then does a JSR to the PARSXQ routine ($C146) to parse and execute the command. The parser first checks the command table ($FE89-94) to see if this is a valid command. Next it checks the syntax of the command. If the command is correct, a JMP is made
183
to the appropriate ROM routine. The table below summarizes the various disk commands and their entry points. Entry $ED84 $D005 $C8C1 $CAF8 $CClB $CB5C $E207 $E7A3 $C8F0 $CA88 $C823 $EEOD Command VALIDATE INITIALIZE DUPLICATE MEMORY-OP Effect Create a new BAM based on the directory. Initialize BAM by reading from disk. Make a backup of a disk (not on 1541). Perform a memory operation (M-R, M-W, M-E). Perform a block operation (B-P, B-A, B-F, etc.). Execute user routines (U0, U1, U2, etc.). Position to record in relative file. Load routine in disk RAM and execute it. Copy a file (single disk only on 1541). Rename a file in the disk directory. Scratch a file in the directory. Format a diskette (short and full).
V I D M
B BLOCK-OP U USER JMP P POSITION & UTIL LDR c COPY R RENAME S SCRATCH N NEW
For more details on these routines see Appendix B. If no errors are encountered during the execution of a command, the routine is terminated with a JMP to the ENDCMD ($C194). If errors are encountered, .A is loaded with an error code, and the routine is aborted with a JMP to the command level error processing routine at $E645. e) File Management File management is a major function of the interface processor. As a result, there are many ROM routines that deal directly or indirectly with the management of files, the directory and the BAM. A few of the major entry points are summarized below. Entry $C5AC $CBB4 $CEOE $D156 $D19D $D50E $D6E4 $D7B4 $DAC0 $DBA5 $DC46 Routine SRCHST OPNBLK FNDREL RDBYT WRTBYT SETJOB ADDFIL OPEN CLOSE CLSDIR OPNRCH Function of File Management Routine Search directory for valid or deleted entry. OPEN a direct access buffer. Find a record in a relative file. Read byte from a file. Get next sector if needed. Write byte to file. Write sector if full. Set up read or write job for FDC. Add a file to the directory. OPEN a channel for read, write, load, or save. Close the file associated with given channel#. Close directory entry for a write file. OPEN a channel to read using double buffering.
184
$DCDA $DFD0 $E31F $E44E $E4FC $E645 $EA6E $EAA8 $EC9E $EF5C $EF90 $FllE
OPNWCH NXTREC ADDREL NEWSS ERRTAB CMDER2 PEZRO DSKINT STDIR WFREE WUSED NXTTS
OPEN a channel to write using double buffering. Set up next record for a relative file. Add a new sector to a relative file. Add new side sectors to relative file. IP mode error message table. IP mode error handler. Display error diagnostics by flashing LED. Initialize IP side of disk. Convert directory to pseudo program and load. Mark given sector as free in the BAM. Mark given sector as in use in the BAM. Finds next available sector from the BAM.
NOTE: You cannot use the memory-execute (M-E) technique described in this section when you are using any routine that involves reading from or writing to a diskette. The reason for this restriction is that memory-execute commands are carried out while the processor is in the IP mode. In this mode, the processor is interrupted every 10 milliseconds by an IRQ and switches into FDC mode. Any read or write operation will be interrupted if this occurs. See Section 9.6 for the technique to use if you want to use a routine that reads from or writes to the diskette. Once you are sure that the routine performs the operation you want and what setup is needed, you are ready to design your program. Your program will normally have three parts: 1. A Setup Section This section normally consists of one or more memory-write (M-W) commands to poke any required setup values into the 1541's RAM memory. 2. A Section to Execute the Routine This section normally consists of one memory-execute (M-E) command to force the 154rs microprocessor to execute the ROM routine.
185
3. An Information Retrieval Section This section normally consists of one or more memory-read (M-R) commands to peek the results of the routine out of the 1541's RAM for use by your program. Let's take a look at a typical application of this technique. Suppose we were writing a data base management program. One thing we would like to build into our program is a check to be sure that we can never produce an unclosed file (*SEQ). This would happen if the user entered too much data and completely filled the disk. We can't rely on checking the drive's error channel in this situation because the DOS sends the disk full error too late; the damage is already done. We are going to have to have some independent method of finding the number of blocks free on the diskette before we write out the file. Since we know that a directory listing shows the number of blocks free, we'll start by looking for some routines that deal with the directory. The chart of ROM routines that deal with file management in Section 9.3 (e) has one entry that looks promising: STDIR ($EC9E), convert directory to pseudo program and load. We now turn to Appendix B and look up this routine. Scanning through this routine doesn't turn up an algorithm that appears to calculate the number of blocks free and we're back to square one. What about the initialize routine? From the chart on the execution of disk commands in Section 9.3 (d) we find that this routine starts at $D005. Back to Appendix B. Eureka! At $D075 we find the routine NFCALC. A bit of disassembly indicates that this routine probably needs very little setup to calculate the number ofblocks free and that it stores the lo-byte of the count in NDBL ($02FA) and the hi-byte in NDBH ($02FC). Before we set up an elaborate program, let's check out these RAM locations using a test program like this: 10 OPEN 15,8,15,"I" 20 GOSUB 12D:REM CHECK DISK STATUS 30 OPEN l,8,5,"@0:TEST FILE,S,W" 40 GOSUB 120:REM CHECK DISK STATUS 50 FOR K=1 TO 300 60 PRINT#l,"THIS IS TEST RECORD NUMBER"; K 70 PRINT K;:GOSUB 170:REM CHECK BLOCKS F REE 80 NEXT 90 CLOSE 1:CLOSE15:END 100 : llO REM SUB TO CHECK DISK STATUS 120 INPUT E,E*,T,S 130 PRINT E;E*;T;s 140 RETURN 150 : 160 REM SUB TO READ BLOCKS FREE 170 PRINT#15,"MHR "CHR*(250)CHR* < 2)CHR*(3
>
180 GET#15,X*:NL=ASC<X*+CHR*<0>)
186
After trying our test program, we find our problem is solved. As we write out our records the DOS automatically updates the count in NDBL and NDBH to reflect the number of blocks left. We don't really need to execute a ROM routine after all. A memory-read command is all we need. The moral? A bit of time spent studying and testing can really simplify your life. Since the "blocks free" example really didn't illustrate the use of an IP routine, let's try again. This time we are interested in converting normal bytes into their GCR equivalents to see what is actually written out to the disk. After snooping through the IP tables in Section 9.3 without any luck, we try the FDC tables in Section 9.5. We find what we need in 9.5 (c): PUT4GB ($F6D0), convert four data bytes into five GCR bytes. In checking Appendix B we find that, although this is nominally an FDC routine, it does not involve reading from, or writing to, a diskette. This means we can use the memory-execute technique. After a bit of disassembly we know what set-up is required: 1. The routine expects to find four normal bytes stored in RAM from $52-$55. 2. The pointer at $30/31 should point to the start of where the five GCR bytes that result from the conversion are to be stored. We'll use $0300-$0304. 3. The GCR pointer at $34 should be $00. 4. The entry point for the routine is definitely $F6D0. Now that we know what we have to do, let's set up the program. First, we'll start by inputting the four bytes we want to convert and storing them in disk RAM from $52 (82) to $55 (85) using a memory-write command (M-W). Second, we will use memory-write (M-W) commands to set the pointers at $30 (to 0), $31 (to 3), and $34 (to 0). Third, we'll execute the routine using a memory-execute (M-E) command. Finally, we will peek the results from $0300-4 of the disk RAM using a memory-read (M-R) command and five GET# statements. Here's what the program looks like: 100 REM CONVERT BINARY TO GCR 110 PRINT"CCLR>ENTER FOUR BYTES (DECIMAL )<DOWN>" 120 B*(0)="0":BS(1>="1":F0RK=0T07:P(K>=2 ^K:NEXT 130 F0RK=0T07:P(K)=2^K:NEXT 140 OPEN 15,8,15 150 : 160 REM INPUT BYTES & STORE IN DISK RAM ($52/5) 170 FOR K=0T03 180 PRINT"BYTE#"K"=";:INPUT X
187
190 IF X<0 OR X>255 GOTO 180 200 PRINT"{UP>"TAB(18);:G0SUB430 210 PRINT#15,"M-W"CHR$<82+K)CHR$(0)CHR$( 1)CHR$(X) 220 NEXT 230 : 240 REM SET UP POINTER TO STORAGE AREA < 30/31) 250 PRINT#15,"MW"CHR$(48)CHR$(0)CHR$(2) CHR$(0)CHR*(3) 260 : 270 REM SET UP GCR POINTER ($34) 280 PRINT#15,"M-W"CHR*(52)CHR*(0)CHR$(1) CHR*(0) 290 : 300 REM EXECUTE PUT4GB ($F6D0) IPC ROUTI NE 310 PRINT#15,"M-E"CHR$(208)CHR$(246) 320 : 330 REM PEEK OUT AND DISPLAY RESULTS 340 PRINT#15,"M-R"CHR*(00)CHR*(3)CHR*(5) 350 PRINT"<DOWN>THE FIVE EQUIVALENT GCR BYTES ARE:{DOWN>" 360 FOR K=1 TO 5 370 GET#15,X$:X=ASC(X$+CHR$(0)) 380 PRINT"BYTE#"K"="X;TAB(18);:G0SUB430 390 NEXT 400 CLOSE 15:END 410 : 420 SUB TO DISPLAY BINARY EQUIVALENTS 430 PRINT"7."; 440 FOR L=7T00STEP-1 450 T=INT(X/2^L) 460 X=X-T*P(L) 470 PRINTB$(T); 480 NEXT:PRINT:RETURN Many ofthe other IP ROM routines arejust as easy to use. However, be careful because some are tricky. Some expect to find a particular command in the command buffer. These are tough to use because the memory-execute command will wipe out any set-up you have done in the command buffer area. In these cases you will have to store a short machine language routine in the disk RAM that sets up the proper command in the buffer before it JMP's to the IP routine. When you execute the routine, it should overwrite the M-E command in the buffer with the command you want there. Happy sleuthing!
188
way through the detailed ROM maps in Appendix B. This section summarizes the major FDC routines and their entry points. a) Initialization When the disk drive is first switched on, the reset line is pulsed lo. This causes the 6502 to RESET and it does an indirect JMP via the vector at $FFFC to the initialization procedure at $EAA0. As part of the set up procedure, the variables and I/O chips for the FDC are initialized by the CNTINT routine ($F259-AF). b) Main FDC Idle Loop Every 10 milliseconds the 6522 timer generates an interrupt (IRQ) and the 6502 begins to execute the main FDC loop looking for something to do. The main features of this loop are summarized below. OVERVIEW OF MAIN FDC LOOP ($F2B0) Any jobs in job queue? Yes Is it a JMP job ($D0)? No Should drive motor be ON? No Is drive up to speed? Yes Is the head stepping? No Is this the right track? | No Set # of the tracks to the step Yes DO THE JOB Yes JMP to END No ^ JMP to END Yes * Motor ON & JMP to END Yes Do JMP job ($F370) No JMP to END
END
($F99C) Yes Set the change in status flag ($lC) Move the head JMP DOSTEP ($FA2E)
Yes
7No
189
Yes Yes
At the end of this loop, or when the job has been completed, the timer interrupt is reenabled and the 6502 leaves FDC mode. c J Major FDC Entry Points When in FDC mode the 6502 executes routines that directly control the operation of the disk drive. These include: turning the drive motor ON or OFF, controlling the stepper motor that moves the head from track to track, formatting a blank diskette, locating a specific sector and reading or writing data, and translating information back and forth between normal 8-bit bytes and the 10-bit GCR code that is actually recorded on a diskette's surface. The 6502 carries out these tasks in response to job requests placed in the job queue by the IP processor. The entry points for the major FDC routines are summarized below.
Entry $F259 $F2B0 $F367 $F37C $F3B1 $F4CA $F56E $F691 $F6D0 $F78F $F7E6 $F8E0 $F934 $F99C $FAC7
Routine CNTINT LCC EXE BMP SEAK REED WRIGHT VRFY PUT4GB BINGCR GET4GB GCRBIN CONHDR END FORMT
Function Initialize important variables and the I/O chips. Main FDC idle loop (IRQ entry every 10 millisec). Do execute job. Bump head to track #1 (step out 45 tracks). Seek any header on a track. Read in data block of specified sector. Write out data block of specified sector. Read back data block to check for good write. Convert four data bytes into five GCR bytes. Convert entire data buffer into GCR write image. Convert five GCR bytes into four data bytes. Convert GCR image of data block into normal data. Convert header into a GCR search image. End of idle loop to control drive & stepper motor. Format blank diskette.
190
Since the read, write and format routines are of particular interest, let's look at them in more detail. d) Read Data Block of Specified Sector Before the read job code ($80) is placed in the job queue, the IP puts the desired track and sector numbers into the header table as indicated below. Job queue location $0000 $0001 $0002 $0003 $0004 $0005 Use buffer address $0300-FF $0400-FF $0500-FF $0600-FF $0700-FF NO RAM Track # address $0006 $0008 $000A $000C $000E $0010 Sector # address $0007 $0009 $000B $000D $000F $0011
_#
0 1 2 3 4 5
Once the track and sector values are in place, the IP puts the read job code into the job queue in the location that corresponds to the data buffer where the data is to be stored. The next time the 6502 is in FDC mode it finds the job request. If necessary, it turns on the drive motor, waits for it to get up to speed, and moves the head to the proper track. It then executes the read routine outlined below: OVERVIEW OF THE FDC READ ROUTINE $F4D1 $F4D4 Find correct sector Read data: first 256 into the data buffer and the rest into the overflow buffer Convert GCR to normal Check data block ID Check data checksum Exit, read was OK _ _ _ _ ^ _ _
e) Write Data Block of Specified Sector Before the write job code ($90) is placed in the job queue, the IP puts the desired track and sector numbers into the header table as indicated below.
191
Once the track and sector values are in place, the IP puts the write job code into the job queue in the location that corresponds to the data buffer containing the data to be written. The next time the 6502 is in FDC mode it finds the job request. If necessary, it turns on the drive motor, waits for it to get up to speed, and moves the head to the proper track. It then executes the write routine outlined below: OVERVIEW OF THE FDC WRITE ROUTINE $F575 $F57A $F586 $F589 $F58C $F594 $F5B1 $F5BF $F5CC $F5D9 $F5DC $F5E6 Calculate checksum. Test if write protect on. Convert buffer to GCR. Find correct sector. Wait out header gap. Switch to write mode and write out five $FF's as sync. Write out overflow buffer. Write out data buffer. Switch to read mode. Convert GCR back to 8-bit. Change job code to VERIFY. Go back to verify it.
f) Format a Blank Diskette The IP format routine at $C8C6 sets up a JMP $FAC7 instruction at $0600 and then puts an EXECUTE job code ($E0) into the job queue ($0003). On its next pass through the idle loop the FDC finds the executejob code, executes the code at $0600, andjumps to the formatting routine outlined below.
192
OVERVIEW OF THE FDC FORMATTING ROUTINE $FAC7 Check if this is first entry. If not, branch to $FAF5. Do bump to track #1 (CLUNK!) Initialize error count and bytes around track. Exit. Check if on right track. Check for write protect tab. Erase track with sync. Write half of track with sync and other half with non-sync. Time sync & non-sync parts. Compare times and calculate how long tail gaps should be. Create images of headers. Create dummy data block. Convert headers to GCR. Convert data block to GCR. Write out sectors in sequence. Go to read mode and verify. All sectors OK; do next track. All tracks done; exit.
$FACB $FAE3
$FB35 $FB7D
193
The tough ones are those that involve reading or writing to a diskette. To illustrate how to do this, we'll try something interesting. How about developing a routine that allows us to move the head anywhere on a diskette (say track 5) and read the next header (or whatever) that passes over the read/write head. First we have to find out how to move the head around. A quick check of the map of the I/O chips at the end of Appendix A tells us that the stepper motor that moves the head is controlled by bits 0 and 1 of DSKCNT ($1C00). Cycling these two bits causes the head to move. H m m . . . Cycling the bits must mean: 00-01-10-11-00 versus 11-10-01-00-11. Time out for a bit of testing. Here's our program: lOO REM MOVE THE 1541'S HEAD 110 PRINT"<CLR> <DOWN>COMMANDS: U=UP D=DO WN Q=QUIT" 120 OPEN 15,8,15,"I" 130 PRINT#15,"M-R"CHR*<0)CHR*<28> 140 GET#15,X$:X=ASC<X$+CHR$(0> > 150 BI=X AND 3 160 PRINT"CHOME> CDOWN 3>BI="BI 170 GET A* 180 IF A*="U"THEN BI=BI+1 190 IF A*="D"THEN BI=BI-1 200 IF A*="Q"THEN CLOSE 15:END 210 BI=BI AND 3 220 R=(X AND 252)0R BI 230 PRINT#15,"M-W"CHR*(0> CHR*(28)CHR*(1) CHR$<R> 240 GOTO 130
After much peeking through the drive door with a flashlight we discover that our program actually does make the head move. When we press "U" the head moves closer to the center (higher track numbers) and when we press "D" the head moves outward (lower track numbers). We've got it! Quick let's write it down before we forget. To move the head, cycle bits 0 and 1 of $lCOO 00 "*" 01 -+~ 10 ~*~ 11^0 1 2 3 11^3 00 0 headmovesinwards headmovesoutwards
10 * ~ 01 ~*~ 00 ~ * ~ 11 2 1 0 3
The only problem that remains is to find out how much the head moves each time. Hmm . . . If we read from a track and then peek at $1C00 . . . Time for more testing: 10 REM CHECK PHASE FOR ALL TRACKS 2 0 OPEN 1 5,8, 1 5,"I"
194
30 OPEN l,B,5,"#" 40 FOR TR=1 TO 35 50 PRINT#15,"Ul:5 0"TR;0 60 PRINT#15,"M-R"CHR*(0)CHR*<28> 70 GET#15,X$:X=ASC(X$+CHR$(O)) 80 PRINT TR;X AND 3 90 NEXT 100 CLOSE1:CLOSE15 When we run this test program, we get a very interesting table: i 8 15 22 29 0 2 0 2 0 2 9 16 23 30 2 0 2 0 2 3 10 17 24 31 0 2 0 2 0 4 11 18 25 32 2 0 2 0 2 5 12 19 26 33 0 2 0 2 0 6 13 20 27 34 2 0 2 0 2 7 14 21 28 35 0 2 0 2 0
The phase of the stepper motor is always even (0 or 2) when the head is on a track. Therefore, the head must be moving half a track at a time. Very interesting indeed! Now that we can move the head around, we want to find out how to read something. But before we go rummaging through the ROM's, wasn't there something about the clock rate being different for each zone? Ah, here it is. Bits 5 and 6 of $1C00 set the recording density. Let's see. Bit 5 represents 32 and bit 6, 64. Let's change one line of our last test program and try again. Here's the new line: 80 PRINT TR;X AND 96 When we run our revised program, we get another interesting table. 1 8 15 22 29 96 96 96 64 32 2 9 16 23 30 96 96 96 64 32 3 10 17 24 31 96 96 96 64 0 4 11 18 25 32 96 96 64 32 0 5 12 19 26 33 96 96 64 32 0 6 13 20 27 34 96 96 64 32 0 7 14 21 28 35 96 96 64 32 0
By George, we've got it. Zone 1 2 3 4 Tracks 1-17 18-24 25-30 31-35 $1C00 Bit 5 Bit 6 1 1 1 0 1 0 0 0 Number 96 64 32 0
Let's do some digging in those ROM's now. A quick scan through the table of Major FDC Entry Points in Section 9.5 (c) turns up SEAK ($F3B1), seek any header on the track. A check of the detailed analysis in Appendix B looks promising. A careful study of a disassembly of the routine indicates that this is just what we were looking for. And, we don't have to do much setup either. Here's all the information we need:
195
1. The entry point is $F3B1. 2. JOB ($45) should be $30 so the branch at $F3E6 is taken. 3. JOBN ($3F) should contain the correct buffer number so the error handler routine at $F969 works properly. Now comes the tricky part. Since the routine involves reading from or writing to a diskette, we cannot execute the routine using a memory-execute command. We have to use a two step process: 1. Use a memory-write command to store a machine language routine (it does the setup and then a JMP to $F969) into the start of one of the buffers (we'll use buffer -0 at $0300). 2. Force the 6502, while in FDC mode, to execute our routine by putting a JUMP or EXECUTE job code in the appropriate spot in the job queue (we'll put a JUMP code into $0000). The program listed below puts it all together for us. It may appear a bit intimidating at first. But, if you are interested in exploring the innards of your drive it is one of the most powerful tools presented in this manual. It allows you to move the head anywhere you want and read the next header passing over the read/write head. The screen display shows you where the head is, what track and sector was read, and describes any read errors that were encountered. lOO PRINT"tCLR> <DOWN> MOVE THE 1541'S READ/WRITE HEAD" 110 PRINT"{DOWN 2>INSERT TEST DISK" 120 PRINT"{DOWN 2>PRESS {RVS>RETURNtROFF > WHEN READY" 130 : 140 REM MACHINE CODE ROUTINE TO READ A HEADER 150 REM RESIDES AT *0300 <BUFFER #0)
160 :
170 DATA 180 DATA 190 DATA 200 DATA 210 DATA
220
230 D$<0>="00":D$<l>="01":D$<2)="10":D*< 3>="11" 240 DIM FD$<16> 250 FD*<0>=" 260 FD$<l>="01 ALL OK 270 FD*<2)="02 HEADER BLOCK NOT FOUND 280 FD*<3)="03 NO SYNC CHARACTER 290 FD$(9)="09 HEADER BLOCK CHKSUM ER 300 T=18:N1$="?":N2*="?":TR=255 310 GET A*:IF A$<>CHR$(13) GOTO 310 320
196
380 GET#15,11$:IFI1$=""THENIl$=CHR$(O) 390 GET#15,12$:IFI2$=""THENI2$=CHR$(0) 400 : 410 PRINT"vCLR>" 420 : 430 REM READ THE DI3K CONTROLLER PORT 440 : 450 PRINT#15,"M-R"CHR$(0)CHR$(28) 460 GET#15,A$:IF A$=""THEN A$=CHR$(0 > 470 A=ASC(A$) 480 CV=3 AND A 490 A=(159ANDA)OR(96+32*((T >17) + < T >24) + < T>30))) 500 PRINT#15,"M-W"CHR$(0)CHR$(28)CHR$(1) CHR$(A OR 4> 510 : 520 REM DISPLAY VALUES 530 : 540 PRINT"{HOME> CDOWN> MOVE THE 1541'3 READ/WRITE HEAD" 550 PRINT"vDOWNJCURRENT PHASE ="CV 560 PRINT"BITS 1 S < 0 OF $1C00 ARE "D$(CV ) 570 PRINT"CDOWN>MASTER DISK ID: "Il$;I2$ 580 PRINT"CDOWN3TRACK # FROM STEPPER:"T" {LEFT> 590 PRINT"CDOWN>FDC ERROR:"FD$(E) 600 T$=STR$ <TR):S$=STR$(SE):IF E<>1 THEN
j$= 77 ;N1$="?":N2$="?":S$="??"
610 PRINT"{DOWN>TRACK # A S READ: "RIGHT $(T$,2) 620 PRINT"SECTOR # AS READ: "RIGHT$(S$,2 ) 630 PRINT"ID OF TRACK READ: "Nl$;N2$ 640 PRINT"DOWN 2>C0MMANDS:" 650 PRINT"{DOWN> F1 = MOVE HEAD OUT (LO WER TRACK #) 660 PRINT" F3 = MOVE HEAD IN (HIGHER TR ACK #) 670 PRINT" F5 = ATTEMPT TO READ TRACK # & ID" 680 PRINT" F7 = TERMINATE PROGRAM" 690 PRINT" I = INITIALIZE (TO TRACK 18 )" 700 P=PEEK(197)
197
710 IF P=3 G0T0 910 720 IF P=4 AND T>1 THEN C=-1:G0T0 800 730 IF P=5 AND T<35 THEN C=l:GOTO 800 740 IF P=6 GOTO 990 750 IF P=33 THEN PRINT#15,"I":T=18:E=0:A =214:G0T0480 760 GOTO 450 770 : 730 REM CHANGE PHASE IN RESPONSE TO COMM AND 790 : 800 CV=(CV + C)AND3 810 T=T+C*.5:IFT<1 THENT=1 820 IFT >36THENT=36 830 B=A AND 252 840 C=B+CV 850 PRINT#15,"M-W"CHR$(0)CHR$ < 28)CHR$(1) CHR*(C) 860 E=0 870 GOTO 450
880 :
890 REM TERMINATE PROGRAM (DRIVE OFF) 900 : 910 PRINT#15,"M-W"CHR$(0)CHR$(28)CHR$(1) CHR*(240) 920 FOR K=1T010:GETA$:NEXT 930 CLOSE 15!END 940 : 950 REM ATTEMPT TO READ ANY HEADER 960 : 970 REM READ S < SEND MACHINE CODE ROUTIME 980 : 990 RESTORE:C$="" 1000 FOR K=1 TO li:READ X : C $ = C $ + C H R $ ( X ) : NEXT 1010 PRINT#15,"M-W"CHR*(0)CHR$(3)CHR*(11
)C*
1020 :
1030 REM PUT JMP JOB IN THE JOB QUEUE 1040 : 1050 PRINT#15,"M-W"CHR$(0)CHR*(0)CHR$(1) CHR$(208)
1060 :
198
1130 REM "E" IS FDC ERROR CODE RETURNED 1140 IF E<>1 GOTO 450 1150 : 1160 REM CLEAN READ SO DIG OUT ID, TRAK & SECT 1170 : 1180 PRINT#15,"M-R"CHR* < 22)CHR*(0)CHR*(4 ) 1190 GET#15,N1$ 1200 GET#15,N2* 1210 GET#15,X$:TR=ASC(X$+CHR$(0)) 1220 GET#15,X*:SE=ASC<X*+CHR*<0>) 1230 GOTO 450 Although this program allows you to move the head and read data in half-track increments, you can't double the capacity of your drive by using all 70 "tracks." The magnetic path produced by the read/write head is just too wide. However, it may be possible to devise a protection scheme in which the "protected information" is recorded when the head is in an "odd phase" (1 or 3). Crosstalk from the two odd-phase tracks, though, would make the diskette unreadable except by a specialized routine like this.
COIL
RECORD/PLAY HEAD
RING OF MAGNETIC MATERIAL
GAP
199
Write Mode: In write mode an electric current passes through the coil. The current causes the head to become an electromagnet whose strength and polarity depends on the amount and direction of the electric current. The gap in the ring interrupts the magnetic field and causes it to flare outwards. Ifthe gap is in contact with the surface of the floppy diskette, some of the magnetic domains on the surface shift position and line up with the magnetic field of the head. Some of these magnetic domains retain their new orientation even after leaving the vicinity of the gap, i.e., the surface of the diskette has become magnetized.
WRITE MODE
t///uutt/tttttu
*////////M/n/t////
The amount and direction ofthe current flowing through the coil determines the strength and polarity of the electromagnet. The more current, the stronger the electromagnet, and the greater the magnetization of the surface of the diskette. In audio recording, the amount of current flowing through the coil fluctuates to match the changing audio signal. In digital recording, there are only two possible currents, full current in one direction or full current in the other direction. When data is recorded onto the surface of a floppy diskette, the track becomes a series of bar magnets laid end to end.
Read mode: In read mode the moving magnetic areas on the surface of a diskette induce an electrical voltage in the head. Because of the nature of electromagnetic induction, the maximum induced voltage is NOT produced by the regions where the magnetic field is greatest. The maximum signal occurs where the magnetic fields change most rapidly. The signal from the head must, of course, be amplified and shaped before it is usable.
200
Writing data to a diskette: When data is being recorded onto a floppy diskette, the data is "clocked out" at a fixed rate. This permits an interesting recording scheme. The direction of the current flowing through the head changes only when a "1" bit is to be recorded. Zeros are represented by the absence of a transition at a particular location. The diagram below represents what is actually recorded on a diskette.
N N
S S
N N
s
1 0
N N
S s
Note that the data recorded onto a diskette is not divided into bytes. There is just one continuous stream of bits. In order to know where to begin to read or write bits, we need some special identifying mark. This is the function of the SYNC mark, a string of 10 or more Ts in a row. The GCR code (see Chapter 7) is designed so that no combination ofbytes can produce more than eight "1" bits in a row. This guarantees the uniqueness of the sync mark. The 1541 records between 4000 and 6000 magnetic zones Ot>its) per inch. Since the diskette rotates at a constant angular velocity (300 rpm), you may wonder how Commodore manages to get more bits on the outer tracks than the inner ones. The 1541 manages this bit of magic by clocking out the data at different rates depending on the track. On the longer outer tracks, the data is clocked out faster than for an inner track (see table in Chapter 3). However, the increase in clock rate is not really proportional to the increase in track length. This means that the outer tracks have a bit density of only 4300 bits/inch while the inner tracks are recorded at 6000 bits/inch. If the clock were not increased for the outer tracks, the bit density on the outermost track would fall to about 3500 bits/inch. Reading data from a diskette: When data is being read from a floppy diskette, the data is "clocked in" at a fixed rate. A magnetic transition is interpreted as a "1" bit. The lack of a signal when data is expected is interpreted as a "0" bit. Since the speed of the drive is not absolutely constant, we can run into problems if there are too many "0" (no signal) bits in a row. Commodore's GCR code is designed so that no GCR byte, or combination of GCR bytes, ever contains more than two consecutive "0" bits. As a further precaution, the clock is zeroed (cleared) every time a "1" bit is read. This re-synchronizes the clock to the bit stream and prevents small fluctuations in the speed of the drive from causing read errors.
201
The divide-by-N counter determines the actual rate at which bits are read or written. For tracks 1-17 the clock divisor is 13, for tracks 18-24 it is 14, for tracks 25-30 it is 15, and for tracks 31-35 it is 16.
WRITE MODE
CD>
6502 MPU 6522
>
P ^ S
Shift Register CLK
Hj>
FLIP FLOP
H2>
DRIVER
TO DISK
S O
BYTE RDY
SO ENABLE
o =
kD-
CLOCK
T " N
202
_r~^~L 0
o _ i
_ 0
| I
I 1
|
DATA
BITS
|CL0CKED|
OUT
1
D I K 1 N N 1
1
S S
1
N N
1
S S
BITCLOCKED OUT -
To help clarify the recording process let's follow a byte of data (10100110) as it is written to a diskette.
STEP 1. The 6502 converts the header block ID ($07), the 256 data bytes, the data block checksum, and two null bytes into 325 GCR encoded bytes. STEP 2. The head is positioned to the appropriate track and the clock divisor is set to the correct value for this track. STEP 3. The track is read until the correct sector header block is found. Wait out the header gap. STEP 4. Switch to write mode by ANDing the contents of the 6522's peripheral control register (PCR) with $lF, ORing the result with $C0, and storing the final result back in the PCR. STEP 5. Write out five $FF characters as the data block sync mark. STEP 6. Transfer the first 8-bit byte of the GCR encoded data to the data lines (D0-D7) of the 6522 PIA. STEP 7. Since Port A of the 6522 is configured as an output port, the data appears on the Port A lines PA0 to PA7. This transfers the byte to the 74LS165 (UD3) parallel to serial shift register. STEP 8. The bits are clocked out of the shift register (2) whenever the QB line (1) of the 74LS193 hexadecimal counter (UF4) makes a transition from ground to +5 volts.
203
STEP 9. The bit stream from the shift register (2) is presented to the clock input of the 74LS74 flip flop (UF6). The output of this flip flop (3) changes state whenever the bit stream (2) makes a transition from ground to +5 volts. STEP 10. The output of the flip flop (3) is amplified and sent to the record/play head of the drive. This causes the magnetic zones to be written onto the surface of a diskette. Note that the direction of the electric current, and hence the direction of magnetization, changes only when a "1" is to be written. STEP 11. Once all 8 bits have been clocked out of the shift register, the byte ready line goes high. This sets the overflow flag in the 6502 to indicate that it is time to send the next data byte to the 6522. STEP 12. Once all the data bytes have been written, switch to read mode by ORing the contents of the 6522's peripheral control register (PCR) with $E0 and storing the result back in the PCR.
N N
S S
N N
S S
CLR CLK
CLR CLK
CLR CLK
CLR CLK
A _
_JL_
204
DATA
BITS
|CL0CKED|
IN
Shift
Shift
Shift
Shift
Shift
Shift
Shift
To help clarify the reading process let's follow a byte of data as it is read from a diskette. STEP 1. The head is positioned to the appropriate track and the clock divisor is set to the correct value for this track. STEP 2. The track is read until the correct sector header block is found. STEP 3. Wait for the sync mark at the start of the data block. STEP 4. As the track passes over the record/play head a stream of weak electrical pulses is induced in the head. A pulse is induced whenever the magnetic field changes its orientation. The pulse is amplified and shaped (1). STEP 5. The stream of pulses from the shaper circuitry (1) is fed to the CLEAR input of the 74LS193 hexadecimal counter (UF4) and to the 74LS02 (UE5) NOR gate. Whenever a pulse occurs, the hexadecimal counter (UF4) and the divide by N counter (UE7) are cleared to a count of zero. This ensures that the clock is always synchronized with the incoming stream of pulses. STEP 6. Once the hexadecimal counter has been cleared, it begins to count up the clock pulses it receives from the divide by 16 counter. QA (not shown) is the l's bit of the counter. QB (2) is the 2's bit of the counter. QC (3) and QD (4) are the 4's and 8's bits, respectively. STEP 7. On each ground to +5 volt transition of QB (2), a bit is shifted into the 74LS164 serial to parallel shift register (UD2). The bit that is shifted in (5) is found by NORing the QC (3) and QD (4) lines of the counter. Note that whenever a pulse clears the divide by 16 counter, the next bit is read as a "1." If the counter has not been cleared before the next ground to +5 volt transition of QB (2), the next bit is read as a "0." STEP 8. Once 8 bits have been clocked into the shift register, the byte ready line goes
205
high. This sets the overflow flag in the 6502 to indicate that it is time to read the data byte from the 6522. STEP 9. The 6502 reads the data byte from the 6522 and stores it in RAM.
206
people wonder if something went wrong; so they VERIFY to be sure the file has been saved correctly. The file verifies as OK. A check of the directory indicates no unclosed files. However, the file may appear somewhat shorter than before. This did not occur because your program has been compacted. Rather, it was truncated by the DOS. It isn't all there! We hope you have a backup handy. If not, you may still be able to recover your file. A printout of the BAM and some quick work on editing the directory entry's starting track and sector are in order. (See Chapter 8.) The sectors shown as unallocated (free) in the BAM hold the only complete copy of your program, the original version that is. The latter portions of the @ replacement version of your program have been stored in disk WOM CWrite Only Memory) by the DOS. Bye, bye. 3. The Block-Read (B-R) command: This command has been replaced by the U1 command and with good reason. The B-R command has two serious bugs that make it unusable on the 1541. The use of this command is NOT RECOMMENDED! See Chapter 5 for the gory details. 4. The Block-Write (B-W) command: This command has been replaced by the U2 command and with good reason too. The B-W command is also unusable on the 1541. The use of this command is NOT RECOMMENDED either. Chapter 5 again gives the scoop. 5. The Block-Allocate (B-A) command: Although this command seems to work correctly on other Commodore drives, it does not work properly on the 1541. This command really has two functions: a) To allocate a free sector in the BAM: When the track and sector specified in the block-allocate command is free (not in use) in the BAM, the block allocate command should allocate the block in the BAM. The B-A command appears to do this correctly on the 1541. b) Find the next available track & sector: If the track and sector specified in the block-allocate command is already allocated (in use) in the BAM, the block allocate command should not change the BAM in any way. It should return a 65, NO BLOCK error and report the track and sector of the next available block in the BAM. This feature of the B-A command was included to allow the programmer who is creating his own random access files to determine the next free block that he/she can use. This feature ofthe B-A command does not work correctly on the 1541! The command does return the track and sector of a free block all right, but with a difference! 1. It occasionally returns a sector on track 18. This should not happen because track 18 is reserved for the directory.
207
2. It ALLOCATES ALL THE BLOCKS on the track that it returns in the error message in the BAM. Because of these bugs, the use of the B-A command on the 1541 is NOT RECOMMENDED. However, the CERTIFY A DISKETTE program listed in Chapter 5 does work. The reason for this is that this program stores a duplicate copy of the BAM in C64 RAM which is later rewritten to the diskette. This technique repairs the damage done by the B-A command. 6. UJ: or U: command: Commodore disk drives have traditionally used one or both of these commands to enable the user to reset the drive Qust as though the drive were turned OFF and then ON again). Neither command works correctly on the 1541 drive. The drive goes on a trip to never-never land and must be turned OFF and then ON again to recover from one of these commands. The command "U;" is the one to use to reset the 1541. 7. UI- command: The 1541 manual indicates that this command is used to set the disk drive to operate correctly with the VIC-20. Current 1541's work with a VIC-20, period.
Summary
Despite its flaws, the DOS in the 1541 is a remarkably efficient peripheral. The DOS programs for most other microcomputers are vastly inferior to DOS 2.6; a little faster maybe, but not as smart. The support of relative file structures, read ahead buffering, and the underlying principles of asynchronous I/O make the 1541 an outstanding bargain in the world of microcomputing. These features are normally found only in multiuser or multiprocess operating systems.
208
the header gaps to be 90 and 64 bits long respectively. However, when we use a bitgrabber to view the gap we find that the actual header gaps as recorded on disk are 100 bits for the 4040 and 92 bits for the 1541. In read mode, this makes no difference. After reading the header bytes to check that this is the correct sector, all the drives simply wait for the next sync mark. The number of bytes in the header gap does not matter. Once the sync mark is over, the first character in the data block is read. This is the data block ID character. If it is not a $07, the DOS reports a 22 READ ERROR (data block not found). In write mode, however, the length of the header gap is important. After reading the header bytes to check that this is the correct sector, all the drives count off the bytes that make up the header gap. Once the correct number of bytes have been read, the drive flips to write mode and begins writing out the data block sync character. Since this is reputed to be an important aspect of the write incompatibility problem, let's examine what happens in some detail. The last part of the header gap and the start of the data block sync mark in a sector of a diskette that hasjust been formatted on a 1541 disk drive looks something like this: 1541 Sync mark x x x x x x x x x x l l l l l l l l l l l l l l l l l l l l l l l l l l l l l l l l l l l l ^ 92 bits
The last part of the header gap and the start of the data block sync mark in a sector of a diskette that has just been formatted on a 4040 disk drive looks something like this: 4040 Sync mark xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 111111111111 ^ - 100 bits
When a sector of a diskette that was ORIGINALLY FORMATTED ON A 4040/2040 disk drive is REWRITTEN ON A 1541, the result is as follows: Original Sync mark 4040 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 111111111111^Rewrite Sync mark 1541 xxxxxxxxxx-lllllllllllllllllllllllllllllllllll^ Sync mark Result xxxxxxxxxx-llllllllllllllllllllllllllllllllllll^
NOTE: The "-" marks when the drive switches into write mode. A transient current appears to flow through the record/play head during this time interval. The original sync mark on the diskette has been completely overwritten by the new one. This sector can be read cleanly on any drive. It appears that a 1541 drive should be able to write data onto a diskette that was originally formatted on a 4040 drive without causing any problems. When a sector of a diskette that was originallyformatted on a 1541 disk drive is rewritten on a 4040/2040, the result is as follows:
209
O r i g i n a l 1 5 4 1 R e w r i t e 4 0 4 0 R e s u l t
S y n cm a r k x x x x x x x x x x l l1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ^ S y n cm a r k x x x x x x x x x x x x x x x x x 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ^ P s e u d o s y n c S y n cm a r k xxxxxxxxxxlllllll-lllllllllllllllllllllllllllll^
NOTE: The "-" marks when the drive switches into write mode. A transient current appears to flow through the record/play head during this time interval. In this case, the original sync mark on the diskette has NOT been completely overwritten by the new one. The start of the old sync mark is still there. What actually gets recorded at the start of the "new" sync mark depends on the speed of the drives, the polarity of the magnetic field used to record the original "1" at that spot on the diskette, and any transients that flow through the head as it switches into write mode. Before you read this next section, be sure that you understand Section 9.7 on the Recording Process. Let's take a look at an "exploded" view ofthat spotjust before the new sync character is written. Remember, a "1" is not recorded as magnetization in a particular direction. It is simply a change in the direction. Now that you've got that straight, here is what that spot might look like.
Original
N 1
S S ]
N N 1
S S
N N 1
S S 1
N N 1
S S 1
N N 1
Everything appears normal. Now let's write that sync mark. Original N by a 1541
S S
N N
S S
N N
S S
N. N
S S
N N
S S
N N
S, S
N. N
Everything worked out just fine. We have a clean sync mark and the sector can be read cleanly by either drive. However, suppose our 74LS74 flip-flop (UF6) had been in the opposite state or the speed of this drive did not exactly match this new one. What would happen? Take a look.
210
Original by a 1541
SS
NN
SS
NN
SS
NN
SS
NN
? ?S
NN
S S
NN
SS
? 0
Argh! Potential problems. Because the magnetic polarity of the new "1" happened to match the polarity of the existing zone, we appear to have just created a double-length magnetic zone. If we have, this will be interpreted as a "0" bit. From a study of the bits actually recorded on disk, this appears to happen every time! If there are more than 10 preceeding "1" bits, this single "0" will be interpreted as the end of the sync mark and the drive will interpret the rest of the sync bits as data. Since this will definitely NOT be decoded as a $07 byte, the drive errs out with a 22 READ ERROR. Since the header gaps only differ in length by 8 bits, we should always have only seven l's in the pseudo-sync. An examination of the bits recorded on the disk seems to support this conclusion. As a further test we did some testing using recently aligned drives. We found surprisingly few errors when we use a 4040 disk drive to rewrite all nondirectory sectors on a 1541 formatted disk. On a freshly formatted diskette, we found no errors at all after rewriting over 2400 sectors. If the sectors of the 1541 diskette had been rewritten several times using a 1541 before they were rewritten on a 4040, we did start to find a few errors. However, the error count was low. Usually less than two errors when rewriting all 640 sectors and these tended to occur in two specific areas: on tracks 25 or 26 or on tracks 31 or 32. These findings lead us to conclude that the differences in header gap length is NOT the cause of write compatibility problems between the 1541 and 4040 disk drives. If for some reason you want to reduce the difference in header gap further when writing onto a 1541 formatted diskette using a 4040 drive, enter the following magic incantation in either program or immediate mode. OPEN 15,3,15 F'RINT#i5, "M-W"CHR* (157) CHR$ (16>CHR$ (1) CHR* (8) CLOSE 15 This will change the header gap length of the 4040 drive from 9 to 8 GCR bytes (actual length = 90 bits). You can now write to the 1541 diskette with little fear of damage. However, you must remember to reset your 4040 drive (turn it off or issue a UJ command) before you insert one of your 4040 formatted diskettes. Otherwise, a magnetic plague will develop among your 4040 formatted diskettes. Don't say you weren't warned! Head Positioning Problems Since we encountered so few errors using properly aligned drives, we feel that most of the reported problems of incompatibilities are the result of head positioning errors.
211
If a sector is rewritten on a different drive and the position of the read/write head is different, the new data will not completely replace the old as indicated below. Original on one drive N N N S s S s s s
s s
s
S S s
N S S S
1 ss
N N N N N N
1
0
S s s s s s s
1 1
1 0 N N N N N N
When this sector is read on the original drive, the head will pick up both the new signal and the old signal. The relative strengths of these two signals depend on the amount of the original signal remaining. If the two drives are sufficiently different, the read signal will be garbled and produce an abundance of 22 and 23 READ ERROR's.
Summary
In conclusion, although there is a difference in header gap size between the 1541 and the 4040 drives, this does NOT appear to be the cause of write incompatibility problems. Most complaints about the write incompatibilities of various disk drives are probably due to problems in head positioning. Further evidence for this is the fact that some schools are experiencing similar difficulties when students use several different 1541 drives for saving programs on a single diskette.
212
NOTE: Line 160 contains a special character #184 repeated 21 times. This character can be typed by holding down the Commodore logo key in the lower left corner and pressing the U key.
1541 DISK PEEK 100 REM 1541 DISK PEEK 110 REM BY GERALD NEUFELD 120 C0=0:C2=2:C7=7:CA=10:F=15:CG=16:H0=4 8:HX=127 130 Z$=CHR$(0):N$="" 140 M*=" {RVS> PRESS: P TO PAUSE Q TO QUIT {ROFFV 150 PRINT"{CLR>"TAB(9>"PEEK OF 1541'S ME MORY" 160 PRINTTAB(9)"<#184 21>" 170 PRINTTAB(4>" COPYRIGHT: G. NEUFELD, 1983" 180 PRINT"{DOWN> ONE MOMENT PLEAS E " 190 DIM HX$<255>,H*<15) 191 FOR K=0 TO 9:H$ <K>=CHR$(48+K>:NEXT:F ORK=10T015:H$(K)=CHR*(55+K):NEX T 200 FORJ=OTOF:FORK=OTOF:HX*<J*16+K> =H*<J )+H$(K):NEXT:NEXT 210 PRINT"{HOME> CDOWN 2>"M$ 220 PRINT"tDOWN3 INPUT START ADDRESS IN HEXADECIMAL" 230 OPEN 15,3,15 240 PRINT"<DOWN> *0000":PRINT"{UP3"; 250 INPUT H$ 260 HL=CO:HH=CO:FORK=1T02:C=ASC(MID$ < H*, K>)-HO:IFC >CATHENC=C-C7 270 IF C<CO OR C>F THENPRINT"CUP 23";:GO T0240 280 D=ASC(MID$ < H$,K+2))-HO:IFD>CATHEND=D -C7 290 IF D<CO OR D>F THENPRINT"UP 2>";:G0 T0240 300 HH=HH+C*CG'S (C2-K) : HL=HL+D*CG^ (C2-K) : NEXTK 310 PRINT"UP>"TAB(6); 320 PRINT#15,"M-R"CHR*(HL)CHR$(HH)CHR*(8 ) 330 0$="":FOR K=C0T0C7:GET#15,A$:IF A$=N *THENA$=Z* 340 A=ASC(A$ >:E=AANDHX:E$=".":IFE>31ANDE <97THENE*=CHR* <E> 350 03>=0$+E$:PRINT" "HX$<ASC<A$>>;:NEXT:
213
PRINT" {RVSJ"0$ 360 FL=0:HL=HL+8:IFHL>255THENHL=HL-256:H H=HH+1:FL=1:PRINTM* 370 IF HL=128 THEN FL=l:PRINTM$ 380 PRINT" *"HX$(HH>HX*<HL>;:IFFL=lTHENP RINT:PRINT"<UP>";:G0T0250 390 GET A$:IF A*=""GOTO 320 400 IF A$="P"THENPRINT: PRINT" <!UP> " 5 : GOTO 250 410 CL03E15 CREATE A FILE 10 PRINT" <CLR> {DOWN> "TAB (6) "DISK ROM TO FILE" 20 INPUT"<DOWN>START AT LOCATION (HEX) ClOOCLEFT 6>";A$ 30 Z$=A*:G0SUB2B0:S=Z:IF ZF=1 GOTO 20 40 PRINT"{UP>"TAB(31)Z 50 INPUT"CDOWN>QUIT AT LOCATION (HEX) F FFFCLEFT 6>";A$ 60 Z*=A$:G0SUB280:Q=Z:IF ZF=1 GOTO 50 70 PRINT"CUP>"TAB(31)Z 80 INPUT"CDOWN>SAVE IN FILE NAMED ROM 1 541CLEFT 10>";F$ 90 INPUT"CDOWNyWITH LOAD ADDRESS OF (HEX ) 1100<LEFT 6>";A* 100 Z$=A*:G0SUB280:L=Z:IF ZF=1 GOTO 90 110 PRINT"{UP>"TAB(31)Z 120 OPEN15,8,15,"I0" 130 OPEN l,8,5,"@0:"+F$+",P,W" 140 INPUT#15,EN,EM$,ET,ES 150 IF EN>19 THEN PRINT"<DOWN>DISK ERROR "EN;EM$;ET;ES:CL0SE1:CL0SE15:STOP 160 PRINT"CDOWN 2>" 170 LH=INT(L/256):LL=L-256*LH 180 PRINT#1,CHR$(LL);CHR$(LH); 190 FOR K=S TO Q 200 KH=INT(K/256):KL=K-256*KH 210 PRINT#15,"M-R"CHR$(KL)CHR$(KH) 220 GET#15,A*:IF A*="" THEN A*=CHR*(0> 230 PRINT#l,A$; 240 PRINT"CUP>WORKING ON"K 250 NEXT 260 CLOSE1:CLOSE15:END 270 : 280 Z=0:ZF=0 290 IF LEN(Z$)>4 THEN ZF=l:PRINT"{DOWN>< RVS>HEX STRING TOO LONG":RETURN 300 IF LEN(Z$)<4 THEN ZF=l:PRINT"CDOWN>t
214
RVS>HEX STRING TOO SHORT":RETURN 310 FOR K=1 TO 4 320 ZN=ASC<MID*<Z*,k))-48:IF ZN>9 THEN Z N=ZN-7 330 IF ZN<0 OR ZN>15 THEN ZF=1:PRINT"CD0 WN>CRVS>BAD HEX CHARACTER":RETURN 340 Z = Z + ZN * 16^<4-K> 350 NEXT 360 RETURN HAVE FUN!
Late News
In early 1984 Commodore began shipping the 1541 disk drives that contained a new $EOOO-$FFFF ROM. The part numbers ofthese ROMs are: original 901229-03 revised 901229-05. The changes in the new ROM are: $E683 $E68B $E780 to $E7A1 $E9DC $EAA4 $EBDB/DD/E0/E2 $FEE6 $FF10 $FF20 Eliminate JSR TO ITTERR($EA4E) to solve stack overflow problems. Eliminate power-on boot of the utility loader to solve possible problems during initialization. Insert JMP to patch at $FF20. Insert JMP to patch at $EF10. Change initialization of the serial bus. New ROM checksum. New patch to change the initialization of the serial bus during the power-up routine DSKINT. New patch to the serial bus listen routine ACPTR.
The ROM in the SX-64 has an additional change. The header block gap at $F58D has been changed from $08 to $09 to eliminate the difference in header gap size between the 4040 and SX-64.
215
APPENDIX A
217
JOB QUEUE: $0000-$0005 The job queue is used to tell the disk controller what disk operations to perform. A disk command such as LOAD, SAVE, SCRATCH, etc. is interpreted by the drive's 6502 (while in its normal mode) and broken down into a set of simple operations (jobs) such as: read track 9 sector 18 into data buffer #2, write the data in buffer #3 out to track 12 sector 5, etc. The track and sector information required for the job is placed into the header table and t.he JOB CODE corresponding to the job to be done is put in t.he job queue. The job code's position in the queue indicates which data buffer (if any) is to be used and where the track and sector information is stored in the header table. When the 6502 is next in its floppy disk controller mode (it switches every 10 milliseconds), it scans the job queue looking for jobs t . o do. If it finds one, it carries it out making use of the track and sector information in the header table. Once the job is done, or aborted, the disk controller replaces the job code with an error code that indicates the job status. JOB CODES $80 $90 $A0 $B0 $C0 READ a sector WRITE a sector VERIFY a sector SEEK any sector BUMP (move) head t . o track #1 $D0 JUMP to machine code in buffer $E0 EXECUTE code in buffer once up t . o speed & head ready NAME JOBS Use Use Use Use Use Use $01 $02 $03 $04 $05 $07 $08 $09 $0A $0B $10 ERROR CODES job completed successfully! header block not found no SYNC character data block not found data block checksum error verify error after write write protect error header block checksum error data block too long ID mismatch error byte decoding error
ADDRESS
$ 0 0 0 0 $0001
$0002
# 0 #1
#2
m in in in in in
$10/1
HEADER TABLE: $0006-$0011 This is the area that specifies which tracks and sectors are t . o be used for the jobs in the job queue. Tracks and sectors are not needed for BUMP or JUMP jobs. ADDRESS | NAME $0006/7 $0008/9 $000A/B $000C/D $000E/F
$0010/1
HEADER TABLE DEFINITIONS Track/sector Track/sector Track/sector Track/sector Track/sector Track/sector 219 for for for for for for job job job job job job in in in in in in $0000 $0001 $0002 $0003 $0004 $0005 (buffer (buffer (buffer (buffer (buffer (buffer 0) 1) 2) 3) 4) 5)
HDRS
ADDRESS $0012
NAME DSKID
1541 RAM VARIABLE DEFINITIONS Master copy of disk ID. This is the ID specified when the disk was formatted. It is updated whenever a SEEK job is performed (see ROM patch $EF25). The initialize command performs a seek and therefore updates the master ID. $0012 first ID character $0013 second ID character Unused - Disk ID for drive #1 Image of the most recent header read. The characters appear here in the same sequence that Commodore's manual says t.hey are recorded onto the disk surface. $0016 first ID character $0017 second ID character $0018 track number $0019 sector number $001A header checksum NOTE: They are actually recorded onto disk in the opposite sequence. Not used Flag to indicate that there has been a change in the write protect status. UNUSED (WPSW for drive #1) last state of the write protect switch UNUSED (LWPT for drive #1) Set to $01 on power-up disk drive status bit meaning 4 shut down drv motor? l=yes 0=no 5 drive motor l=on 0=off 6 head stepping l=on 0=off 7 drive ready? l=no 0=yes UNUSED (DRVST for drive #1) Track currently under R/W head UNUSED (DRVTRK for drive #1) Work area for doing interconversions of binary data and it.s GCR write images Temporary storage of pointers Point.er to currently active buffer Pointer to active values in header table Pointer to last character converted Not used Byte counter for GCR/binary conversions Not used Data block ID character ($07) Header block ID character ($08) Storage of dat.a or header checksum
$0021 $0022 $0023 $0024$002D $002E/F $0030/1 $0032/3 $0034 $0035 $0036 $0037 $0038 $0039 $003A
DRVTRK STAB SAVPNT BUFPNT HDRPNT GCRPNT GCRERR BYTCNT BITCNT BID HBID CHKSUM
220
ADDRESS $003B $003C $00 3D $003E $003F $0040 $0041 $0042 $0043 $0044 $0045 $0046 $0047
NAME HINIB BYTE DRIVE CDRIVE JOBN TRACC NXTJOB NXTRK SECTR WORK JOB CTRACK DBID
1541 RAM VARIABLE DEFINITIONS Unused Unused Always $00 on 1541 Currently active drive ($FF if inactive) Position of last job in job queue (0-5) Byte counter for GCR/binary conversions Position of next job in job queue (0-5) Next track to move head to Sector counter. Used by format routine Temporary workspace Temporary storage of job cod.e Unused Dat.a block ID code. Set on reset to $07. This may be changed to write or read data blocks with different data block ID codes. However, the first nybble of the data block ID code should always be a zero ($0-). Otherwise, the controller will have difficulty detecting the end of the sync mark and the start of DBID. If you try to read a sector whose DBID is different from the value stored here, t.he disk controller will put an error code of $04 in the job queue and the drive will report a #22 error (DATA BLOCK NOT FOUND). Timer for acceleration of head Temporary save of the stack pointer The number of steps to move the head to get. to the desired track. To move the head over 1 track, requires XX steps. Values between 0 and 127 move the head out (to lower track numbers). Values over 128 move t.he head (256-value) steps in (to higher track numbers) Temporary storage Last sector read Next. sector to service Hi byte of a pointer to the next buffer of GCR bytes to be changed into binary. The GCR bytes in the overflow buffer are translated first. This points to t.he buffer that holds the rest of them. Lo byte of a pointer to the next GCR byte location t.hat is to be translated Flag to indicate whether the data in the currently active buffer has been left in binary (0) or GCR (1) form. Used by the formatting routine to store the number of the track currently being formatted. Set. on reset to $FF.
221
1541 RAM VARIABLE DEFINITIONS Staging area for the four binary bytes being converted t . o GCR by PUT4BG($F6D0) or from GCR by GET4GB($F7E6). Staging area for the five GCR bytes being converted from binary by PUT4BG ($F6D0) or to binary by GET4GB($F7E6). Number of steps to use to accelerate or decelerate when stepping t.he head ($04) Acceleration/deceleration factor ($04) Number of steps left to accelerate or decelerate when stepping the head Number of steps left to step the head in fast stepping (run) mode. Pointer to the appropriate head stepping routine. Normally $FA05 (not stepping) Minimum number of steps for the head to move to make the use of fast stepping mode useful($C8). If fewer steps needed, use the slow stepping mode. Pointer to start of NMI routine ($EB2E). Set on power up or drive reset. Flag to indicate whether NMI in progress Flag to enable (0) or disable (1) the auto initialization of a disk (read BAM) if ID mismatch detected. Sector increment for use by SEQ routine. Set on reset to ($0A). Counter for error recovery (number of attempts so far) Set on reset to $05 Pointer to the start of the user jump table($FFF6). Set on power up or reset. Pointer to the start of the bit map ($0400). Set when a disk is initialized. Temporary work area ($6F on reset) Temporary work area Temporary work area Temporary work area ($FF on reset) Temporary work area Temporary work area Indirect pointer variable ($0100) Set on power up or reset. Listener address ($28 on reset) Talker address ($48 on reset) Active listener flag Active talker flag Addressed flag Attention pending flag 6502 in attention mode Last program accessed Current drive number (always 0 in 1541) Current track number ($00 after use) Current sector number ($00 after use) Logical index (current channel#)
$0065/6 $0067 $0068 $0069 $006A $006B/C $006D/E $006F $0070 $0071 $0072 $0073 $0074 $0075/6 $0077 $0078 $0079 $007A $007B $007C $007D $007E $007F $0080 $0081 $0082
VNMI NMIFLG AUTOFG SECINC REVCNT USRJMP BMPNT TO=TEMP T1 T2 T3 T4 IP LSNADR TLKADR LSNACT TLKACT ADRSED ATNPND ATNMOD PRGTRK DRVNUM TRACK SECTOR LINDX
222
ADDRESS $0083 $0084 $0085 $0086 $0087 $0088 $0089 $008A $008B/E $008F/3 $0094/5 $0096 $0097 $0098
1541 RAM VARIABLE DEFINITIONS Current secondary address Original secondary address Temporary data byte Temporary result Temporary result Temporary result Temporary result Temporary result Result area ($008B-$008E) Accumulator ($008F-009 3) Directory buffer ($0094-0095) $05/$02 IEEE command in (not used on 1541) MY PA flag $00 Bit counter for serial $00 Buffer byte pointers These pointers (one for each buffer) are used to point at the next byte in the buffer to be used. The B-P command sets these pointers.
$0099/A $009B/C $009D/E $009F/0 $00Al/2 $00A3/4 $00A5/6 $00A7/D $00AE/4 $00B5/A $00BB/0 $00Cl/6 $00C7/C $00CD/2 $00D3 $00D4 $00D5 $00D6 $00D7 $00D8/C $OODD/l $00E2/6 $00E7/B $OOEC/l $00F2/7 $00F8 $00F9
BUFTAB
to to to to to to to
in in in in in in in
buffer #0 ($0300) buffer #1 ($0400) buffer #2 ($0500) buffer #3 ($0600) buffer #4 ($0700) CMD buffer($0200) ERR buffer($02D6)
BUFO BUF1 RECL RECH NR RS SS FlPTR RECPTR SSNUM SSIND RELPTR ENTSEC ENTIND FILDRV PATTYP FILTYP CHNRDY EIOFLG JOBNUM
Table of channel#'s assigned to each of t.he buffers. $FF is inactive buffer. Table of channel#'s assigned to each of the buffers. $FF is inactive buffer. Table of lo bytes of record numbers for each buffer Table of hi bytes of record numbers for each buffer Table of next record numbers for buffers Table of record size for each buffer Table of side sectors for each buffer File stream 1 pointer Pointer to start of record Number of side sector Index to side sector Relative file pointer to track Sector of directory entries Index of directory entries Default flag, drive # (all 0 on 1541) Pattern, replace, closed-flags, type Channel file type Channel status Temporary for EOI Current job number
223
1541 RAM VARIABLE DEFINITIONS Least recently used table No drive flag for drives 0 and 1 DOS version taken from track 18 sector 0 for drives 0 and 1 Unused STACK AREA $0104-$01FF
$0200$0229
CMDBUF
$022A $022B/D
CMDNUM LINTAB
$022E/3 $0244/9 $024A $024B $024C $024D $024E $024F/0 $0251/2 $0253 $0254 $0255 $0256 $0257 $0258 $0259 $025A $025B/F $0260/5 $0266/B $02 6C $026D $026E $02 6F
CHNDAT LSTCHR TYPE STRSIZ TEMPSA CMD LSTSEC BUFUSE MDIRTY ENTFND DIRLST CMDWAT LINUSE LBUSED REC TRKSS SECSS LSTJOB DSEC DIND ERWORD ERLED PRGDRV PRGSEC
Command buffer ($0200-$0229) Disk commands such as: N0:GAMES #1,G1 t.hat. are sent to t.he disk drive from t.he computer over the serial bus are stored here. The command is parsed to locate special characters such as : , Once t.he command has been interpreted, ROM routines are executed t . o do it. Command code number SA:LINDX table ($022B-$023D) This table indicates the current status of each dat.a channel (secondary address) EachTposition represents one channel, channel 0=$022B; 1=$022C; 2=$022D; etc. Possible channel status values are: $FF - inactive $81 - open for write $41 - read/writ.e $01 - open for read Channel data byte ($023E-$0243) The most recent bvt.e read or written for each channel Channel last character pointer Points to the last, character read or written in the buffer for each channel Active file type Length of the string Temporary secondary address Temporary job command Work area for finding best. sector t . o do Buffer allocation BAM dirty flag (drives 0/1) Directory entry found flag Directory listing flag Command waiting flag LINDX use word Last buffer used Record size. Used by directory routines Side sector track. Used by dir routines Side sector sector. Used by dir routines Last. job by buffer ($025B/C/D/E/F) Sector of directory entry by buffer Index of directory entry by buffer Error word for recovery Error LED mask for flashing Last program drive Last program sector
224
ADDRESS $0270 $0271 $0272/3 $0274 $0275 $0276 $0277 $0278 $0279
NAME WLINDX RLINDX NBTEMP CMDSIZ CHAR LIMIT FlCNT F2CNT F2PTR
1541 RAM VARIABLE DEFINITIONS Write LINDX Read LINDX # blocks temp Command string size Character under the parser PTR limit, in comparison File stream 1 count File stream 2 count File stream 2 pointer PARSER TABLES ($027A-$0289)
$027A/F $0280/4 $0285/9 $028A $028B $02 8C $028D $028E $028F $0290 $0291 $0292 $0293 $0294 $0295 $0296 $0297 $0298 $0299 $029A $029B/C $029D/0 $02Al/0
FILTBL FILTRK FILSEC PATFLG IMAGE DRVCNT DRVFLG LSTDRV FOUND DIRSEC DELSEC DELIND LSTBUF INDEX FILCNT TYPFLG MODE JOBRTN EPTR TOFF UBAM TBAM BAM
Table of filename pointers First file link (Track) First file link (Sector) Pattern presence flag File stream image Number of drive searches Drive search flag Last drive w/o error. Used as the default drive number. Found flag in directory searches Directory sector Sector of first available entry Index of first available entry =0 if last block Current index in buffer Counter of file entries Match by type of flag Active file mode (R,W) Job return flag Pointer for recovery Total track offset Last BAM update pointer Track # of BAM image (drive 0/1) BAM images ($02A1-02B0) OUTPUT BUFFERS ($02Bl-$02F8)
Directory buffer ($02Bl-$02D4) Error message buffer ($02D5-$02F8) Don't write BAM flag. Set to 0 at start and end of any disk command. # of disk blocks free (lo byte 0/1) # of disk blocks free (hi byte 0/1) Current phase of head stepper motor
225
ADDRESS
NAME
#0 #1 #2 #3 #4
BAM ONLY!
$1800
DATA PORT B - Serial data I/O BITS FOR SERIAL HANDSHAKE Bit 0 - $01 Data in line Bit 1 - $02 Dat.a out line Bit 2 - $04 Clock in line Bit 3 - $08 Clock out line Bit 4 - $10 Attention acknowledge line Bit 7 - $80 Attention in line DATA PORT A - Unused DATA DIRECTION FOR PORT B DATA DIRECTION FOR PORT A - Unused TIMER 1 LOW COUNTER TIMER 1 HIGH COUNTER TIMER 1 LOW LATCH TIMER 1 HIGH LATCH TIMER 2 LOW COUNTER TIMER 2 HIGH COUNTER SHIFT REGISTER AUXILIARY CONTROL REGISTER PERIPHERAL CONTROL REGISTER INTERRUPT FLAG REGISTER INTERRUPT ENABLE REGISTER DISK CONTROLLER 6522 ($1C00-$1C0F)
$1801 $1802 $1803 $1804 $1805 $1806 $1807 $1808 $1809 $180A $180B $180C $180D $180E
PA1 DDRB1 DDRA1 T1LC1 T1HC1 TlLL2 TlHL2 T2LC1 T2HC1 SR1 ACR1 PCRl IFR1 IER1
$1C00
DSKCNT
DATA PORT B - Disk controller I/O Bit. Bit. Bit BitBit Bit Bit Bit 0 1 2 3 4 5 6 7 $01 $02 $04 $08 $10 $20 $40 $80 Bit.s 0 & 1 are cycled to step t.he head Motor on (1) or off (0) Drive active LED on/off Write protect sense Density select (0) Density select (1) SYNC detect line
226
ADDRESS $1C01 $1C02 $1C03 $1C04 $1C05 $1C06 $1C07 $1C08 $1C09 $lCOA $lCOB $iCOC $lCOD $lCOE
NAME DATA2 DDRB2 DDRA2 TlLC2 TlHC2 TlLL2 TlHL2 T2LC2 T2HC2 SR2 ACR2 PCR2 IFR2 IER2
1541 I/O DEFINITIONS DATA PORT A - GCR data I/O to diskette DATA DIRECTION FOR PORT B DATA DIRECTION FOR PORT A TIMER 1 LOW COUNTER TIMER 1 HIGH COUNTER TIMER 1 LOW LATCH TIMER 1 HIGH LATCH TIMER 2 LOW COUNTER TIMER 2 HIGH COUNTER SHIFT REGISTER AUXILIARY CONTROL REGISTER PERIPHERAL CONTROL REGISTER INTERRUPT FLAG REGISTER INTERRUPT ENABLE REGISTER
227
APPENDIX B
229
NAME SETLDA
ADDRESS $C100
DESCRIPTION OF WHAT ROM ROUTINE DOES Turn on drive-active LED: Set bit 3 of DSKCNT ($1C00) to turn on LED for the current drive (DRVNUM; $7F). Turn on drive-active LED: Set bit 3 of DSKCNT ($1C00) to turn on drive active LED for drive 0. Turn off error LED: Store $00 in ERWORD ($026C) and in ERLED ($026D) to clear any error status and turn off drive-active/error LED. Turn on error LED: Store $80 in ERWORD ($026C) to ensure LED will continue to flash and set bit 3 of DSKCNT to turn the LED on using the LED mask from LEDMSK ($FECA). Parse string in command buffer: Clear the "don't write BAM" flagf WBAM ($02F9) and move the drive number of the last successful job from LSTDRV ($028E) ($028E) to DRVNUM ($7F). This makes the last used drive the default drive. JSR to OKERR ($E6BC) to clear any errors and move the OK error message into the error buffer. Check if the command's secondary address (ORGSA; $84) was $0F (command channel). If it was not $0F, exit with a JMP to OPEN ($D7B4). If the secondary address was $0F, JSR to CMDSET ($C2B3) to interpret the command and set up the necessary variables and registers (on return .Y=0). Move first character of command from the command buffer ($0200) to CHAR ($0275). Search the command table (CMDTBL; $FE89) for this character. If not found, exit by loading .A with a #$31 (BAD COMMAND) and jumping to the command level error handler (CMDERR; $ClC8). If found, store the command's position in the table (the command number) into CMDNUM ($022A). Check if this command must be parsed by comparing the command number with $09. If parsing is required (NEW, RENAME, SCRATCH, COPY, & LOAD), JSR to TAGCMD ($ClEE) to set tables, pointers and patterns.
LEDSON
$C118
ERROFF
$C123
ERRON
$C12C
PARSXQ
$C146
PS20
$C17A
$C181
231
NAME PS3 0
ADDRESS $C184
DESCRIPTION OF WHAT ROM ROUTINE DOES Move the address of the appropriate ROM routine from the tables, CJUMPL ($FE95) and CJUMPH ($FEA1) into $6F/$70 (TEMP). Exit with an indirect JMP to the routine via the vector at TEMP ($6F). Terminate command successfully: Clear the "don't write BAM" flag, WBAM ($02F9). Load .A with the error status from ERWORD ($026C). If non-zero, an error has occurred so exit with a JMP to CMDERR ($ClC8). If command completed with no errors, set TRACK ($80) , SECTOR ($81) , and the pointer into the command buffer, CB($A3) to $00. JSR to ERRMSG ($E6C7) and ERROFF ($C123) to clear any error status. Move current drive number from DRVNUM ($7F) to last used drive number, LSTDRV ($028E). Set the drive-busy flag, NODRV ($FF) to $00 to indicate that the drive is inactive. JSR to CLRCB ($ClBD) to zero the command buffer. JMP to FREICH ($D4DA) to clear the internal channel. Clear the command buffer ($0200-$0228] Erase any old command information by overwriting the old command with $00. Command level error handling: Set TRACK ($80) and SECTOR ($81) to $00 and JMP to CMDER2 ($E645). Simple parser: Initialize .X and the file table pointer FILTBL ($027A) to $00. Load .A with a $3A (:) and JSR to PARSE ($C268) to scan the command string for a colon. On return Z=1 if ":" found and .Y points to its position in the command. If not found, leave FILTAB=$00 and exit. If ":" was found, set FILTAB=(":" position - 1) and exit. All exits are with a JMP to SETANY ($C368) to set the drive number. Find colon (:) in command string: Load .X and .Y with $00 and .A with $3A (:) and JMP to PARSE ($C268). Tag command string, set up CMD structure and file stream pointers:
ENDCMD
$C194
SCREND
$ClA3
SCREN1
$ClAD
CLRCB
$ClBD
CMDERR
$ClC8
SIMPRS
$C1D1
$ClDB
PRSCLN
$ClE5
232
NAME
ADDRESS
DESCRIPTION OF WHAT ROM ROUTINE DOES COMMAND STRUCTURE (Bit mapped) The disk commands, RENAME, SCRATCH, NEW, and LOAD, are analyzed by this routine to determine the command structure. As the command is parsed, bits in IMAGE ($028B) are set or cleared to indicate the presence or absence of various parts of the command. Once the command has been analyzed, its structure image is checked against the correct structure for that command given in STRUCT($FEA5+) Bit Name 7 6 5 4 3 2 1 0 P1 G1 D1 N1 P2 G2 D2 N2 Meaning Wild cards present (Y=1) More than one file implied (Y=1) Drive # specified (not default) Filenamel given Wild cards present (Y=1) More than one file implied (Y=1) Drive # specified (not default) Filename2 given
NOTE: Bits 7-4 refer to file #1 Bits 3-0 refer to file #2 TAGCMD $ClEE JSR to PRSCLN ($ClE5) to locate the position of the colon (:) that is a necessary part of all these commands. e.g. R0:NEWNAME=OLDNAME (Rename) If no colon was found, load .A with $34 to indicate a bad command and exit with a JMP to CMDERR ($ClC8). If a colon was found, set FILTAB t . o the colon position - 1. Check if a comma was found before the colon (.X > 0 on return from PARSE). If a comma was found, the syntax is bad so exit via TC25 ($ClF3). Load .A with $3D (=) and JSR to PARSE ($C268). On return .x=0 indicates that no wild-card characters (? or *) were found. If any were found, set bit 6 (G1) of IMAGE ($028B) to indicate that the command applies to more than one file. In all cases, set bit 5 (D1) of IMAGE to indicate that a drive # is present and set bit 0 (N2) to indicate that a second file name is given (fixed later)
TC25 TC3 0
TC3 5
$C200
TC4 0
$C20A
233
NAME
ADDRESS $C20F
DESCRIPTION OF WHAT ROM ROUTINE DOES Increment .X and use it to set the lengths of filenames 1 and 2, FlCNT and F2CNT ($0277/8). Filename 2 will default to the same length as filename 1. Check if PARSE found any wild cards by loading PATFLG ($028A). If any found, set bit 7 (P1) of IMAGE ($028B). Set pattern flag, PATFLG ($028A) to $00 to prepare for parsing the rest of the command. Check if there is any command left to parse by checking the value of .Y set by PARSE. If .Y=0, nothing left so branch to TC75 ($C254) to check structure. Store value from .Y in filetable, FILTBL ($027A),X. Set the pointer to the start of filename #2, F2PNT ($0279) from the current value of FlCNT ($0277). Load .A with $8D (shifted CR) and JSR to PARSE ($C268) to parse the rest of the command. On return increment .X so it points to the end of the string and put the value into F2CNT ($0278). Decrement the value of .X to restore its former value. Check if any wild cards were found by PARSE in filename 2 by checking the pattern flag, PATFLG ($028A). If any were found, set 3 (P2) of IMAGE ($028B). Check if there was a second filename by checking if .X = FlCNT. If second file name is only 1 chr long, branch to TC70. Set bit 2 to indicate that the command implies more than one second file name. Set bit 1 to indicate that a second drive is specified and bit 0 to indicate that a second file name is given. EOR this with IMAGE (clears bit 0) and store the result back into IMAGE ($028B). Check IMAGE against the entry for that command (CMD number from CMDNUM, $022A) in the structure table, STRUCT ($FEA5+) If match, syntax is OK; exit with an RTS Store IMAGE in ERWORD ($026C). Load .A with a $30 to indicate a bad syntax and exit with a JMP to CMDERR ($ClC8).
$C2 2B
$C234
$C23E
TC6 0
$C245 $C24A
TC70
$C2 4C
TC7 5
$C254
TC8 0
$C26 0
234
NAME
ADDRESS
DESCRIPTION OF WHAT ROM ROUTINE DOES Parse string: On entry, .A contains the character to be found in the string, .Y points to the the character in the string where the scan is to start, and .X points into the file table, FILTAB,X. The routine scans the string for special characters "*", "?", and "," as well as the desired character. In scanning the string .Y is used as a pointer to the character in the command string being examined and .X is a pointer into the file table, FILTAB ($027B+) for storing the positions (.Y value) of the start & end of file names that are found. When a wild card (* or ?) is found, the pattern flag PATFLG ($028A) is incremented. When a comma is found, its position is noted in the file table, FILTAB and a check is made to ensure that not too many file names are present. When the special character is found or the end of the command is reached, the routine ends. If no wild cards have been found, the pattern type, PATTYP,X is set to $80. Otherwise it is left unchanged. On exit, .Y=0 and the Z flag =0 if the desired character has not been found. If it has been found, .Y = t.he position of the character and t.he Z flag is set.
PARSE PR10
$C26 8 $C26B
$C2 70
Store the desired character in CHAR ($0275). Start of loop using .Y as a counter to scan the command string. If .Y is greater than or equal to the length of the command string, CMDSIZE ($0274), branch to PR30 ($C29E). Load command string character into .A and increment .Y counter. Check if it is the desired character. If it is, branch to PR35 ($C2A0). Check if it is a wild card ("*" or "?"). If not, branch to PR25 ($C283). Increment the pattern flag, PATFLG ($028A) to count the # of wild cards. Check if it is a comma (","). If not, branch back to PR10 to get next command string character.
235
NAME
ADDRESS $C28 7
DESCRIPTION OF WHAT ROM ROUTINE DOES Transfer character count from .Y to .A and store in the file table, FILTAB+l,X ($027B,X) to indicate where the file name ends. Load .A with the pattern flag PATFLG and AND it with $7F. If the result is zero (no wild cards found), branch to PR28. Wild cards were present, so store $80 in PATTYP,X ($E7,X) to indicate this. Also store $80 into PATFLG to zero the count of wild cards but indicate that there are wild cards in the string. Increment .X (counts number of files & points into FILTAB) and compare it to $04 (the maximum number of file names allowed in a command string). If the maximum has not been exceeded, branch back to PR10 to continue the scan. Load .Y with $00 to indicate that the desired character was not found. Store a copy of the command size, CMDSIZ ($0274) into the file table, FILTAB+l,X ($027B,X). Load the pattern flag, PATFLG and AND it with $7F. If the result is 0, no wild cards have been found so branch to PR4 0. Wild cards were present, so store $80 in PATTYP,X ($E7,X) to indicate this. Transfer character count from .Y to .A. This sets the Z flag if the desired character has not been found. Initialize command tables & pointers Find length of command string and zero all variables and pointers. Load .Y from BUFTAB+CBPTR ($A3). This is the length of the command that was sent from the computer. If .Y=0, branch to CS08 ($C2CB). Decrement .Y and if .Y=0, branch to CS07 ($C2CA). Load .A with the character from the command buffer, CMDBUF,Y ($0200,Y) and see if it is a carriage return ($0D). If it is, branch to CS08 ($C2CB). Decrement .Y and load the next character from t.he command buffer. If t.his is a carriage return ($0D), branch to CS08 ($C2CB). If not, increment .Y Increment .Y pointer int.o command buffer
$C292
PR28
$C29 9
PR30 PR35
$C29E $C2A0
CMDSET
$C2B3
$C2B7 $C2BA
$C2C1
CS0 7
$C2CA
236
NAME CS08
ADDRESS $C2CB
DESCRIPTION OF WHAT ROM ROUTINE DOES Store length of command (.Y) in CMDSIZ ($027B). Compare length (.Y) with the maximum allowable length ($2A) to set the carry flag. Load .Y with $FF. If command length was OK, branch to CMDRST. Command over-size so set command number ($02 2A) to $FF, load .A with $32 to indicate a TOO LONG ERROR and exit with a JMP to CMDERR ($ClC8). Zero all important variables & pointers: BUFTAB+CBPTR ($A3) REC ($0258) FILTBL ($027A-7F) TYPE ($024A) ENTSEC ($00D8-DC) TYPFLG ($0296) ENTIND ($00DD-E1) FlPTR ($00D3) FILDRV ($00E2-E6) F2PTR ($0279) PATTYP ($00E7-EB) PATFLG ($028A) FILTRK ($0280-84) ERWORD ($026C) FILSEC ($0285-89) Set first drive & table pointers: Change pointer to end of the first file name (FlCNT; $0277) to point to the end of the second file name (use value from F2CNT; $0278). Store $01 in F2CNT and in F2PTR ($0279) to clear these variables Set up all drives from F2CNT: Load .Y with last drive used from LSTDRV ($028E) and .X with $00. Save .X into FlPTR ($D3). Load .A from FILTAB,X ($027A,X) so it points to the start of the Xth file specified in t.he command string. JSR to SETDRV ($C33C) to set drive #. On return .Y contains the drive number specified in the command or the default. NOTE: Bits represent drives (If bit 7 set, use default. Bit 0 = drive #0/1) Recover .X pointer from FlPTR. Store .A in FILTAB,X ($027A,X). Move drive # from .Y to .A and store in FILDRV,X ($027A,X) Increment .X pointer and compare it to F2CNT ($0278) to see if any more files were specified. If more, branch back to AD10 to do the next one. If not, RTS Set drive # from text or default to 0 On entry and exit .A is an index into the command buffer. On entry .Y is the default drive #. On exit it is the drive specified or the default drive.
$C2D4
CMDRST
$C2DC
ONEDRV
$C312
ALLDRS AD10
$C3 20 $C3 25
$C32A
$C32D $C3 35
SETDRV
$C3 3C
237
NAME
DESCRIPTION OF WHAT ROM ROUTINE DOES Move pointer into command buffer from .A to .X Load .Y with $00 to ensure that the 15 41 1 s default drive is ALWAYS DRIVE #0 Load .A with $3A (:) to prepare to hunt for a colon (drive # is just before :). Check for colon in command string at CMDBUF+1,X ($0201,X). Picks up syntax: X#:FILENAME as in S0:JUNK If found, branch to SD40. Check for colon in command string at CMDBUF,X ($0200,X). Picks up default drive syntax as in S:JUNK If colon NOT found, branch to SD40. Colon found so increment pointer (.X) so it points to the first character in the filename. Transfer .Y to .A to set up the default drive AND .A with $01 to ensure drive number in ASCII form ($30 or $31) is converted to $00 or $01. Transfer .A to .Y to restore drive #. Transfer .X to .A to restore index into command string and exit with an RTS. Set drive # from command string with the syntax: X#:FILENAME. On entry .X points to the # in the command string. Load .A with the drive number (in ASCII) from CMDBUF,X ($0200,X). Increment .X twice so it points to the first character in the file name. Compare .A (drive number) to $30 (dr#0). If equal, branch back to SD22 ($C34D) Compare .A (drive number) to $31 (dr#l). If equal, branch back to SD22 ($C34D) If not equal, must be default drive so branch back to SD20 ($C34C). Set drive # from command string with the syntax: X#,FILE or xx=FILE. Transfer t.he drive number from .Y to .A. OR .A with $80 to set t.he default drive bit and then AND the result with $81 to mask off any odd bits. Branch back to SD2 4 ($C34F) to terminate routine. Set drive # from any configuration: Set IMAGE ($028B) t . o $00. Load .Y from FILTBL ($027A). Load .A with the (CB),Y character from the command string and JSR to TST0V1 to test for a "0" or "1".
$C3 46
SD40
SD50
$C361 $C362
SETANY SA0 5
238
NAME
ADDRESS $C371
DESCRIPTION OF WHAT ROM ROUTINE DOES On return .A contains $00 or $01 if the drive was specified. If not specified, .A is $80 or $81. If the drive number was given, branch to SA20 ($C388). Increment the pointer into the command string (.Y). Compare the pointer value to the command length (CMDSIZ; $0274) to see if we are at the end. If we are, branch to SA10 ($C383). If not "0" or "1", set the pointer (.Y) to the end of the command less one (so it points to the last character before the RETURN to pick up things like V0) and loop back to SA05 ($C370). Decrement IMAGE (becomes $FF) to flag a default drive status and load .A with a $00 to ensure default to 0 on the 1541. AND the drive number in .A with $01, and store the result in the current drive number, DRVNUM ($7F). Exit with a JMP to SETLDS ($C100) to turn on the drive active light. Toggle drive number: Load .A with current drive number from DRVNUM ($7F). EOR it with $01 to flip bit. #0, AND it with $01 to mask off the bits 1-7, and store the result back in DRVNUM ($7F). Set pointers to one file stream and check type: Zero .Y and load .A with the pointer to the end of file name 1 (FlCNT? $0277). Compare .A to the pointer to the end of file name 2 (F2CNT; $0278). If equal, there is no second file so branch to FS15 ($C3B8). Decrement F2CNT and load .Y with its value. Load .A with the pointer to the filetype in the command string from FILTAB,Y ($027A,Y). Transfer this value to .Y and use it to load the file type into .A from the command string (CB),Y. Load .Y with $04 (the number of file types less 1). Loop to compare the file type in .A to the list of possible file types,TYPLST,Y When a match occurs, branch to FS15 ($C3B8). If no match found this time, decrement .Y and, if there are any file types left, loop back to FS10. NOTE: if no match occurs, file assumed to be DEL.
$C377
$C37D
SA10 SA2 0
TOGDRV
$C38F
FSlSET
$C398 $C39D
$C3A2
239
DESCRIPTION OF WHAT ROM ROUTINE DOES Transfer file type from .Y to .A and store in TYPFLG ($0296). Test if character in .A is ASCII 0 or 1: Compare .A to ASCII "0" ($30) and then to ASCII "1" ($31). If a match in either case, branch to T0V1. OR .A with $80 to set bit 7 to indicate no match was found. AND .A with $81 to convert ASCII to HEX and preserve bit 7. Determine optimal search for LOOKUP and FINFIL: Zero TEMP ($6F) and DRVFLG ($028D) and push $00 onto the stack. Load .X with value from F2CNT ($0278). Note: TEMP is the drive mask. Pull .A from the stack and OR it with the value in TEMP ($6F). Push the result back onto the stack. Load .A with $01 and store this value in TEMP. Decrement .X (pointer into file table). If no files left (.X=$FF), branch to $OS30. Load .A with the drive for the file from FILDRV,X ($E2,X). If this file uses the default drive (bit 7 set), branch to OS15. Do two ASL's on TEMP ($6F). Do one LSR on .A. If drive number in .A was 1, the carry bit is set so branch back to OSlO. Since drive number was 0, do one ASL on TEMP ($6F) and branch back to OSlO. Pull .A from the stack and transfer this value to .X. Use this value as an index and load .A with a value from the search table, SCHTBL-l,X ($C43F,X). Push this value onto the stack, AND it with $03, and store the result in DRVCNT ($028C). Pull the original value off the stack and do an ASL. If bit 7 is not set, branch to OS40. If bit 7 was set, load A. with the value from FILDRV ($E2). AND .A with $01 and store the result in DRVNUM ($7F). Load .A with DRVCNT($0 28C) and if $00, only one drive is addressed so branch to OS60. JSR to AUTOI ($C63D) to check the drive status and initialize it if necessary. On return, branch to OS70 if the drive is ready (.A=0). Drive is not ready so load .A with $74 to indicate the drive is not ready and JSR to CMDERR ($ClC8). 240
T0V1
$C3C7
OPTSCH
$C3CA
OSlO
$C3D5
$C3E0
OS15
$C3E8 $C3EB
OS3Q
$C3EF
$C4 09
OS4 5
$C41B
NAME OS5Q
ADDRESS $C4 20
DESCRIPTION OF WHAT ROM ROUTINE DOES JSR to TOGDRV ($C38F) to switch drives and JSR to AUTOI ($C63D) to check this drive's status and init it if necessary. On return, save the processor status on the stack. JSR to TOGDRV to switch back to the first drive. On return, pull the status back off the stack. If the second drive is active, branch to 0s70. Since second drive is not active, set DRVCNT ($020C) to $00 to indicate only one drive addressed and branch to OS70. JSR to AUTOI ($C63D) to check the drive status and initialize it if necessary. On return, branch to OS45 if the drive is NOT ready (.A<>0). Teminate routine with a JMP to SETLDS ($C100) to turn on the drive active LEDs Do a ROL on the value in .A and JMP to OS3 5 ($C400). Search BYTES BYTES BYTES BYTES Table $00, $80, $01, $01, $81, $81, $42, $42, $41 $01, $01 $81, $81 $42, $42
$C4 2D
OS6O
$C434
LOOKUP LK0 5
$C4 4F $C4 52
LK2 0
$C4 70 $C4 73
Look up all files in command string in the directory and fill tables with info. JSR to OPTSCH to find optimal search pattern and turn on drive active LEDs. Store $00 in DELIND ($0292) , to indicate that we are NOT looking for a deleted or unused directory entry. But, for one or more specific file names. JSR to SRCHST ($C5AC) to start the search process. On return, branch to LK25 if a valid file name was found (Z flag =0) Since no file name was found, decrement DRVCNT ($028C), the number of drive searches to be made. If any more left (DRVCNT >= 0), branch to LK15. Since there are no more drive searches to be done, exit with an RTS. Store $01 in DRVFLG ($028D) and JSR to TOGDRV ($C38F) to switch drives. JSR to SETLDS ($C100) to turn on the other LED. Then JMP back to LK05 to begin the search on the other drive. JSR to SEARCH ($C617) to read the next valid file name in the directory. On return, branch to LK30 to abandon the search if a valid file name was NOT found (Z flag = 1).
241
NAME LK2 5
ADDRESS $C475
DESCRIPTION OF WHAT ROM ROUTINE DOES JSR to COMPAR ($C4D8) to compare the list. of files found with list of those required. On return, FOUND ($028F) is 0 if all files have NOT been found. Load .A with the value from FOUND. If not all the files have been found yet, branch to LK26 to continue the search. All files have been found so exit from the routine with an RTS. Load .A with the value from ENTFND ($0253) to check if the most recent compare found a match. If not (.A=$FF), branch to LK20 to search directory for another valid file name. If a match was found, branch back to LK25 to try again. Load .A with the value from FOUND. If not all the files have been found yet, branch to LK10 to continue the search. All files found so exit with an RTS. Find next file name matching any file in stream & return with entry stuffed into tables: JSR to SRRE ($C604) to set up and read in the next block of directory entries. If no files found, branch to FF10. If files were found, branch to FF25. Store $01 in DRVFLG ($028D) and JSR to TOGDRV ($C3 8F) to switch to the other drive. JSR to SETLDS ($C100) to turn on the new drive active light. Find starting entry in the directory: Store $00 in DELIND ($0292), to indicate that we are NOT looking for a deleted or unused directory entry. But, for one or more specific file names. JSR to SRCHST ($C5AC) to start the search process. On return, branch to FF25 if a valid file name was found (Z flag =0) Store .A value in FOUND ($028F). Load .A from FOUND ($028F). If non-zero, all files found so branch to FF40 & exit Since there is nothing more on this drive, decrement DRVCNT by 1. If any more drives left, branch to FF15 to try the other drive. If none left, do an RTS Continue scan of directory: JSR to SEARCH ($C617) to retrieve the next valid file name from the directory.
LK3 0
$C485 $C4 8A
FFRE
FF15
$C49 2
FFST
$C49D
FNDFIL
$C4B5
242
NAME
ADDRESS $C4B8
DESCRIPTION OF WHAT ROM ROUTINE DOES On return, branch to FF10 if no more entries available on this drive. JSR to COMPAR ($C4D8) to see if any of the names found match the ones needed. On return, load .X from ENTFND ($0253). If a match on a name was found (.X<128), branch to FF30 to check the file type. If no match found (.X>127), load .A with the value from FOUND($028F) to check if all files have been found. If not(.A=0), branch back to FNDFIL to load another name from the directory. If .A<>0, all files have been found so branch to FF40 and exit with an RTS. Check the file type flag, TYPFLG($0296). If it is $00, there is no file type restriction so branch to FF40 and exit. Load the file pattern type from PATTYP,X ($E7,X), AND it with the file type mask #$07, and compare it to the value in TYPFLG ($0296). If the file types do not match, branch back to FNDFIL to continue the search. Terminate the routine with an RTS. Compare all file names in command list with each valid entry in directory. Any matches are tabulated. Set the found-entry flag, ENTFND ($0253) to $FF and zero the pattern flag PATFLG ($028A). JSR to CMPCHK ($C589) to check the file table for unfound files. If there are unfound files (Z flag = 1), branch to CP10 to begin comparing. Terminate routine with an RTS. JSR to CC10 ($C594) to set F2PTR ($0279) to point to the next file needed on this drive. On return, branch to CP02 to exit if no more files needed on this drive. Load .A with the current drive number from DRVNUM ($7F) and EOR it with the drive number specified for t.he file, FILDRV,X ($E2,X). LSR the result. If the carry flag is clear, the drive number is correct for this file so branch to CP20 to find the name in the directory list. AND the value in .A with $40 to check if we are to use the default drive (NOTE: $40 rather than $80 because of the LSR). If we can not use the default drive, branch back to CP05 to set up the next file name on our list of files needed.
FF2 5
$C4BA $C4BD
FF4 0
$C4D7
COMPAR
$C4D8
CP0 2 CP05
$C4E6 $C4E7
CP10
$C4EC
$C4F3
243
NAME
ADDRESS $C4F7
DESCRIPTION OF WHAT ROM ROUTINE DOES Compare DRVCNT ($028C) with $02. If equal, don't use default drive so branch back to CP05. At this point we have a match on the drive numbers so check the directory entries to see if we can match a name. Load .A with the pointer to the position of the required file name from FILTBL,X ($027A,X) and transfer this value to .X. JSR to FNDLMT to find the end of the command string. On return, load the pointer into the directory buffer (.Y) with $03 (so it points past the file type, track and sector) and JMP to CP33. Compare the .Xth character in the command string (the required filename) with the .Yth character in the directory buffer (the directory entry). If equal, branch to CP32 to set up for the next character. No exact match so check if the command buffer character is a "?" which will match any character. If not, branch to to CP05 to try the next file name. Compare the character we just used from the directory buffer with $A0 to see if we've reached the end of the name. If we have, branch to CP0 5 to try the next file name. Increment .X and .Y Compare .X with the length of the command string, LIMIT ($0276). If we are at the end, branch to CP34. Check if the new character in the file name, CMDBUF,X ($0200,X) is a "*". If it is, it matches everything so branch to CP4 0 to tabulate this match. If not a "*", branch to CP30 to keep on matching. Compare .Y to $13 to see if we are at the end of the name in the directory. If we are, branch to CP40 to tabulate. If not at the limit, check the character in the directory entry name. If it isn't an $A0, we did not get to the end of the name so branch back to CP0 5 to try again The filenames match so keep track of it by storing the pointer to the entry from F2PNT ($0279) into ENTFND ($0253). Get the file type pattern ($80,$81,etc) from PATTYP,X ($E7,X), AND it with $80, and store it in PSTFLG.
CP20
$C4FE
$C502
CP3 0
$C50A
$C511
$C515
CP3 2 CP33
CP40
$C535 $C53B
244
NAME
DESCRIPTION OF WHAT ROM ROUTINE DOES Get the pointer to the directoryentry from INDEX ($0294) and store it in the entry index, ENTIND,X ($DD,X). Get the sector on track 18 on which the entry is stored from SECTOR ($81) and store it in, ENTSEC,X ($D8,X). Zero .Y and load .A with the file type of this directory entry from (DIRBUF),Y ($94),Y. Increment .Y. Save the type on the stack. AND the type with $40 to see if this is a locked file type, and store the result in TEMP ($6F). Pull the file type off the stack and AND it with $DF ($FF-$20). If the result is > 127 (the replacement bit not set), branch to CP42 OR the result with $20. AND the result with $27 and OR it with the value stored in TEMP ($6F) and store the final result back in TEMP. Load .A with $80, AND .A with the file pattern type from PATTYP,X ($E7,X), OR the result with the value in TEMP ($6F), and store the final result back in PATTYP,X. Load .A with the file's drive number from FILDRV,X ($E2,X). AND it with $80 to preserve the default drive bit, OR it with the current drive number, DRVNUM ($7F) and store the result back into FILDRV,X ($E2,X). Move the file's first track link from (DIRBUF),Y(.Y=1) to FILTRK,X ($0280) and increment .Y. Move the file's first sector link from (DIRBUF),Y(.Y=2) to FILSEC,X ($0285). Check the current record length, REC ($0258). If NOT $00, branch to CMPCHK. Set .Y to $15 and move the file entry's record size from (DIRBUF),Y to REC. Check table for unfound files Set all-files-found flag, FOUND ($028F) to $FF. Move the number of files to test from F2CNT ($0278) to F2PTR ($0279). Decrement the file count, F2PTR ($0279). If any files left, branch to CC15. If none left, exit with an RTS. Load .X with the number of the file to test from F2PTR. Load .A with the file's pattern type from PATTYP,X ($E7,X). If file has not been found yet (bit 7 is still set) abort search by branching to CC20.
CP4 2
$C56A
CC10 CC15
$C59 4 $C59A
245
NAME
ADDRESS
DESCRIPTION OF WHAT ROM ROUTINE DOES Load .A with the file's first track link from FILTRK,X ($0280,X). If non-zero, the file has been found, so branch back to CC10 to test the next file. Load .A with $00 and store it in the all-files-found flag, FOUND ($028F) to indicate that all files have NOT been found and exit with an RTS. Initiate search of directory: Returns with valid entry (DELIND=0) or with the first deleted entry (DELIND=1) Load .Y with $00 and store it in DELSEC. ($0291) . Decrement .Y to $FF and store it in the founa-an-entry flag, ENTFND ($0253) . To start search at the beginning of the directory, set TRACK ($80) to $12 (#18) (from $FE79) and SECTOR ($81) to $01. Also store $01 in last-sector-in-file flag, LSTBUF ($0293) . JSR to OPNIRD ($D475) to open the internal channel (SA=16) for a read and to read in the first one or two sectors in the file whose T/S link is given in TRACK ($80) and SECTOR ($81). Test LSTBUF ($0293) to see if we have exhausted the last sector in the directory file. If not (LSTBUF <> $00), branch to SR15. Exit with an RTS. Set the file count, FILCNT ($0295) to $07 to indicate that there are 8 entries (0-7) left to examine in the buffer. Load .A with $00 and JSR to DRDBYT to read the first byte in the sector (the track link). On return store this value into LSTBUF ($0293). This sets LSTBUF to $00 if there are no more blocks left in in the directory file. JSR to GETPNT ($D4E8) to set the directory pointer, DIRBUF ($94/5) to the data that was just read into the active buffer, BUFTAB,X ($99/A,X).
CC20
$C5A6
SRCHST
$C5AC
$C5B5
$C5C1
SR10
$C5C4
SR15
SR20
$C5D7
NOTE: DIRBUF does NOT point to the start of the data buffer ($0300, $0400,...). It points to the first data byte ($0302, $0402,...). As the entries are examined, it is updat.e to point to the start of the entry ($0x02, $0x22, $0x42,...).
246
NAME
DESCRIPTION OF WHAT ROM ROUTINE DOES Decrement the entry count, FILCNT and load .Y with $00 to begin examination of the first directory entry. Test the entry's file type in (DIRBUF),Y If non-zero, this is NOT a deleted or blank entry so branch to SR30. Process a scratched or blank entry Test DELSEC ($0291) to see if a deleted entry has already been found. If it has (DELSEC <> $00), branch to SEARCH($C617) This is first deleted entry so JSR to CURBLK ($DE3B) to set up the current sector in SECTOR ($81). Save the sector number in DELSEC ($0291) . Load .A with the low byte of the pointer to the start of this entry (its position in the data buffer) from DIRBUF ($94). Load .X with the current value of DELIND ($0292). This sets the Z flag to 1 if only valid entries are desired. Store the pointer in .A into DELIND. If the Z flag is set, we need valid entries, not deleted ones, so branch to SEARCH to continue the search. We wanted a deleted entry and we found one so terminate routine with an RTS. We have found a valid entry. Check if we are looking for one by comparing DELIND ($0292) to $01. If not equal, we want a valid entry so branch to SR50. If DELIND = 1, we want a deleted entry, not a valid one, so branch to SEARCH to continue the quest! Re-enter the directory search: Set TRACK ($80) to $12 (#18) from $FE85 Set SECTOR ($81) from the last directory sector used, DIRSEC ($0290) . JSR to OPNIRD ($D475) to open the internal channel (SA=16) for a read and to read in the first one or two sectors in the file whose T/S link is given in TRACK ($80) and SECTOR ($81). Load .A with the pointer INDEX ($0294) that points to the start of the last entry we were examining and JSR to SETPNT ($D4C8) to set the DIRPNT ($94/5) t . o point to the start of the entry.
$C5E8
$C5F0
$C602
SRRE
$C604 $C60E
$C611
247
NAME
ADDRESS
DESCRIPTION OF WHAT ROM ROUTINE DOES Continue search of entries: Set found-entry flag, ENTFND ($0253) to $FF. Load .A with number of entries left in the buffer from FILCNT ($0295). If none left, branch to SR40 to get the next buffer of directory entries. There is at least one more entry left in this buffer so load .A with $20* (the # of characters in each entrv) and JSR to INCPTR ($DlC6) to set DIRPTR ($94/5) to point to the start of the next entry. JMP to SR20 ($C5D7) to process it. Get next buffer of entries: JSR to NXTBUF ($D44D) to read in the next directory sector and JMP to SR10 to begin processing it. We have found a valid entry so save how far we got and return. Save low byte of the pointer to the entry, from DIRBUF($94) in INDEX($0294). JSR to CURBLK ($DE3B) to store the sector we are checking in SECTOR ($81). Save the current sector number from SECTOR ($81) in DIRSEC ($0290) and RTS. Check drive for active diskette, init if needed. Return no drive status. Test auto-initialization flag, AUTOFG ($68). If AUTOFG <> 0, auto-init is disabled so branch to AUT02 ($C669). Load .X with the current drive number from DRVNUM ($7F). Test whether the diskette has been changed by doing an LSR on the write-protect-change flag for the current drive, WPSW,X ($lC/D). If the carry flag, C, is clear, the disk has not been changed so branch to AUT02. Load .A with $FF. Store this value as the job return code in JOBRTN ($0298) . JSR to ITRIAL ($D00E) to do a SEEK to the current drive to determine if a diskette is present. Load .Y with $FF (default to true). Compare the value in return job code in .A with $02. If equal, NO SYNC was found so branch to AUTOl to abort. Compare the value in return job code in .A with $03. If equal, NO HEADER was found so branch to AUTOl to abort. Compare the value in return job code in .A with $0F. If equal, NO DRIVE was found so branch to AUTOl to abort. Seems OK so load .Y with $00.
SEARCH
$C617
$C621
SR40
$C629
SR50
$C62F
$C634 $C637
AUTOI
$C63C $C641
248
NAME AUTOl
ADDRESS $C6 5F
DESCRIPTION OF WHAT ROM ROUTINE DOES Load .X with the current drive number DRVNUM ($7F). Transfer the value of .Y into .A ($00 if OK;$FF if BAD) and store in the current drive status, NODRV,X ($FF,X). If status is bad (not $00), branch to AUT02 to abort. JSR to INITDR ($D042) to initialize the current drive. Load .A with the current no-drive status and terminate routine with an RTS. NOTE: Z flag set if all is OK. Transfer filename from CMD to buffer: On entry, .A=string size; .X=starting index in command string; .Y=buffer # Save .A (string size) on the stack. JSR to FNDLMT ($C6A6) to find the limit of the string in the command buffer that is pointed to by .X. JSR to TRCMBF ($C688) to transfer the command buffer contents from .X to LIMIT to the data buffer whose number is in .Y Restore the string size into .A from the stack. Set the carry flag and subtract the maximum string size, STRSIZ ($024B). Transfer the result from .A to .X. If the result is 0 or negative, the string does not need padding so branch to TN20. String is short and needs to be padded so load .A with $A0. Loop to pad the string in the directory buffer with .X $A0's. Terminate routine with an RTS. Transfer CMD buffer to another buffer: .X=index to first chr in command buffer LIMIT=index to last chr+1 in CMD buffer .Y=buffer#. Uses current buffer pointer. Multiply .Y by 2 (TYA;ASL;TAY). Use current buffer pointers, BUFTAB,Y ($99/A,Y) to set the directory buffer pointers, DIRBUF ($94/5). Zero .Y (index into directory buffer) Move character from CMDBUF,X ($0200,X) to (DIRBUF) ,Y ; ($94) ,Y. Increment .Y. If .Y equals $00, branch to TR20 to abort. Increment .X. If .X < LIMIT ($0276) branch back to TR10 to do next character Terminate routine with an RTS.
TRNAME
TN10 TN2 0
$C681 $C687
TRCMBF
$C688 $C68B
TR10
TR2 0
$C6A5
249
NAME
ADDRESS
DESCRIPTION OF WHAT ROM ROUTINE DOES Find the limit(end) of the string in the command buffer that is pointed to by X Zero the string size, STRSIZ ($024B). Transfer the starting pointer from .X to .A and save it on the stack. Load .A with the Xth command string character, CMDBUF,X ($0200,X). Compare the character to a ",". If they match, we're at the end. Branch to FL10. Compare the character to a "=". if they match, we're at the end. Branch to FL10. Increment STRSIZ ($024B) and .X Check if the string size, STRSIZ, has reached the maximum size of $0F (#15). If it has, branch to FL10 to quit. Compare .X to the pointer to the end of the command string, CMDSIZ ($0274). If we're NOT at the end. Branch to FL05. Store the .X value (the last character plus 1) into LIMIT ($0276). Pull the original .X value off the stack into .A and transfer it to .X Terminate routine with an RTS. Get file entry from directory: (called by STDIR and GETDIR) Save secondary address, SA ($83) on the stack. Save the current channel#, LINDX ($82) on the stack. JSR to GNSUB ($C6DE) to get a directory entry using the internal read channel SA=$ll(#17). Pull the original SA and LINDX values from the stack and reset these variables Terminate the routine with an RTS. Get file entry subroutine: Set current secondary address, SA ($83) to $11 (internal read secondary address) JSR to FNDRCH ($D0EB) to find an unused read channel. JSR to GETPNT ($D4E8) to set the directory buffer pointer, DIRBUF ($94/5) from the pointer to the currently active buffer using values from BUFTAB ($30/1). Test the found entry flag, ENTFLG($0253) to see if there are more files. If there are more (ENTFLG > 127), branch to GN05. No more entries so test DRVFLG ($028D) to see if we have the other drive to do. If DRVFLG <> 0, branch to GN050 to do the other drive.
FNDLMT FL05
FL10
GETNAM
GNSUB
$C6E8 $C6ED
250
NAME
DESCRIPTION OF WHAT ROM ROUTINE DOES JSR to MSGFRE ($C806) to send the BLOCKS FREE message. Clear carry bit and exit with an RTS. Test drive flag, DRVFLG ($028D). If $00, branch to GN10. Decrement drive flag, DRVFLG ($028D). If not $00, branch to GN051 to do a new directory. Decrement drive flag, DRVFLG ($028D). JSR to TOGDRV ($C3 8F) to switch drives, JSR to MSGFRE ($C806) to send the BLOCKS FREE message. Set the carry flag and exit with a JMP to TOGDRV ($C38F) to switch drives. Load .A with $00 and zero the hi byte of the number of blocks counter, NBTEMP+1 ($0273) and the drive flag DRVFLG($028D) JSR to NEWDIR ($C7B7) to begin a new directory listing. Set the carry flag and exit with an RTS. Load .X with $18 (#24) , the length of an entry in a directory listing e.g.* 114 "PROGRAM FILENAME" PRG Load .Y with $lD, the position of the hi byte of the # of blocks in the file. Load .A with the hi byte of the # of blocks in the file. Store this into the hi byte of the block counter, NBTEMP+1 ($0273). If zero, branch to GN12. Load .X with $16 (#22) the directory length less 2. Decrement Y so it points to the position of the lo byte of the # of blocks in the file. Load .A with the lo byte of the # of blocks in the file. Store this into the lo byte of the block counter, NBTEMP ($0272). Compare .X to $16 (#22) the directory length less 2. If they are equal, branch to GN14. Compare .A (the lo byte of the blocks) with $0A (#10). If .A<10 branch to GN14 Decrement .X (we will need less padding since # of blocks is at least 2 digits. Compare .A (the lo byte of the blocks) with $64 (#100). If A<100 branch to GN14 Decrement .X (we will need less padding since # of blocks is at least 3 digits.
GN0 5 GN0 50
GN051
GN10
251
NAME GN14
DESCRIPTION OF WHAT ROM ROUTINE DOES JSR to BLKNB ($C7AC) to clear the name buffer for the next entry. On return Y=0 Load .A with the file type from the directory buffer (DIRBUF),Y and save the file type onto the stack. Do an ASL of the value in .A to set the carry bit if this is a valid file that has not been closed, (see BCS $C764) If .A<128, branch to GN15.
NOTE: The branch at $C742 and the code following is what produces the PRG<, SEQ<, etc. file types. Note that these file types are LOCKED and can't be SCRATCHED! The locking and unlocking of files is NOT supported by any Commodore DOS. To lock a file, change its file type in its directory entry from $80, $81, etc to $C0, $C1, etc. Reverse the process for unlocking $C74 5 $C74 7 GN15 $C74A $C74E $C7 54 $C755 $C7 5B $C7 5C $C762 $C764 $C76 6 GN2 0 $C768 $C76B $C771 GN2 2 $C7 73 $C77E $C7 80 Load .A with a $3C (a "<"). Store this value into the name buffer NAMBUF+1,X ($02Bl,X). Pull the file type off the stack and AND it with $0F to mask off the higher bits. Transfer it to .Y to use as an index. Move last character in file type name from TP2LST,Y ($FEC5,Y) to the name buffer, NAMBUF,X ($02Bl,X). Decrement .X Move middle character in file type name from TPlLST,Y ($FEC0,Y) to the name buffer, NAMBUF,X ($02Bl,X). Decrement .X Move first character in file type name from TYPLST,Y ($FEBB,Y) to the name buffer, NAMBUF,X ($02Bl,X). Decrement .X twice If carry bit is set (indicates valid entry? see $C742) branch to GN20. Load .A with $2A (a "*") to indicate an improperly closed file. Store the "*" in NAMBUF+l,X ($02Bl,X). Store a shifted space, $A0 in the buffer (between name & type) and decrement .X Load .Y with $12 (#18) so it points to the end of the name in the dir buffer. Loop to transfer the 16 characters in the file name from the directory buffer to the name buffer. Load .A with $22 (a ) Store quotation mark before the name.
252
NAME GN3 0
ADDRESS $C7 83
DESCRIPTION OF WHAT ROM ROUTINE DOES Loop to scan up the name looking for a quote mark($22) or a shifted space($A0). When either character is found or the end of the name is reached, store a $22 (quote mark) at that location. Then AND any remaining characters in the name with $7F to clear bit 7 for each one. JSR to FNDFIL ($C4B5) to find the next entry. On return, set the carry bit. Terminate the routine with an RTS. Blank the name buffer: Load .Y with $lB, the length of the name buffer, and .A with $20, a space. Loop to store $20's in all locations in the name buffer, NAMBUF ($02Bl-CB) Terminate the routine with an RTS. New directory in listing JSR to BAM2X ($F119) to set BAM pointer in buffer 0/1 tables and leave in .X JSR to REDBAM ($F0DF) to read in the BAM to $0700-FF if not already present. JSR to BLKNB ($C7AC) to blank the name buffer, NAMBUF ($02Bl-CB). Set TEMP ($6F) to $FF Set NBTEMP ($0272) to the current drive number from DRVNUM ($7F) Set NBTEMP+1 ($0273) to $00 Load .X with the position of the read BAM job in the queue from JOBNUM ($F9). Set high byte of the pointer to the directory buffer, DIRBUF ($94/5) using a value (3,4,5,6,7,7) from BUFIND,X($FEE0) Set low byte of the pointer to the directory buffer, DIRBUF ($94/5) using the value ($90) from DSKNAM ($FE88). DIRBUF now points to the start of the disk name in the BAM buffer ($0x90) Load .Y with $16 (#22), the name length. Load .A with character, (DIRBUF),Y and test if it is a shifted blank ($A0). If not, branch to ND20. Since it is not a shifted blank, load .A with a $31 (ASCII "1") for version #1. BYTE $2C here causes branch to ND20. Load .A with character, (DIRBUF),Y and test if it. is a shifted blank ($A0) . If not, branch to ND20. Since it is not a shifted blank, load .A with a $20 (ASCII space). Store the character in .A into the name buffer, NAMBUF+2,Y ($02B3,Y).
NEWDIR
ND10
ND15
ND2 0
$C7ED
253
NAME
DESCRIPTION OF WHAT ROM ROUTINE DOES If more characters left (.Y>=0) branch back to ND15. Store a $12 (RVS on) in NAMBUF ($02B1) Store a $22 (quote) in NAMBUF+1 ($02B2) Store a $22 (quote) in NAMBUF+18($02C3) Store a $20 (space) in NAMBUF+19($02C4) Terminate routine with an RTS. Set up message "BLOCKS FREE" JSR to BLKNB ($C7AC) to clear the name buffer. Load .Y with $0B (message length -1). Loop using .Y as index to move message from FREMSG,Y ($C817,Y) to NAMBUF,Y ($02Bl,Y). Terminate routine with a JMP to NUMFRE ($EF4D) to calculate the number free. Message "BLOCKS FREE" SCRATCH ONE OR MORE FILES - * - * -
MSGFRE
FREMSG
$C817
JSR to FSlSET ($C398) to set up for one file stream. JSR to ALLDRS ($C320) to all drives needed based on F2CNT. JSR to OPTSCH ($C3CA) to determine best sequence of drives to use. Zero file counter, R0 ($86) JSR to FFST ($C49D) to find the first directory entry. If not successful, branch to SC30.
NOTE: THE FOLLOWING CODE PREVENTS FREEING THE SECTORS OF AN UNCLOSED FILE. SC15 $C83 5 $C838 JSR to TSTCHN ($DDB7) to test for active files from index table. If file active (carry clear), branch to SC25.
NOTE: THE FOLLOWING CODE PREVENTS THE SCRATCHING OF A LOCKED FILE (BIT 6 OF THE FILE TYPE SET). $C83A $C8 3C $C83E $C84 0 $C842 Load .Y with $00. Load .A with file type from (DIRBUF),Y ($94,Y). AND the file type with $40 to test if it is a locked file (bit 6 of filetype set) If a locked file, branch to SC25. JSR to DELDIR ($C8B6) to delete the directory entry. Stores $00 as the file type and rewrite the sector on disk.
254
NAME
DESCRIPTION OF WHAT ROM ROUTINE DOES Load .Y with $13 (#19). Test whether this is a relative file by loading .A wit.h 19th character of the entry (the track of the side-sector pointer for a REL file) from (DIRBUF),Y If $00, not a REL file so branch to SC17 Store track pointer into TRACK ($80) . Increment .Y and move sector pointer from (DIRBUF),Y into SECTOR ($81). JSR to DELFIL ($C87D) to free the side sectors by updating and writing the BAM
NOTE: TKE FOLLOWING CODE PREVENTS FREEING THE SECTORS OF A FILE IF ITS REPLACEMENT WAS INCOMPLETE (BIT 5 SET). SC17 $C855 $C85A $C85C $C85E $C863 $C868 SC20 SC2 5 $C86B $C86D $C8 70 SC3 0 $C872 $C876 $C87A DELFIL $C87D $C8 80 $C8 83 $C8 86 Load .X with the directory entry counter ENTFND ($0253) and .A with $20. AND .A with the file pattern type in PATTYP,X ($E7,X) to check if this is an opened but unclosed file. If unclosed file, branch to SC20. Move initial track link from FILTRK,X ($0280,X) into TRACK ($80). Move initial sector link from FILSEC,X ($0285,X) into SECTOR ($81). JSR to DELFIL ($C87D) to free the file blocks by updating and writing the BAM Increment the file counter, R0 ($86). JSR to FFRE ($C48B) to match the next filename in the command string. If a match found, branch to SC15 All done. Store number of files that have been scratched, R0 ($86) into TRACK ($80) Load .A with $01 and .Y with $00 Exit with a JMP to SCREND ($CiA3) Delete file by links: JSR to FRETS ($EF5F) to mark the first file block as free in the BAM. JSR to OPNIRD ($D475) to open the internal read channel (SA=17) and read in the first one or two blocks. JSR to BAM2X ($F119) to set BAM pointers in the buffer tables. Load .A from BUF0,X ($A7,X) and compare it to $FF to see if buffer inactive. If inactive (.A=$FF), branch to DEL2 Load write BAM flag, WBAM ($02F9), OR it with $40 to set bit 6 and store it back in WBAM to indicate both buffers active.
255
NAME DEL2
DESCRIPTION OF WHAT ROM ROUTINE DOES Zero .A and JSR to SETPNT($D4C8) to set pointers to the currently active buffer. JSR to RDBYT ($D156) to direct read one byte (the track link from the buffer) Store track link into TRACK ($80) JSR to RDBYT ($D156) to direct read one byte (the sector link from the buffer) Store sector 'link into SECTOR ($81) Test track link. If not $00 (not final sector in this file), branch to DEL1 JSR to MAPOUT ($EEF4) write out the BAM. Exit with a JMP to FRECHN ($D227) to free the internal read channel. JSR to FRETS($EF5F) to de-allocate(free) specified in TRACK ($80) & SECTOR ($81) in the BAM. JSR to NXTBUF ($D44D) to read in the next block in the file (use T/S link). JMP to DEL2 to de-allocate the new block Delete the directory entry: Load .Y with $00 (will point to the 0th character in the entry? the file type). Set the file type, (DIRBUF),Y? ($94),Y to $00 to indicate a scratched file. JSR to WRTOUT ($DE5E) to write cut the directory block. Exit with a JMP to WATJOB ($D599) to wait for the write job to be completed. * NOT AVAILABLE ON THE 1541
DEL1
DELDIR
Load .A with a $31 to indicate a bad command and JMP to CMDERR ($ClC8). FORMAT DISKETTE ROUTINE - * - * -
This routine sets up a jump instruction in buffer 0 that points to the code used by the disk controller to do the formatting. It then puts an exectute job code in the job queue. The routine then waits while the disk controller actually does the formatting. FORMAT $C8C6 $C8D5 $C8DA Store JMP $FABB ($4C,$BB,$FA) at the start of buffer 0 ($0600/1/2). Load .A with $03 and JSR to SETH ($D6D3) to set up header of active buffer to the values in TRACK ($80) and SECTOR ($81) . Load drive number, DRVNUM ($7F), EOR it with $E0 (execute job code) and store the result in the job queue ($0003).
256
NAME FMT10 5
DESCRIPTION OF WHAT ROM ROUTINE DOES Load .A from the job queue ($0003). If .A > 127, the job has not been finished yet so branch back to FMT105. Compare .A with $02. if .A < 2, the job was completed OK so branch to FMT110. Error code returned by disk controller indicates a problem so load .A with $03 and .X with $00 and exit with a JMP to ERROR ($E60A). Job completed satisfactorily so exit with an RTS. COPY DISK FILES ROUTINE _ _ _
FMT110
* _
$C8EF
* _
DSKCPY
Store $E0 in BUFUSE ($024F) to kill the BAM buffer. JSR to CLNBAM ($F0D1) to set track and sector links in BAM to $00. JSR to BAM2X ($F119) to return the BAM LINDX in .X. Store $FF in BUF0,X ($A7,X) to mark the BAM as out-of-memory. Store $0F in LINUSE ($0256) to free all LINDXs. JSR to PRSCLN ($ClE5) to parse the command string and find the colon. If colon found (Z flag =0), branch to DX0000. Colon not found in command string so command must be CX=Y. This command is not supported on the 1541 so exit with a JMP to DUPLCT ($C8C1). JSR to TC3 0 ($ClF8) to parse the command string. JSR to ALLDRS ($C320) to put the drive numbers into the file table. Load .A with the command pattern image as determined by the parser from IMAGE ($028B). AND the image with %01010101 ($55). If the result is not $00, the command must be a concatenate or normal copy so branch to DX0020. Check for pattern matching in the name (as in cl:game=0:*) by loading .X from FILTBL ($027A) and then loading .A from the command string, CMDBUF,X ($0200,X). The value in .A is compared to $2A ("*") If there is no match, there is no wild so branch to DX0020. Load .A with the $30 to indicate a syntax error and JMP to CMDERR ($ClC8).
DX0000 DX0005
$C919
257
NAME DX0020
ADDRESS $C928
DESCRIPTION OF WHAT ROM ROUTINE DOES Load .A with the command pattern image as determined by the parser from IMAGE ($028B). AND the image with %11011001 ($D9). If the result is not $00, the syntax is bad so branch to DX0010 and abort. JMP to COPY ($C952) to do the file copy, syntax error and JMP to CMDERR ($ClC8). Subroutine used to set up for copying entire disk (C1=0). Not used on 1541. Copy file(s) to one file: JSR to LOOKUP ($C44F) to look up the file(s) listed in the command string in the directory. Load .A with the number of filenames in the command string from F2CNT($0278) and compare it with $03. If fewer than three files, this is not a concatenate so branch to COPlO ($C9A1). Load .A with the first file drive number from FILDRV ($E2) and compare it to the second drive number in FILDRV+1 ($E3). If not equal, this is not a concatenate so branch to COPlO ($C9A1). Load .A with the index to the first file entry from ENTIND ($DD) and compare it to the second file's index in ENTIND+1 ($DE). If not equal, this is not a concatenate so branch to COPlO ($C9A1). Load .A with the first file's sector link from ENTSEC ($D8) and compare it to the second file's link in ENTSEC+1 ($D9). If not equal, this is not a concatenate so branch to COPlO ($C9A1). CONCATENATE FILES
COPY
$C952 $C955
$C95C
$C962
$C968
$C96E $C971
JSR to CHKIN ($CACC) to check if input file exists. Set F2PTR ($0279) to $01 and JSR to OPIRFL ($C9FA) to ope n the internal read channel, read in the directory file, and locate the named file JSR to TYPFIL ($D125) to determine the file type. If $00, a scratched file so branch to COPOl (file type mismatch). Compare the file type to $02. If not equal, it is not a de leted program file so branch to COP05 to continue. Bad file name. Load A with $64 to indicate a file type mismatch and JSR to CMDERR ($ClC8) .
258
NAME COP05
DESCRIPTION OF WHAT ROM ROUTINE DOES Set secondary address, SA ($83) to $12 (#18, the internal write channel) Move the active buffer pointer from LINTAB+IRSA ($023C) to LINTAB+IWSA ($023D). Deactivate the internal read channel by storing $FF in LINTAB+IRSA ($023C). JSR to APPEND ($DA2A) to copy first file Load .X with $02 and JSR to CY10 ($C9B9) to copy second file behind the first. Exit routine with a JMP to ENDCMD ($C194 COPY FILE
COPlO CY
$C9A1 $C9A3 $C9A7 $C9AA $C9B0 $C9B3 $C9B6 $C9B9 $C9BC $C9BF $C9C3 $C9C6 $C9C9 $C9CB
JSR to CY ($C9A7) to do copy. Exit routine with a JMP to ENDCMD ($C194 JSR to CHKIO ($CAE7) to check if file exists. Get drive number from FILDRV ($E2), AND it with $01 (mask off default bit), and store it in DRVNUM ($7F). JSR to OPNIWR ($D486) to open internal write channel. JSR to ADDFIL ($D6E4) to add the new file name to the directory and rewrite the directory. Load .X with pointer from FlCNT ($0277). Store .X in F2CNT ($0278). JSR to OPIRFL ($C9FA) to open internal read channel and read in one or two blocks of the directory. Set secondary address, SA ($83) to $11, to set up the internal read channel. JSR to FNDRCH ($D0EB) to find an unused read channel. JSR to TYPFIL ($D125) to determine if the file is a relative file. If not a relative file (Z flag not set on return), branch to CY10A. JSR to CYEXT ($CA53) to open copy the relative file records. Store $08 (EOI signal) into EOIFLG($F8). JMP to CY20. JSR to PIBYTE ($CF9B) to write out last byte to disk. JSR to GIBYTE ($CA35) to get a byte from the internal read channel. Load .A with $80 (the last record flag) and JSR to TSTFLG ($DDA6) to see if this is the last record.
CY10
259
NAME
ADDRESS $C9E0
DESCRIPTION OF WHAT ROM ROUTINE DOES On return if Z flag is set (test failed; this is not the last record) branch to CY15 to do some more. Last record done so JSR to TYPFIL($D125) to get file type. On return if Z flag is set branch to CY30 to do some more. JSR to PIBYTE ($CF9B) to write out last byte to disk. Check if there are more files to copy by loading .X from F2PTR ($0279) , incrementing it by 1, and comparing it to F2CNT ($0278). If the carry bit is clear, there are more files to copy so branch back to CY10. Since no more files to copy, set the SA ($83) to $12 (internal write channel) and JMP to CLSCHN ($DB02) to close the copy channel and file. Open internal read channel to read file: Load .X with the file pointer F2PTR ($0279) and use this as an index to load .A with the drive number of the file to be read from FILDRV,X ($E2,X). AND this drive number with $01 to mask off the default drive bit, and store the value in DRVNUM ($7F) to set the drive number. Set the current TRACK ($80) to 18 ($12), the directory track. Set the current SECTOR($81) to the sector containing the directory entry for this file from ENTSEC,X ($D8,X). the directory track. JSR to OPNIRD ($D475) to open the internal read channel to read the directory. Load .X with the file pointer F2PTR ($0279) and use this as an index to load .A with the pointer to the start of the entry from ENTIND,X ($DD,X). JSR to SETPNT ($D4C8) to set the track sector pointers from the entry. Load .X with the file pointer F2PTR ($0279) and use this as an index to load .A with the file's pattern mask from PATTYP,X ($E7,X). AND this value with $07 (the file type mask) and use it to set the file type in TYPE ($024A). Set the record length, REC ($0258) to $00 since this is not a relative file. JSR to OPREAD ($D9A0) to open a read channel.
$C9F3
OPIRFL
$C9FA
$CA03 $CA08
$CAOC $CAOF
$CA14 $CA17
$CA21 $CA26
260
NAME
DESCRIPTION OF WHAT ROM ROUTINE DOES Load Y with $01 and JSR to TYPFIL ($D125) to get the file type. If Z flag set on return (indicates that this is not a relative file) branch to OPIRlO. Increment. .Y by 1. Transfer the value in .Y into .A Exit with a JMP to SETPNT ($D4C8) to set the track & sector pointers from the directory entry. Get byte from internal read channel: Set the secondary address, SA ($83) to $11 (#17) the internal read channel. Get byte from any channel: JSR to GBYTE ($D39B) to get the next byte from the read channel. Store the byte in DATA ($85). Load .X with the logical file index LINDX ($82) and use this as an index to load .A with the channel status flag, CHNRDY,X EOR .A with $08, the not EOI send code and store the result in EOIFLG ($F8). If .A <> $00 (EOI was sent!), branch to GIB20 and exit. JSR to TYPFIL ($D125) to get the file type. If Z flag set on return (indicates this is not a relative file), branch to GIB20 and exit. Load .A with $80 (the last record flag) and JSR to SETFLG ($DD97). Terminate routine with an RTS. Copy relative records: JSR to SETDRN ($DlD3) to set drive #. JSR to SSEND ($ElCB) to position side sector and BUFTAB to the end of the last record. Save side sector index, SSIND ($D6) and the side sector number, SSNUM ($D5) onto the stack. Set the secondary addres s, SA ($83) to $12, the internal write channel. JSR to FNDWCH ($D107) to find an unused write channel. JSR to SETDRN ($DlD3) to set drive #. JSR to SSEND ($ElCB) to position side sector and BUFTAB to the end of the last record.
OPIRlO
GIBYTE
$CA35
GCBYTE
$CA4D GIB20 CYEXT $CA52 $CA53 $CA56 $CA59 $CA5F $CA63 $CA66 $CA69
261
NAME
DESCRIPTION OF WHAT ROM ROUTINE DOES JSR to POSBUF ($E2C9) to position the proper data blocks into the buffers. Set R1 ($87) to the current value of the side sector index, SSIND ($D6). Set R0 ($86) to the current value of the side sector number, SSNUM ($D5). Zero R2 ($88) and the low bytes of the record pointer RECPTR ($D4) and the relative file pointer ($D7). Restore the original values of the side side sector number, SSNUM ($D5) and the sector index, SSIND ($D6) from the stack Terminate the routine with a JMP to ADDRl ($E33B). RENAME FILE IN THE DIRECTORY
RENAME
$CA88 $CA8B
$CA9F
$CAA5
JSR to ALLDRS ($C320) to set up all the drives given in the command string. Load .A with the drive specified for the second file from FILDRV+1 ($E3), AND it with $01 to mask off the default drive bit, and store the result back in FILDRV+1 ($E3). Compare the second drive number (in .A) with the first one in FILDRV ($E2). If equal, branch to RN10. OR the drive number in .A with $80 to set bit 7. This will force a search of both drives for the named file. Store the value in .A into FILDRV ($E2) JSR to LOOKUP ($C44F) to look up both file names in the directory. JSR to CHKIO ($CAE7) to check for the existance of the files named. Load the value from FILDRV+1 ($E3), AND it with $01 to mask off the default drive bit, and use the result to set the currently active drive, DRVNUM ($7F). Set the active sector number, SECTOR ($81) using the directory sector in which the second file name was found (from ENTSEC+1; $D9). JSR to RDAB ($DE57) to read the directory sector specified in TRACK($80) and SECTOR ($81). JSR to WATJOB ($D599) to wait for the job to be completed. Load .A with the pointer to the entry in the buffer from ENTIND+1 ($DE), add $03 (so it points to the first character in the file name), and JSR to SETPNT($D4C8) to set the pointers to the file name.
262
NAME
DESCRIPTION OF WHAT ROM ROUTINE DOES JSR to GETACT ($DF93) to store the active buffer number in .A. Transfer the buffer number to .Y, load .X from the file table FILTBL ($027A), A with $10 (the number of characters in a file name) and JSR to TRNAME($C6 6E) to transfer the file name from the command string to the buffer containing the file entry. JSR to WRTOUT ($DE5E) to write out the revised directory sector. JSR to WATJOB ($D599) to wait for the job to be completed. Terminate the routine with a JMP to ENDCMD ($C194). Check existance of input file: Load .A with the first file type from PATTYP+1 ($E8), AND it with the file type mask ($07) and store it in TYPE ($024A). Load .X from F2CNT ($0278). Decrement .X by 1 and compare it with the value of FlCNT ($0277). If the carry is clear, the file has been found so branch to CK10. Load .A with the file's track link from FILTRK,X ($0280,X). If link is NOT $00, branch to CK10. Since the file has not been found, load .A with $62 and exit with a JMP to CMDERR ($ClC8). Terminate routine with an RTS. Check existance of I/O file: JSR to CHKIN ($CACC) to check for the existance of the input file. Load .A with the file's track link from FILTRK,X ($0280,X). If link equals $00, branch to CK30. The file already exists so load .A with $62 and exit with a JMP to CMDERR($ClC8) Decrement .X (file counter). If more files exist, branch back to CK25. CMDERR ($ClC8). Terminate routine with an RTS.
CHKIN
$CACC
CK10
CK3 0
$CAF4 $CAF7
MEMORY ACCESS COMMANDS (M-R, M-W, AND M-E) MEM $CAF8 Check that the second character in the command is a "-" by: loading .A with the character from CMDBUF+1 ($0201), and comparing it with $2D ("-"). If not equal, branch to MEMERR ($CB4B).
NAME
ADDRESS $CAFF
DESCRIPTION OF WHAT ROM ROUTINE DOES Set up address specified in command by moving the characters from CMDBUF+3 ($0202) and CMDBUF+4 ($0203) to TEMP ($6F) and TEMP+1 ($70) . Set .Y to $00. Load .A with the third character of the command (R,W,E) from CMDBUF+2 ($0202) . Compare .A with "R". If equal, branch to MEMRD ($CB20). JSR to KILLP ($F258) to kill protection, NOTE: this does nothing on the 1541! Compare .A with "W". If equal, branch to MEMWRT ($CB50). Compare .A with "E". If NOT equal, branch to MEMERR ($CB4B). Do indirect jump using the pointer set up in TEMP ($006F). Load .A with the contents of (TEMP),Y ($6F),Y and store the value in DATA($85) Compare the command string length,CMDSIZ ($0274), with $06. If it is less than or equal to 6 (normally 5), branch to M30.
$CB09 $CBOB $C30E $CB12 $CB15 $CB19 MEMEX MEMRD $CBlD $CB20 $CB2 4
NOTE: PREVIOUSLY UNDOCUMENTED COMMAND!! PRINT#15,"M-R";CHR$(LO);CHR$(HI);CHR$(HOW MANY) MRMULT $CB2B $CB2E $CB2F $CB31 $CB33 $CB35 $CB37 $CB3A Multi-byte memory read: Load .X with the 6th character in the command string from CMDBUF+5 ($0205). Decrement .X (now $00 if only one to read). If the result is $00, all done so branch to M30. Transfer the value in .X to .A and clear the carry flag. Add the lo byte of the memory pointer in TEMP ($6F). This value is the lo byte of the last character to be sent. Increment the lo byte pointer in TEMP ($6F) so it points to the second memory location t . o be read. Store the value in .A into LSTCHR+ERRCHN ($0249) . Load .A with the current value of TEMP ($6A), the lo byte of the second memory location to be read and store this value in CB+ 2 ($A5) . Load .A with the current value of TEMP+1 ($70), the hi byte of the second memory location to be read and store this value in CB+3 ($A6).
$CB3E
264
NAME
ADDRESS $CB42
DESCRIPTION OF WHAT ROM ROUTINE DOES Continue memory read with a JMP to GE20 ($D443). JSR to FNDRCH ($DOEB) to find an unused read channel. Terminate memory read with a JMP to GE15 ($D43A). Load .A with $31 to indicate a bad command and JMP to CMDERR ($ClC8). Move byte from CMDBUF+6,Y ($0206,Y) to memory at TEMP,Y ($BF,Y). Increment .Y and compare .Y with the number of bytes to do, CMDBUF+5 ($0205). If more to do, branch back to M10. Terminate memory write with an RTS. NOTE: U0 restores pointer to JMP table User jump commands: Load .Y with the second byte of the command string from CMDBUF + 1 ($0201) . Compare .Y to $30. If not equal, this is NOT a U0 command so branch to US10. Restore normal user jump address ($FFEA) storing $EA in USRJMP ($6B) and $FF in USRJMP+1 ($6C). Terminate routine with an RTS. JSR to USREXC ($CB72) to execute the code according to the jump table. Terminate routine with a JMP to ENDCMD ($C194). Decrement .Y, transfer the value to .A, AND it with $0F to convert it to hex, multiply it by two (ASL), and transfer the result back into .Y. Transfer the lo byte of the user jump address from the table at (USRJMP),Y to IP ($75). Increment .Y by 1. Transfer the hi byte of the user jump address from the table at (USRJMP),Y to IP + 1 ($76) . Do an indirect jump to the user code through the vector at IP ($0076). Open direct access buffer in response to an OPEN "#" command: Use the previous drive number, LSTDRV ($028E) to set the current drive number DRVNUM ($7F). Save the current secondary address, SA ($83) on the stack.
M30
$CB45 $CB48
MEMERR MEMWRT
USER COMMANDS USER $CB5C $CB5F USRINT $CB63 $CB6B $CB6C $CB6F USREXC $CB7 2
US10
OPNBLK
$CB84 $CB89
265
NAME
ADDRESS $CB8C $CB8F $CB92 $CB96 $CB98 $CB9D $CBA0 $CBA5 $CBAA
DESCRIPTION OF WHAT ROM ROUTINE DOES JSR to AUTOI ($C63D) to initialize the disk. This is necessary for proper channel assignment. Restore the original secondary address, SA ($83) by pulling it off the stack. Load .X with the command string length CMDSIZ ($0274). Decrement .X by 1. If .X not equal to zero, a specific buffer number has been requested(e.g.#l) so branch to OBlO. No specific buffer requested so get any available buffer by loading .A with $01 and doing a JSR to GETRCH ($DlE2). On return, JMP to OB30. Load .A with $70 to indicate that no channel is available and JMP to CMDERR ($ClC8). Specific buffer requested so load .Y with $01 and JSR to BP05 ($CC7C) to check the block parameters. Load .X with the number of the buffer requested from FILSEC ($0285) and check it against $05 (the highest numbered buffer available). If too large, branch to OB05 and abort the command. Set TEMP ($6F) and TEMP+1 ($70) to $00 and set the carry flag. Loop to shift a 1 into the bit position in TEMP or TEMP+1 that corresponds to the buffer requested. For example: TEMP+1(00000000) TEMP(0000001)=buffer 0 TEMP+1(0 0000000) TEMP(0000100)=buffer 2 TEMP+1(00000001) TEMP(0000000)=buffer 8 Load .A with the value in TEMP ($6F) and AND it with the value in BUFUSE ($024F) which indicates which buffers are already in use. If the result is N O T $ O O , the buffer requested is already in use so branch to OB05 to abort. Load .A with the value in TEMP+1 ($70) and AND it with the value in BUFUSE+1 ($0250) which indicates which buffers are already in use. If the result is NOT $00, the buffer requested is already in use so branch to OB05 to abort. Mark the buffer requested as in use by ORing the value in TEMP with the value in BUFUSE and the value in TEMP+1 with the value in BUFUSE+1. Set up the channel by loading .A with $00 and doing a JSR to GETRCH ($DlE2) to find an unused read channel. Load .X with the current channel# from LINDX ($82) .
OB0 5 OBlO
$CBBF
$CBC6
$CBCD
$CBDD $CBE2
266
NAME
DESCRIPTION OF WHAT ROM ROUTINE DOES Use .X as an index to move the sector link from FILSEC($0285) to BUF0,X($A7,X) Transfer the sector link from .A to .X. Use .X as an index to move the current drive number from DRVNUM($7F) to JOBS,X ($00,X) and to LSTJOB,X ($025B,X). Load X with the current secondary address, SA ($83) Load .A with the current value from the logical index table, LINTAB,X ($022B,X). OR this value with $40 to indicate that it is read/write mode and store the result back in LINTAB,X. Load .Y with the current channel#, LINDX ($82) . Load .A with $FF and store this value as the channel's last character pointer LSTCHR,Y ($0244,Y). Load .A with $89 and store this value in CHNRDY,Y ($00F2,Y) to indicate that the channel is a random access one and is ready. Load .A with the channel number from BUF0,Y ($00A7,Y) and store it in CHNDAT,Y($02 3E,Y) as the first character Multiply the sector value in .A by 2 and transfer the result into .X Set the buffer table value BUFTAB,X ($99,X) to $01. Set the file type value FILTYP,Y ($EC,Y) to $0E to indicate a direct access file type. Terminate routine with a JMP to ENDCMD ($ClC4). (B-A;B-F;B-R;B-W;B-E;B-P)
OB3Q
$CBF1 $CBF3
Block commands: Zero .X and .Y. Load .A with $2D ("-") and JSR to PARSE ($C268) to locate the sub-command (separated from the command with a "-"). On return branch to BLK40 if Z flag is not set ("-" was found). Load .A with $31 to indicate a bad command and JMP to CMDERR ($ClC8). Load .A with $30 to indicate a bad syntax and JMP to CMDERR ($ClC8). Transfer the value in .X to .A. If not $00, branch to BLK30. Load .X with $05 (the number of block commands - 1).
267
NAME
ADDRESS $CC3 5
DESCRIPTION OF WHAT ROM ROUTINE DOES Load .A with the first character in the sub-command from CMDBUF,Y ($0200,Y). Loop to compare the first character in the sub-command with the characters in the command table BCTAB,X ($CC5D,X). If a match is found, branch to BLK60. If NO MATCH is found, branch to BLK10. Transfer the pointer to the command in the command table from .X to .A. OR this value with $80 and store it as the command number in CMDNUM ($02 2A). JSR to BLKPAR ($CC6F) to parse the block parameters. Load .A with the command number from CMDNUM ($022A), multiply it by 2 (ASL), and transfer the result into .X. Use .X as an index into the jump table BCJMP,X ($CC63) to set up a jump vector to the ROM routine at TEMP ($6F/70). Do an indirect JMP to the appropriate ROM routine via the vector at TEMP($6F). Block sub-command table .BYTE "AFRWEP" Block jump table $CC63/4 $03,$CD $CC65/6 $F5,$CC $CC6 7/8 $56,$CD $CC69/A $73,$CD $CC6B/C $A3,$CD $CC6D/E $BD,$CD ($CC5D-$CC62)
BLK50
$CC3 8
BLK60
$CC4 2
($CC63-$CC6E) BLOCK-ALLOCATE $CD0 3 BLOCK-FREE $CCF5 BLOCK-READ $CD56 BLOCK-WRITE $CD73 BLOCK-EXECUTE $CDA3 BLOCK-POINTER $CDBD
BLKPAR
$CC6F $CC78
BP05
BP10
$CC8B $CC91
Parse the block parameters: Zero .X and .Y. Load .A with $3A (":") and JSR to PARSE ($C268) to find the colon, if any. On return branch to BP05 if Z flag is not set (":" found; .Y= ":"-position+l) Load .Y with $03 (start of parameters) Load .A with the .Yth character from the command string. Compare the character in .A with $20, (a space). If equal, branch to BP10. Compare the character in .A with $29, (a skip chr). If equal, branch to BP10. Compare the character in .A with $2C, (a comma). If NOT equal, branch to BP20, Increment .Y. Compare .Y to the length of the command string in CMDSIZ ($0274), If more left, branch back to BP05. If no more, exit with an RTS.
268
NAME BP20
ADDRESS
$CC92
DESCRIPTION OF WHAT ROM ROUTINE DOES JSR to ASCHEX ($CCA1) to convert ASCII values into hex and store the results in tables. Increment the number of parameters processed FlCNT ($0277) . Load .Y with the value in F2PTR ($0279) Compare the value in .X (the original value of FlCNT ($0277) to $04 (the maximun number of files - 1). If the value in .X <= $04, branch to BP10. If .X was > $04, the syntax is bad so branch to BLK3 0 ($CC2B). Convert ASCII to HEX and store the converted values in the FILTRK ($0280) and FILSEC ($0285) tables: On entry: .Y = pointer into CMD buffer Zero TEMP($6F), TEMP+l($70), and TEMP+3 ($72) as a work area. Load .X with $FF. Load .A with the command string byte from CMDBUF,Y. Test if the character in .A is numeric by comparing it to $40. If non-numeric, branch to AH20. Test if the character in .A is ASCII by comparing it to $30. If it is not an ASCII digit, branch to AH20. AND the ASCII digit with $0F to mask off the higher order bits and save this new value on the stack. Shift the values already in the table one position (TEMP+1 goes into TEMP+2; TEMP goes into TEMP+1). Pull the new value off the stack and store it in TEMP. Increment .Y and compare it to the command length stored in CMDSIZ ($0274). If more command left, branch back to AH10. Convert the values in the TEMP table into a single hex byte: Save the .Y pointer to the command string into F2PTR ($0279), clear the the carry flag, and load .A with $00. Increment .X by 1 (index into TEMP). Compare .X to $03 to see if we're done yet. If done, branch to AH40. Load .Y from TEMP,Y ($6F,Y). Decrement .Y by 1. If Y<0 branch to AH30 Add (wit.h carry) the value from DECTAB,X ($CCF2,X) to .A. This adds 1, 10 or 100. If there is no carry, branch to AH35.
$CC95
$CC98
$CC9B
$CC9F
ASCHEX AH10
AH20 AH3 0
AH3 5
269
NAME
ADDRESS $CCDF
DESCRIPTION OF WHAT ROM ROUTINE DOES Since there is a carry, clear the carry, increment TEMP+3, and branch back to AH35. Save the contents of .A (the hex number) onto the stack. Load .X with the command segment counter from FlCNT ($0277). Load .A with the carry bit (thousands) from TEMP+3 ($72) and store it in the table, FILTRK,X ($0280,X). Pull the hex number off the stack and store it in the table, FILSEC,X($0285,X) Terminate routine with an RTS. The decimal conversion table: Byte $01 = 1 10 Byte $ 0A = Byte $64 = 100 Free (de-allocate) block in the BAM: JSR to BLKTST ($CDF5) to test for legal block and set up track & sector. JSR to FRETS ($EF5F) to free the block in the BAM and mark the BAM as changed. Terminate routine with a JMP to ENDCMD ($C194). Unused code: LDA #$01 / STA WBAM($02F9) Allocate a sector (block) in the BAM: JSR to BLKTST ($CDF5) to test for legal block and set up track & sector. Load .A with the current sector pointer, SECTOR ($81) and save this on the stack. JSR to GETSEC ($FlFA) to set the BAM and find the next available sector on this track. If Z flag is set on return to indicate that the desired sector is in use and there is no greater sector available on this track, branch to BA15. Pull the requested sector from the stack and compare it to the current contents of SECTOR ($81). If not equal, the requested sector is already in use so branch to BA30. Requested sector is available so JSR to WUSED ($EF90) to allocate the sector in the BAM and terminate the command with a JMP to ENDCMD ($C194). Pull the desired sector off the stack. It is of no further use since that sector is already in use.
AH40
DECTAB $CCF2 $CCF3 $CCF4 BLKFRE $CCF5 $CCF8 $CCFB $CCFE BLKALC $CD03 $CD06 $CD09
$CDOC
$CDOE
$CD13
BA15
$CD19
270
NAME BA20
ADDRESS $CDlA
DESCRIPTION OF WHAT ROM ROUTINE DOES Set the desired sector, SECTOR ($81) to $00, increment the desired track, TRACK ($80) by 1, and check if we have reached the maximum track count of 3 5 (taken from MAXTRK $FECB). If we have gone all the way, branch to BA40. JSR to GETSEC ($FlFA) to set the BAM and find the next available sector on this track. If Z flag is set on return, no greater sector is available on this track so branch back to BA20 to try another track Requested block is not available so load .A with $65 to indicate NO BLOCK ERROR and JMP to CMDER2 ($E645). No free sectors are available so load .A with $65 to indicate NO BLOCK ERROR and JMP to CMDERR ($ClC8). B-R Sub to test parameters: JSR to BKOTST ($CDF2) to test block parameters and set track & sector. JMP to DRTRD ($D460) to read block B-R Sub to get byte w/o increment: JSR to GETPRE ($D12F) set parameters. Load .A with the value in (BUFTAB,X), ($99,X). Terminate routine with an RTS. B-R Sub to do read: JSR to BLKRD2 ($CD36) to test parameters Zero .A and JSR to SETPNT ($D4C8) to set the track and sector pointers. JSR to GETSIM ($CD3C) to read block. On return .Y is the LINDX. Store the byte in .A into LSTCHR,Y ($0244,Y) as the last character. Store $89 in CHNRDT,Y($F2,Y) to indicate t;hat it is a random access channel and is now ready. Exit routine with an RTS. Block read a sector: JSR to BLKRD3 ($CD42) to set up to read the requested sector. JSR to RNGET1 ($D3EC) to read in the sector. Terminate routine with a JMP to ENDCMD ($C194).
BLKRD2
$CD36 $CD3 9
GETSIM
BLKRD3
BLKRD
271
NAME
ADDRESS
NOTE: The only real difference between a B-R command and a U1 (preferred) is that the U1 command move the last byte into the data buffer and stores $FF as the last byte read. UBLKRD $CD5F $CD6 2 $CD65 $CD6B $CD70 JSR to BLKPAR ($CC6F) to parse the block parameters. JSR to BLKRD3 ($CD42) to set up to read the requested sector. Move the last character read from LSTCHR,Y ($0244,Y) to CHNDAT,Y ($023E,Y) Store $FF in LSTCHR,Y ($0244,Y) as the last character to be read. Terminate routine with a JMP to ENDCMD ($C194) which ends with an RTS. Block-write of a sector: JSR to BKOTST ($CDF2) to test the buffer and block parameters and set up the drivef track, and sector pointers. JSR to GETPNT ($D4E8) to read the active buffer pointers. On exit, .A points into the buffer. Transfer .A to .Y and decrement .Y. If the value in .A is greater than $02, branch to BW10 Load .Y with $01. Load .A with $00. JSR to SETPNT ($D4C8) to set the buffer pointers. Transfer the value in .Y to .A and JSR to PUTBYT ($CFF1) to put the byte in .A into the active buffer of LINDX. Transfer the value of .X to .A and save it on the stack. JSR to DRTWRT ($D464) to write out the block. Pop the value off the stack and transfer it back into ,X. JSR to RNGET2 ($D3EE) to set the channel ready status and last character. Terminate routine with a JMP to ENDCMD ($C194) which ends with an RTS. U2: Block write of a sector: JSR to BLKPAR ($CC6F) to parse the block parameters. JSR to BKOTST ($CDF2) to test the buffer and block parameters and set up the drive, track, and sector pointers.
BLKWT
BW10
BW2 0
UBLKWT
$CD97 $CD9A
272
NAME
DESCRIPTION OF WHAT ROM ROUTINE DOES JSR to DRTWRT ($D464) to write out the block. Terminate routine with a JMP to ENDCMD ($C194) which ends with an RTS. Block execute a sector: JSR to KILLP ($F258) to kill the disk protection. Does nothing on the 1541! JSR to BLKRD2 ($CC6F) to read the sector Store $00 in TEMP ($6F) as the lo byte of the JMP address) Load .X from JOBNUM ($F9) and use it as an index to load the hi byte of the JMP address from BUFIND,X ($FEE0,X) and store it in TEMP+1 ($70). JSR to BE10 ($CDBA) to execute the block. Terminate routine wit.h a JMP t . o ENDCMD ($C194) which ends wit.h an RTS. JMP (TEMP) Used by block execute,
BLKEXC
Set the buffer pointer: JSR to BUFTST ($CDD2) to test for allocated buffer. Load the buffer number of the channel requested from JUBNUM ($F9), multiply it by two (ASL), and transfer the result into .X. Load .A with the new buffer pointer value from FILSEC+1 ($0286) and store it in the buffer table BUFTAB,X ($99,X). JSR to GETPRE ($D12F) to set up pointers JSR to RNGET2 ($D3EE) to ready the channel for I/O. Terminate routine wit.h a JMP to ENDCMD ($C194) which ends with an RTS. Test whether a buffer has been allocated for the secondary address given in SA. Load .X with the file stream 1 pointer, FlPTR ($D3) and then increment the original pointer FlPTR ($D3). Load .A with that file's secondary address from FILSEC,X ($0285,X). Transfer the secondary address to .Y. Decrement it by 2 (to eliminate the reserved secondary addresses 0 and 1) and compare the result with $0C (#12) . If the original SA was between 2 and 14, it passes the test so branch to BT20. Load .A with $70 to indicate no channel is available and JMP to CMDERR ($ClC8).
BUFTST
BT15
$CDE0
273
NAME BT20
DESCRIPTION OF WHAT ROM ROUTINE DOES Store the original secondary address (in .A) into SA ($83) as the active SA. JSR to FNDRCH ($DOEB) to find an unused read channel. If none available, branch to BT15. JSR to GETACT $DF93] to get the active buffer number On return, store the active buffer number in JOBNUM ($F9). read channel. If none available, branch Terminate routine with an RTS. Test all block parameters: buffer allocated and legal block. If OK, set up drive, track, and. sector values. JSR to BUFTST($CDD2) to test if buffer is allocated for this secondary address. Set the drive number, track, and sector values requested for a block operation and test to see that these are valid. Load .X with the channel number from FlPTR ($D3) Load .A with the drive number desired from FILSEC,X($0285,X), AND it with $01 to mask off the default drive bit, and store the result as the current drive number, DRVNUM ($7F). Move the desired sector from FILSEC+2,X ($0287,X) to SECTOR ($81). Move the desired track from FILSEC+l,X ($0286,X) to TRACK ($80). JSR to TSCHK ($D55F) to test whether the track and sector values are legal. JMP to SETLDS to turn on drive active LED. Do RTS from there. FIND RELATIVE FILE
$CDEC
$CDF1
BKOTST
$CDF2
BLKTST
$CDF5 $CDF7
(ALL 1 BYTE) - record # (lo byte) - record # (hi byte) - record size - pointer into record $CE0E $CE11 $CE14 $CE18
OUTPUTS: (ALL 1 BYTE) SSNUM - side sector # SSIND - index into SS RELPTR - pointer into sector
JSR to MULPLY($CE2C) to find total bytes TOTAL = REC# x RS + RECPTR JSR to DIV254 to divide by 254. The result is the record's location (in sectors) from the start of the file. Save the remainder (in .A) into RELPTR ($D7). This points into the last sector. JSR to DIV120 to divide by 120. The result points into the side sector file.
274
NAME
DESCRIPTION OF WHAT ROM ROUTINE DOES Increment the pointer into the sector, RELPTR ($D7) by two to bypass the two link bytes at the start of the sector. Move the quotient of the division by 120 from RESULT ($8B) to SSNUM ($D5). Load .A with the remainder of the division from ACCUM+1 ($90) , multiply it by two (ASL) because each side sector pointer occupies two bytes (t & s), add $10 (#16) to skip the initial link table in the sector, and store the resulting side sector index (points into the sector holding the side sectors) into SSIND ($D6). Terminate routine with an RTS. Calculate a record's location in bytes. TOTAL = REC# x RS + RECPTR JSR to ZERRES ($CED9) to zero the RESULT area ($8B-$8D). Zero ACCUM+3 ($92). Load .X with the LINDX ($82) and use it to move the lo byte of the record number from RECL,X ($B5) to ACCUM+1 ($90). Move the hi byte of the record number from RECH,X ($BB) to ACCUM + 2 ($91) . If the hi byte of the record number is not $00, branch to MUL25. If the lo byte of the record number is $00, branch to MUL50 to adjust for record #0 (the first record). Load .A with the lo byte of the record size from ACCUM+1 ($90), set the carry flag, subtract $01, and store the result back in ACCUM+1. If the carry flag is still set, branch to MULT50. Decrement the hi byte of the record size in ACCUM+2 ($91) . Copy the record size from RS,X ($C7,X) to TEMP ($6F). Do an LSR on TEMP ($6F). If the carry flag is clear, branch to MUL200 (no add t.his time) . JSR to ADDRES ($CEED) to add. RESULT = RESULT + ACCUM+l,2,3 JSR to ACCX2 ($CEE5) to multiply the ACCUM+1,2,3 by two. Test TEMP to see if done, if not branch back to MUL100. Add the byte pointer to the result. Terminate routine with an RTS.
$CE2B
MULPLY
MUL25
$CE41
$CE4A MUL50 MUL10 0 $CE4C $CE5 0 $CE5 4 MUL200 $CE57 $CE5A MUL400 $CE5E $CE6D
275
NAME
ADDRESS
DIVIDE ROUTINE: RESULT ($83) = QUOTIENT ACCUM+1 ($90) DIV254 DIV120 $CE6E $CE70 $CE71 $CE73 $CE75 $CE84 $CE87 $CE8 9 $CE8D $CE92
REMAINDER
DIV150 DIV20 0
$CE9A
DIV3 00
DIV400
$CEB0
DIV500
$CEBF
$CEC5
$CED0
Divide by 254 entry point: Load .A with $FE (#254) Byte $2C (skip over next instruction) Divide by 120 entry point: Load .A with $78 (#120) Store divisor into TEMP ($6F). Swap ACCUM+1,2,3 with RESULT,l,2 JSR to ZERRES ($CED9) to zero RESULT,1,2 Zero X Divide by 256 by moving the value in ACCUM+1,X ($90,X) to ACCUM,X ($8F,X). Increment .X. If .X is not 4 yet, branch back to DIV200. Zero the hi byte, ACCUM+3 ($92). Check if this is a divide by 120 by testing bit 7 of TEMP. If it is a divide by 254, branch to DIV300. Do an ASL of ACCUM ($8F) to set the carry flag if ACCUM > 127. Push the processor status on the stack to save the carry flag. Do an LSR on ACCUM to restore its original value. Pull the processor status back off the stack and JSR to ACC200 ($CEE6) to multiply the value in the ACCUM,l,2 by two so that we have, in effect, divided by 128. X/128 = 2 * X/256 JSR to ADDRES ($CEED) to add the ACCUM to the RESULT. JSR to ACCX2 ($CEE5) to multiply the ACCUM by two. Check if this is a divide by 120 by testing bit 7 of TEMP. If it is a divide by 254, branch to DIV400. JSR to ACCX4 ($CEE2) to multiply the ACCUM by four. A= 4 * (2 * A) = 8 * A Add in the remainder from ACCUM ($8F) to ACCUM+1. If a carry is produced, increment. ACCUM+2 and, if necessary, ACCUM+3. Test if remainder is less than 256 by ORing ACCUM+3 and ACCUM+2. If the result is not zero, the remainder is too large so branch to DIV to crunch some more. Test if remainder is less than divisor subtracting the divisor,TEMP ($6F) from the remainder in ACCUM+1 ($90). If the remainder is smaller, branch to DIV600. Since the remainder is too large, add 1 to the RESULT.
276
NAME DIV600
DESCRIPTION OF WHAT ROM ROUTINE DOES Store the new, smaller remainder in ACCUM+1 ($90) . Terminate routine with an RTS. Zero the RESULT area: Load .A with $00 and store in RESULT ($8B), RESULT+1($8C), and RESULT+2($8D) Terminate routine with an RTS. Multiply ACCUM by 4: JSR ACCX2 ($CEE5) Multiply ACCUM by 2: Clear the carry flag. Do a ROL on ACCUM+l($90), ACCUM+2($91), and ACCUM+2($92). Terminate routine with an RTS. Add ACCUM to RESULT: Load .X with $FD. Add RESULT+3,X ($8E,X) and ACCUM+4,X ($93) and store the result in RESULT+3. Increment .X. If not $00 yet, branch back to ADD100. Terminate routine with an RTS. Initialize LRU (least recently used) table: Load .X with $00. Transfer .X to .A. Store the value in .A into LRUTBL,X ($FA,X). Increment .X and compare it to $04, the command channel number. If not yet equal, branch back to LRUILP. Load .A with $06, the BAM logical index for the floating BAM, and store this value into LRUTBL,X ($FA,X). Terminate routine with an RTS. Update LRU (least recently used) table: Load .Y with $04, the command channel number. Load .X from LINDX ($82) the current channel number. Load .A with the value from LRUTBL,Y ($00FA,Y). Store the current channel number (from .X) into LRUTBL,Y. Compare the value in .A with the current channel number in LINDX ($82). If they are equal, branch to LRUEXT to exit. Decrement .Y the channel counter. If no more channels to do (Y<0) branch to LRUINT ($CEFA) since no match was found. Transfer .A to .X and JMP to LRULPl .A into LRUTBL,X ($FA,X). Terminate routine with an RTS.
ZERRES
$CED9 $CEE1
ADDRES ADD10 0
LRUINT LRUILP
LRUUPD LRULP1
LRUEXT
$CFlD
277
NAME
ADDRESS
DESCRIPTION OF WHAT ROM ROUTINE DOES Double buffer: Switch the active and inactive buffers. JSR to LRUUPD ($CF09) to update the LRU (least recently used) table. JSR to GETINA ($DFB7) to get the LINDX channel's inactive buffer number (in A) On return, if there is an inactive buffer, branch to DBL15. There is no inactive buffer so make oneI JSR to SETDRN ($DlD3) to set the drive number to the one in LSTJOB. JSR to GETBUF ($D28E) to get a free buffer number. If no buffers available, branch to DBL30 and abort. JSR to PUTINA ($DFC2) to store the new buffer number as the inactive buffer. Save the current values of TRACK ($80) and SECTOR ($81) on the stack. Load .A with $01 and JSR to DRDBYT ($D4F6) to direct read .A bytes. Store the byte read as the current SECTOR($81) Load .A with $00 and JSR to DRDBYT ($D4F6) to direct read .A bytes. Store the byte read as the current TRACI<($80). If the TRACK byte was $00 (last sector in the file), branch to DBL10. JSR to TYPFIL ($D125) to determine the file type we are working on. If it is a relative file, branch to DBL05. JSR to TSTWRT ($DDAB) to see if we are writing this file or just reading it. If just reading, branch to DBL05 to read ahead. We are writing so JSR to TGLBUF ($CF8C) to toggle the buffers. On return, JMP to DBL08. JSR to TGLBUF ($CF8C) to toggle the inactive and inactive buffers. JSR to RDAB ($DE57) to read in the next sector of t.he file (into active buffer) . Pull the old SECTOR($81) and TRACK($80) values from the stack and restore them. JMP to DBL20. Pull the old SECTOR($81) and TRACK($80) values from the stack and restore them. JSR to TGLBUF ($CF8C) to toggle the inactive and active buffers. JSR to GETACT ($DF93) to get the active buffer number (in .A). Transfer the active buffer number into .X and JMP to WATJOB ($D599) to wait until job is done
DBLBUF
$CFlE $CF21 $CF24 $CF2 6 $CF2 9 $CF2E $CF31 $CF3 7 $CF3E $CF45 $CF47 $CF4C
$CF51 DBL0 5 $CF5 7 $CF5A DBL08 DBL10 DBL15 DBL20 $CF5D $CF63 $CF66 $CF6C $CF6F
278
NAME DBL3 0
ADDRESS $CF76
DESCRIPTION OF WHAT ROM ROUTINE DOES No buffers to steal so load .A with $70 to indicate a NO CHANNEL error and JMP to CMDERR ($ClC8) . Set up double buffering: JSR to LRUUPD ($CF09) to update the LRU (least recently used) table. JSR to GETINA ($DFB7) to get the number of the inactive buffer (in .A). If there is an inactive buffer, branch to DBS10 to exit. JSR to GETBUF ($DF93) to find an unused buffer. If no buffers available, branch to DBL30 ($CF76) to abort. JSR to PUTINA ($DFC2) to set the buffer found as the inactive buffer. Terminate routine with an RTS. Toggle the inactive & active buffers: Input: LINDX = current channel # Load .X with the channel number from LINDX ($82) and use it as an index to load .A with the buffer number from BUF0,X ($A7). EOR this number with $80 to change its active/inactive state and store the modified value back in BUF0,X. Load .A with the buffer number from BUFl,X ($AE). EOR this number with $80 to change it.s active/inactive state and store the modified value back in BUFl,X. Terminate routine with an RTS. Write byte to internal write channel: Load .X with $12 (#18) the secondary address of the internal write channel and use it to set the current secondary address SA ($83) . JSR to FNDWCH ($D10 7) to find an unused write channel. JSR to SETLED ($C100) to t.urn on the drive active LED. JSR to TYPFIL ($D125) to determine the current file type. If NOT a relative file, branch t . o PBYTE ($CFAF). Load .A with $20 (the overflow flag bit) and JSR to CLRFLG ($DD9D) t . o clear the overflow flag. Write byte to any channel: Load .A with the current secondary address from SA ($83) . Compare the SA with $0F (#15) to see if we are using the command channel. If SA=$0F, this is the command channel so branch to L42 ($CFD8). If not, branch to L40 ($CFBF).
DBSET
DBS10 TGLBUF
$CF8B $CF8C
$CF94
$CFAA
PBYTE
$CFAF
279
NAME
ADDRESS
DESCRIPTION OF WHAT ROM ROUTINE DOES Main routine to write to a channel: Check if this is the command channel or a data channel by loading the original secondary address from ORGSA ($84), ANDing it with $8F, and comparing the result with $0F (#15). If less than 15, this is a data channel so branch to L42. JSR to TYPFIL ($D125) to determine the file type. If we are NOT working on a sequential file, branch to L41. Since this is a sequential file, load .A with the data byte from DATA ($85) and JMP to WRTBYT ($D19D) to write the byte to the channel. If Z flag not set, we are writing to a true random access file (USR) so branch to L46. We are writing to a relative (REL) file so JMP to WRTREL ($EOAB). Since this is a USR file, load .A with the data byte from DATA ($85) and JSR to PUTBYT ($CFF1) to write it to the channel. To prepare to write the next byte: load .Y with the channel number from LINDX ($82) and JMP to RNGET2 ($D3EE). Since this is the command channel, set LINDX ($82) to $04 (the command channel number). Test if command buffer is full by doing a JSR to GETPNT ($D4E8) to get the position of the last byte written and comparing it to $2A. If they are equal, the buffer is full so branch to L50. Since there is space, load .A with the command message byte from DATA ($85) and JSR to PUTBYT ($CFF1) to write it to the command channel. Test if this is the last byte of the message by checking the EOIFLG ($F8). If it is zero, this is the last byte so branch to L45. Terminate command with an RTS. Increment CMDWAT ($0255) to set the command-waiting flag. Terminate command with an RTS. Put byte in .A into the active buffer of the channel in LINDX: Save byte in .A onto the stack. JSR to GETACT ($DF93) to get the active buffer number (in .A). If there is an active buffer, branch to PUTB1.
PUT
$CFB7
L40
$CFBF $CFC4
L41
$CFC9 $CFCB
L46
$CFCE
$CFE3
L50
$CFE8
L45
PUTBYT
$CFF1 $CFF2
280
NAME
ADDRESS $CFF7
DESCRIPTION OF WHAT ROM ROUTINE DOES No active buffer so pull the data byte off the stack, load .A with $61 to indicate a FILE NOT OPEN error, and JMP to CMDERR ($ClC8). Multiply the buffer number by 2 (ASL) and transfer this value to .X Pull the data byte off the stack and store it in the buffer at (BUFTAB,X) ($99,X). Increment the buffer pointer BUFTAB,X NOTE: Z flag is set if this data byte was stored in the last position in the buffer! Terminate routine with an RTS. INITIALIZE DRIVE(S)
PUTB1
$D004
INTDRV
$D005 $D008
ID20
$D00B
Initialize drive(s): (Disk command) JSR to SIMPRS ($C1D1) to parse the disk command. JSR to INITDR ($D042) to initialize the drive(s). Terminate command with a JMP to ENDCMD ($C194) . Initialize drive given in DRVNUM: JSR to BAM2A ($F10F) to get the current BAM pointer in .A. Transfer the BAM pointer to .Y and use it as an index to load the BAM LINDX from BUFO,Y ($A7,Y) into .X. If there is a valid buffer number for the BAM (not $FF), branch to IT30. No buffer so we had better get one! Save the BAM pointer in .A on the stack and JSR to GETBUF ($D28E) to find an unused buffer. If a buffer is available, branch to IT20. No buffer available so load .A with $70 to indicate a NO CHANNEL error and JSR to CMDER3 ($E648). Pull the BAM pointer from the stack and transfer it to .Y. Transfer the new buffer number from .X to .A, OR it with $80 (to indicate an inactive status), and store the result in BUF0,Y ($00A7,Y) to allocate the buffer. Transfer the buffer number from .X to .A, AND it with $0F to mask off the inactive status bit, and store it in JOBNUM ($F9). Set SECTOR ($81) to $00 and TRACK ($80) to $12 (#18) to prepare to read the BAM.
ITRIAL
$DOOE $D011
$D018
IT30
$D02C
$D031
281
NAME
DESCRIPTION OF WHAT ROM ROUTINE DOES JSR to SETH ($D6D3) to set up the seek image of the BAM header. Load .A with $B0 (the job code for a SEEK) and JMP to DOJOB ($D58C) to do the seek to track 18. Does an RTS when done. Initialize drive: JSR to CLNBAM ($F0D1) to zero the track numbers for the BAM. JSR to CLDCHN ($D313) to allocate a channel for the BAM. JSR to ITRIAL ($D00E) to allocate a buffer for the BAM and seek track 18. Store $00 in MDIRTY,X ($0251) to indicate that the BAM for drive .X is NOT DIRTY (BAM in memory matches BAM on the diskette). Set the master ID for the diskette in DSKID,X ($12/3 for drive 0) from the track 18 header values ($16/17) read during the seek to track 18. JSR to DOREAD ($D586) to read the BAM into the buffer. Load the disk version(#65 for 4040/1541) from the $0X02 position in the BAM and store it in DSKVER,X($0101,drive number) Zero WPSW,X ($lC,X) to clear the write protect switch and NODRV,X ($FF,X) to clear the drive-not-active flag. Count the number of free blocks in BAM JSR to SETBPT ($EF3A) to set the bit map pointer and read in the BAM if necessary Initialize .Y to $04 and zero .A and .X (.X will be the hi byte of the count). Clear carry and add (BMPNT),Y; ($6D),Y to the value in .A. If no carry, branch to NUMF2. Increment .X (the hi byte of the count). Increment .Y four times so it points to the start of the next track byte in the BAM. Compare .Y to $48 (the directory track location). If .Y=$48, branch to NUMF2 to skip the directory track. Compare .Y to $90 to see if we are done. If there is more to do, branch to NUMF1. All done. Save the lo byte of the count on the stack and transfer the hi byte from .X to .A. Load .X with the current drive number from DRVNUM ($7F) and store the hi byte of the count (in .A) into NDBH,X ($02FC,X). Pull the lo byte of the count off the stack and save it in NDBL,X ($02FA,X).
INITDR
$D052
NFCALC
$D075 $D0 78
NUMF1
NUMF2
$D08B $D08F
282
NAME
ADDRESS $D09A
DESCRIPTION OF WHAT ROM ROUTINE DOES Terminate routine with an RTS. Start reading ahead: Use the values in TRACK and SECTOR to read a data block. Use the track and sector pointers to set up the next one. JSR to SETHDR ($D6D0) to set up the header image using TRACK ($80) and SECTOR ($81) values. JSR to RDBUF ($D0C3) to read the first block into the data buffer. JSR to WATJOB ($D599) to wait for the read job to be completed. JSR to GETBYT ($D137) to get the first byte from the data buffer (track link) and store it in TRACK ($80) . JSR to GETBYT ($D137) to get the second byte from the data buffer (sector link) and store it in SECTOR ($81). Terminate routine with an RTS. Start double buffering: (reading ahead) JSR to STRRD ($D09B) to read in a data block and set up the next one. Check the current TRACK ($80) value. If not $00, we are not at the end of the file so branch to STR1. Terminate routine with an RTS. JSR to DBLBUF ($CFlE) to set up buffers and pointers for double buffering and set TRACK and SECTOR for the next block, JSR to SETHDR ($D6D0) to set up the header image using TRACK ($80) and SECTOR ($81) values. JSR to RDBUF ($D0C3) to read the next block into the dat.a buffer. JMP to DBLBUF ($CFlE) to set up buffers and pointers for double buffering and set TRACK and SECTOR for the next block. Start a read job of TRACK and SECTOR Load .A with $80, the job code for a read, and branch to STRTIT ($D0C9). Start a write job of TRACK and SECTOR Load .A with $90, the job code for a write. Store command desired (in .A) as the current command in CMD ($024D). JSR to GETACT ($DF93) to get the active buffer number (in .A) . Transfer t.he active buffer number into .X.
STRRD
$D09B
$D09E $D0A1 $D0A4 $D0A9 $DOAE STRDBL $DOAF $D0B2 $D0B6 $D0B7 $DOBA $DOBD $D0C0
STR1
RDBUF
$D0C3
WRTBUF STRTIT
283
NAME
ADDRESS $D0D0
DESCRIPTION OF WHAT ROM ROUTINE DOES JSR to SETLJB ($D506) to set up drive number (from the last. job) , check for legal track & sector, and, if all OK, do the job. On return .A=job number and .X=buffer number. Transfer buffer number from .X to .A and save it on the stack. Multiply the buffer number by two (ASL) and transfer the result into .X and use it as an index to store $00 in the buffer table pointer BUFTAB,X ($99,X) JSR to TYPFIL ($D125) to get the file type. Compare the file type to $04. If this is not a sequential file, branch to WRTC1. Since this is a sequential file, increment the lo byte of the block count in NBKL,X ($B5,X) and, if necessary, the hi byte in NBKH,X ($BB,X). Pull the original buffer number off the stack and transfer it back into .X. Terminate routine with an RTS. Find the assigned read channel: Compare the current secondary address from SA ($83) with $13 (#19) the highest, allowable secondary address+1. If too large, branch to FNDC20. AND the secondary address with $0F NOTE: This masks off the high order bits of the internal channel sec adr's: Internal read $11 (17) -> $01 Internal write $12 (18) -> $02 Compare t.he sec addr in .A with $0F(15), t.he command channel sec addr. If they are not equal, branch to FNDC25. Load .A with $10, the sec addr error value. Transfer the sec addr from .A to .X, set the carry flag, and load the channel number from LINTAB,X ($022B,X). If bit. 7 is set, no channel has been assigned for this sec addr, so branch to FNDC3 0 to exit (with carry bit. set) . AND the current channel number wit.h $0F and store the result- as the current channel number in LINDX ($82) . Transfer t.he channel number into .X and clear the carry bit. Terminate routine wit.h an RTS.
$D0D3
$DODB
$D0E2
WRTC1
$D0E8 $DOEA
FNDRCH
$DOEB
$D0F1
FNDC20
$D0F3 $D0F7
FNDC25
$D0F9
$D100
FNDC3 0
$D106
284
NAME FNDWCH
ADDRESS $D107
DESCRIPTION OF WHAT ROM ROUTINE DOES Find the assigned write channel: Compare the current secondary address from SA ($83) with $13 (#19) the highest allowable secondary address+1. If t.oo large, branch to FNDW13. AND the secondary address with $0F NOTE: This masks off the high order bits of t.he internal channel sec adr's: Internal read $11 (17) -> $01 Internal write $12 (18) -> $02 Transfer the sec addr from .A to .X, and load the channel number assigned to this sec addr from LINTAB,X ($022B,X). Transfer this channel number to .Y. Do an ASL of the channel number in .A. If a channel has been assigned for this sec addr (bit 7 of LINTAB, X is not. set) branch to FNDW15. If no channel assigned has been assigned for t.his secondary address (bit. 6 also set), branch to FNDW20 and abort. Transfer the original sec addr from .Y to .A, AND it wit.h $0F to mask off any high order bit.s, and store it in LINDX ($82) as the currently active channel. Transfer the channel number to .X, clear t.he carry flag, and terminate with RTS. If bit 6 of LINTAB,X is set (indicates an inactive channel), branch t . o FNDW10. Abort by setting the carry flag and terminate the routine with an RTS. Get current file type: Load .X with the current channel number from LINDX ($82) . Load .A wit.h the file type from the file type table, FILTYP,X ($EC,X). Divide the file t.ype by 2 (LSR), AND it with $07 to mask off higher order bits, and compare the result wit.h $04 '(set the Z flag if it is a REL file!). Terminate t.he routine with an RTS. Set buffer pointers: JSR t . o GETACT ($DF93) to get t.he active buffer number (in .A). Multiply the buffer number by 2 (ASL) and transfer the result into .X. Load .Y with the current channel number from LINDX ($82) . Terminate the routine with an RTS.
$D10D
FNDW13
FNDW10
$D119
FNDW15 FNDW20
$D121 $D123
TYPFIL
285
NAME GETBYT
DESCRIPTION OF WHAT ROM ROUTINE DOES Read one byte from the active buffer: If last data byte in buffer, set Z flag. JSR to GETPRE to set buffer pointers. Load .A with the pointer to the last character read from LSTCHR,Y ($0244,Y). If pointer is zero, branch t . o GETB1. Load the data byt.e from (BUFTAB,X) ($99,X) and save it on the stack. Load t.he pointer from BUFTAB,X ($99,X) and compare it to the pointer to the last character read in LSTCHR,Y. If the pointers are not equal, branch to GETB2. Store $FF in BUFTAB,X ($99,X) Pull the data byte off t.he stack and increment BUFTAB,X ($99,X). This will set the Z flag if this is the last byte. Terminate routine with an RTS. Load the data byte from (BUFTAB,X) ($99,X). Increment BUFTAB,X ($99,X). Terminate routine with an RTS. Read byte from file: The next file will be read if necessary and CHNRDY($F2) will be set to EOI if we have read the last. character in file. JSR to GETBYT to read a byte from the active buffer. On return, if Z flag is not set, we did not read the last byte in the buffer so branch to RD3 and RTS. We read t.he last byte so load .A with $80, the EOI flag. Store the channel status (in .A) into CHNRDY,Y ($00F2,Y). Load .A with the byte from DATA ($85) . Exit from routine with an RTS. JSR to DBLBUF ($CFlE) to begin double buffering. Load .A wit.h $00 and JSR to SETPNT ($D4C8) to set up the buffer pointers JSR to GETBYT ($D137) t . o read the first byte from the active buffer (track link) Compare the track link to $00. If it is $00, there is no next block so branch to RD4. There is another block in this file so store the track link in TRACK ($80). JSR t . o GETBYT ($D137) to read the next. byte from the active buffer(sector link) and store it in SECTOR ($81). JSR to DBLBUF ($CFlE) to begin double buffering.
GETB2
GETB1
RDBYT
$D15 6
$D162 RD01 $D164 $D16 7 $D169 $D16A $D16D $D172 $D175 $D179 $D17B $D180
RD1
286
NAME
DESCRIPTION OF WHAT ROM ROUTINE DOES JSR to SETDRN ($DlD3) to set up the drive number. JSR t . o SETHDR ($D6D0) to set up the next. header image. JSR to RDBUF ($D0C3) to read in t.he next block in t.he file. JSR to DBLBUF ($CFlE) to toggle the active & inactive buffers & read ahead. Load .A wit.h the byt.e from DATA ($85) . Exit from routine with an RTS. JSR to GETBYTE ($D137) to get the next, byt.e. Load .Y with t.he current, channel number from LINDX ($82) and store the new character as the pointer to the last, character read from the dat.a buffer LSTCHR,Y ($0244,Y). Load .A wit.h the byte from DATA ($85) . Exit. from routine with an RTS. Write character t . o the active channel: If this fills t.he buffer, write t.he dat.a buffer out. t . o disk. JSR t . o PUTBYT ($CFF1) to write the byte to the active channel. If Z flag is set. on return, the buffer is full so branch to WRT0. Exit, from routine wit.h an RTS. JSR to SETDRN ($DlD3) t . o set. the current drive number from t.he one in LSTJOB. JSR to NXTTS ($FllE) to get the next available track and sector. Load .A with $00 and JSR to SETPNT ($D4C8) to set up the buffer pointers. Load .A with t.he next available track from TRACK ($80) and JSR to PUTBYT ($CFF1) to store the track link. Load .A with t.he next, available sector from SECTOR ($81) and JSR to PUTBYT ($CFF1) to store t.he sector link. JSR to WRTBUF ($D0C7) to write out the buffer to disk. JSR to DBLBUF ($CFlE) to toggle the active and inactive buffers and set up the next inactive buffer. JSR to SETHDR ($D6D0) to set. up the header image for the next block. Load .A with $02 (to bypass the t.rack and sector link) and JMP t . o SETPNT to set up the pointers to the next buffer.
RD3 RD4
$D19A $D19C
WRTBYT
$D19D $D1A0
WRT0
$DlA2 $DlA3 $DlA6 $DlA9 $DlAB $DlB3 $DlB8 $DlBB $DlBE $D1C1
287
NAME
ADDRESS
DESCRIPTION OF WHAT ROM ROUTINE DOES Increment the pointer of the active buffer by .A Store the value from .A in TEMP ($6F). JSR to GETPNT ($D4E8) to' get the active buffer pointer (in .A). Clear t.he carry flag and add the value from TEMP ($6F). Store the result into BUFTAB, X ($99',X) and into DIRBUF ($94). Terminate routine with an RTS. Set drive number: Sets DRVNUM t . o the same drive as was used on the last. job for the active buffer. JSR to GETACT ($D4E8) to get. the active buffer number (in .A). Transfer the buffer number to .X and use it. as an index to load t.he last job number from LSTJOB,X ($025B) into .A. AND the job number with $01 to mask off all but the drive number bit and store the result as t.he current drive number in DRVNUM ($7F). Terminate routine with an RTS. Open a new write channel: .A = number of buffers needed The routine allocates a buffer number and sets the logical file index, LINDX. Set the carry flag to indicate that we want a write channel. Branch to GETR2. Open a new read channel: .A = number of buffers needed The routine allocates a buffer number and sets the channel#, LINDX. Clear the carry flag to indicate that we want a read channel. Save t.he processor status (t.he carry flag) onto t.he stack. Save the number of buffer needed (in .A) into TEMP ($6F). JSR to FRECHN ($D227) to free any channels associated with this secondary address. JSR to FNDLNX ($D37F) to find the next, free logical index (channel) to use and allocate it. Store the new channel number in LINDX as the current channel number. Load . X wit.h the current secondary address from SA ($83) .
INCPTR
SETDRN
$DlD3
$DlD6 $DlDA
$DlDE
GETWCH
$DlDF $D1E0
GETRCH GETR2
288
NAME
ADDRESS $DlF0
DESCRIPTION OF WHAT ROM ROUTINE DOES Pull the processor status off the stack and if carry flag is clear (read), branch t . o GETR55. OR the channel number in .A wit.h $80 to set bit. 7 t . o indicate a write file. Store the channel number (in .A) into the logical index table, LINTAB,X ($02 2B,X). NOTE: Bit 7 set. for a write channel AND the channel number in .A with $3F to mask off the write channel bit. and transfer the result to .Y. De-allocate any buffers associated wit.h this channel by storing $FF in BUF0,Y ($00A7,Y), in BUFl,Y ($00AE,Y), and in SS,Y ($00CD,Y). Decrement the value in TEMP ($6F). This is the number of buffers to allocate. If there are no more to allocate ($FF), branch to GETR4 and exit. JSR to GETBUF ($D28E) to allocate a new buffer. If a buffer was allocated, branch to GETR5. No buffers available, so JSR to RELBUF ($D25A) to release any buffers allocated Load .A wit.h $70 to indicate a NO CHANNEL error and JMP to CMDERR ($ClC8). Store t.he buffer number (in .A) int.o BUF0,Y ($00A7,Y). Decrement the value in TEMP ($6F). This is the number of buffers to allocate. If there are no more to allocate ($FF), branch to GETR4 and exit. JSR to GETBUF ($D28E) to allocate a new buffer. If a buffer was NOT allocated, branch t . o GETERR and abort. Store the buffer number (in .A) into BUFl,Y ($00AE,Y). Terminate routine with an RTS. Free channel associated with SA Read and write channels are freed. The command channel is not freed. Load .A with the secondary address from SA ($83). Compare it. with $0F (#15), the command channel secondary address. If t.he secondary address is not $0F, branch to FRECO. Since we are not. to free the command channel, simply exit with an RTS. Free data channel associated wit.h SA: Load .X with the secondary address from SA ($83).
GETR52 GETR55
$DlF3 $DlF5
$DlF8 $DlFB
GETR3
$D20 6
FRECHN
$D227
$D2 2D
FRECO
$D2 2E
289
NAME
ADDRESS $D230
DESCRIPTION OF WHAT ROM ROUTINE DOES Load .A with the channel number associated wit.h t.his secondary address from LINTAB,X ($022B,X). If it is $FF, there is no associated channel so branch to FRE25 and exit. AND the channel number with $3F to mask off the higher order bits and store the result as the current channel in LINDX ($82) . Free the channel by storing $FF into LINTAB,X ($022B,X). Load .X with the channel number from LINDX ($82) and store $00 as the channel status (free) in CHNRDY,X ($F2,Y). JSR to RELBUF ($D25A) to release buffers Load .X with the channel number from LINDX ($82) and .A with $01. Decrement .X, the channel number. If it. is $FF (no lower channel numbers) , branch t . o REL10. Do an ASL on the value in .A. Note t.hat the bit set. shifts left one position each t.ime through the loop. If .A <> 0, branch to REL15 (always). OR t.he value in the accumulator with LINUSE ($0256) to free the channel (bit = 1 for free; bit = 0 for used). Store t.he resulting value back in LINUSE ($0256). Terminate routine with an RTS. Release buffers associated with channel: Load .X with the channel number from LINDX ($82). Load .A wit.h the buffer number for this channel from BUF0,X ($A7,X). Compare t.he buffer number with $FF (free) . If it. is already free, branch to REL1. Save t.he buffer number on the stack and store $FF into BUF0,X ($A7,X) to free this buffer. Pull the buffer number off t.he stack and JSR to FREBUF ($D2F3) to free the buffer Load .X with the channel number from LINDX ($82). Load .A with the buffer number for this channel from BUFl,X ($AE,X). Compare the buffer number with $FF (free). If it is already free, branch to REL2. Save t.he buffer number on the stack and store $FF into BUFl,X ($AE,X) to free this buffer. Pull the buffer number off the stack and JSR to FREBUF ($D2F3) to free the buffer
$D237
RELINX REL15
REL10
FRE25 RELBUF
$D2 73 $D2 78
290
NAME REL2
DESCRIPTION OF WHAT ROM ROUTINE DOES Load .X wit.h the channel number from LINDX ($82). Load .A with the side sector for t.his channel from SS f X ($CD,X). Compare the side sector with $FF (free) . If it. is already free, branch to REL3. Save t.he side sector on the stack and store $FF into SS,X ($CD,X) t . o free the side sector pointer. Pull the side sector off the stack and JSR to FREBUF ($D2F3) to free any buffer Terminate routine with an RTS. Get a free buffer number: .Y=channel # If successful, initialize JOBS & LSTJOB and return with buffer number in .A. If not. successful, .A = $FF; N flag set. Save channel number by transferring it. from .Y to .A and pushing it on t.he stack. Load .Y with $01 and JSR to FNDBUF ($D2BA) to find a free buffer (# in .X). If one is found, branch to GBF1. Decrement .Y and JSR to FNDBUF ($D2BA) t . o find a free buffer (# in .X) . If one found, branch to GBF1. Can't find a free one so let's try to steal one! JSR to STLBUF ($D339) to try to steal an inactive one. On return, buffer # in .A so transfer it to .X. If we didn't get one, branch to GBF2. Wait till any job using JOBS,X ($00,X) is completed. Clear the job queue by setting JOBS,X ($00,X) and LSTJOB,X ($025B,X) to the current drive number using the value from DRVNUM ($7F). Transfer the buffer number from .X to .A multiply it. by two (ASL) , and transfer the result to .Y. Store a $02 on BUFTAB,Y ($0099,Y) so the point.er points beyond the track and sector link. Restore the original .Y value from the stack. Transfer t.he buffer number from .X t . o .A to set. the N flag if not successful. Terminate routine with an RTS.
GBF1
$D2A3 $D2A7
291
NAME
ADDRESS
DESCRIPTION OF WHAT ROM ROUTINE DOES Find a free buffer and set BUFUSE: On entry: .Y = index into BUFUSE Y=0 buffers 0-7; Y=1 buffers 8-15 If successful, .X = buffer number If not successful, .X = $FF; N flag set Load .X with $07 (for bit test) Load .A with BUFUSE,Y ($024F,X). Each bit indicates whether a buffer is free (1) or in use (0). AND this value in .A with the bit mask, BMASK,X ($EFE9,X). Each of these masks has just one bit set. If the result of t.he AND is $00, we have found a free buffer so branch to FB2. Decrement .X to try next buffer. If any left, branch back to FBI. No more buffers to try (.X=$FF) so exit with an RTS. Found a free buffer so let's grab it.I Load .A with the value in BUFUSE,Y ($024F,Y), EOR it with the bit map for the free buffer, BMASK,X ($EFE9,X), and store the result back in BUFUSE,Y. Transfer the buffer number from .X to .A and if .Y is $00, branch to FB3. Since .Y is $01 (never happens on the 1541) , we have to add 8 to t.he buffer number. So: Clear the carry flag and add $08 to the buffer number in .A. Transfer the buffer number from .A to .X Terminate routine with an RTS. Free the inactive buffer: Load .X with the current channel number from LINDX ($82) . Load .A wit.h the buffer number from BUF0,X ($A7,X). If bit 7 is set, branch t . o FRI10. Transfer the channel number from .X to .A, clear t.he carry flag, add $07 (the maximum number of channels +1), and transfer the result back into .X. This is the alternate buffer for this channel Load .A with the buffer number from BUF0,X ($A7,X). If bit. 7 is NOT set, this buffer is active too so exit to FRI20 (above). Compare the buffer number to $FF. If it is $FF, the buffer is free already so exit to FRI20 (above). Save t.he buffer number on the stack. Free t.he buffer by storing $FF into BUF0,X ($A7,X). Pull the buffer number off the stack.
FNDBUF FBI
$D23A $D2BC
$D2D1 $D2D5
$D2E5
FRI10
292
NAME FREBUF
ADDRESS $D2F3
DESCRIPTION OF WHAT ROM ROUTINE DOES Free buffer in BUFUSE: AND the buffer number with $0F t . o mask off any higher order bits, transfer the result into .Y and increment .Y by 1. Load .X with $10 (#16) 2 * 8 bits Loop to ROR BUFUSE+1 ($0250) and BUFUSE ($024F) 16 times. Use .Y to count down t . o 0. When .Y is zero, the bit that corresponds to the buffer we want. is in the carry flag so we clear the carry bit to free that buffer. We then keep looping until .X has counted down all the way from $10 to $FF. When .X reaches $FF, the bits are all back in the right places, so exit with an RTS. Clear all channels except the CMD one: Set the current secondary address in SA ($83) to $0E (#14) JSR to FRECHN ($D227) to free the channel whose secondary address is SA Decrement the value in SA ($83) . If i t is not- $00, branch back to CLRC1. Terminate routine with an RTS. Close all channels except the CMD one: Set the current secondary address in SA ($83) to $0E (#14) Load .X with the secondary address from SA ($83) and use it as an index to load .A with the channel number from LINTAB,X ($022B,X). Compare the channel number with $FF; if equal, no channel has been assigned so branch t . o CLD2. AND the channel number with $3F to mask off the higher order bits and store the result in LINDX ($82) as the current channel number. JSR to GETACT to get the active buffer number for this channel (returned in .A) Transfer the buffer number to .X and use it load .A wit.h the last job number for this buffer from LSTJOB,X ($025B,X). AND the last job number with $01 and compare it. wit.h the current drive number in DRVNUM ($7F). If not equal, branch to CLD2 . JSR to FRECHN ($D227) to free this channel. Decrement the secondary address in SA ($83) and if there are more to do (not. $FF yet), branch back to CLSD Terminate routine with an RTS.
FREB1
$D2F7 $D2F9
CLRCHN CLRC1
CLDCHN CLSD
$D313 $D317
$D320
293
NAME
ADDRESS
DESCRIPTION OF WHAT ROM ROUTINE DOES Steal an inactive buffer: Scan t.he least recently used table and steal t.he first inactive buffer found. Returns the stolen buffer number in .A Save the value in TO ($6F) on the stack and zero .Y (t.he index to LRUTBL) . Load .X (the channel index) with the value from LRUTBL,Y ($FA,Y). Load .A with the buffer status for this channel from BUFO,X ($A7,X). If this buffer is active (status < 128), branch to STL10. Compare the status to $FF (unused). If not equal, it's inactive so branch to STL30 to steal it! Transfer the channel number from .X to .A, clear t.he carry flag, add $07 (t.he maximum number of channels +1), and transfer the result back into .X. Not.e .X now points to the alternative buffer for this channel. Load .A wit.h the buffer status for this channel from BUF0,X ($A7,X). If this buffer is active (status < 128), branch to STL3 0. Increment .Y and compare the new value wit.h #$05 (the maximum number of channels + 1). If there are still some channels left to check, branch to STL05 No luck stealing a buffer so load .X wit.h $FF (indicates failure) and branch to STL6 0 t . o exit. Store t.he channel number (in .X) into T0 ($6F) temporarily. AND t.he buffer number in .A with $3F to mask off any higher order bits and transfer the result to .X. Check if the buffer is being used for a job currently underway by loading .A wit.h the job queue byte for the buffer from JOBS, X ($00,X). If bit 7 is set., a job is in progress so branch back to STL40 t . o wait for completion. Compare the job queue value wit.h $02 to see if any errors occurred. If there were no errors (job queue was $01), branch to STL50 to steal the buffer. No luck so load .X with t.he value we save int.o T0 ($6F) and compare it to $07 (the maximum number of channels+1). If .X < $07 we still need to check the alternative buffer for this channel so branch to STL10.
STL3UF STL05
$D34D
STL20
$D355
$D367
$D36B $D36F
294
NAME
ADDRESS $D371
DESCRIPTION OF WHAT ROM ROUTINE DOES If .X >= $07, we were checking the alternative channel so branch back to STL20 to check the next channel. We've found an inactive buffer, now to steal it! Load .Y with the channel number from T0 ($6F) and store $FF into BUF0,Y ($A7,Y) to steal it. Pull the original value of T0 off the stack and restore it. Transfer the buffer number from .X to .A (sets the N flag if not successful) and terminate routine with an RTS. Find free LINDX and allocate in LINUSE Load .Y with $00 and .A with $01. Test whether the same bit is set in LINUSE ($0256) and the accumulator. If a bit is set in LINUSE, the corresponding channel is free. If the test indicates a free channel, branch to FND30. Increment .Y (the counter) and do an ASL on the value in the accumulator to shift. the test bit one place left. If more tests are needed, branch to FND10. No channel found so load .A with $70 to to indicate a NO CHANNEL error and JMP to CMDERR ($ClC8). EOR the bit mask (in .A) with $FF to flip the bits, AND the flipped mask with LINUSE to clear the appropriate bit, and store the result back in LINUSE ($0256) . Transfer the channel number (LINDX) from .Y t . o .A and exit with an RTS. Get next byte from a channel: JSR to FNDRCH ($D0EB) to find an unused read channel. JSR to SETLDS ($C100) to turn on the drive active light. JSR to GET ($D3AA) to get. one byte from any type of file. Load .X with the current channel number from LINDX ($82) and load .A with the data byte from CHNDAT,X ($023E). Terminate routine with an RTS. Get next byte from any type of file: Load .X with t.he current- channel number from LINDX ($82) JSR to TYPFIL ($D125) to determine t.he file type. If Z flag not set on return, this is not a relative file so branch to GET00.
STL5 0
$D3 73
STL60
$D37A
FNDLDX FND10
$D37F $D383
$D388
$D399
GBYTE
GET
$D3AA
295
NAME
ADDRESS $D3B1
DESCRIPTION OF WHAT ROM ROUTINE DOES It. is a relative file so JMP t . o RDREL ($E120) t . o do this type. Test if the current secondary address from SA ($83) is $0F (the CMD channel). If it is, branch to GETERC ($D414). Test if the last character we sent on this channel was an EOI by checking if the channel status in CHNRDY,X ($F2,X) is $08. If the last character was NOT an EOI, branch to GET1. Last character was EOI so JSR to TYPFIL ($D125) t . o determine the file type. If the file type is NOT $07, a random access file, branch to GET0. This is a direct access file so we will leave it. active. Store an $89 (random access file ready) as the channel status in CHNRDY,X ($F2,X) and exit with a JMP to RNDGET ($D3DE) t . o get the next, character ready. Last. character sent was EOI so set. t.he channel status as NOT READY by storing a $00 in CHNRDY,X ($F2,X). Terminate routine with an RTS. Test if this is a LOAD by testing if the secondary address in SA ($83) is a $00. If it is a LOAD, branch to GET6. It.'s not. a LOAD. Maybe it's a random access file. JSR to TYPFIL ($D125) to determine the file type. If t.he file type is less than $04, it is NOT a random access file, so branch to SEQGET. It is a random access file so JSR t . o GETPRE ($D12F) to set. up the right pointers in .X and .Y. Load the pointer to t.he data byt.e int.o .A from BUFTAB,X ($99,X). Compare t.his value to the pointer to t.he last. character pointer in LSTCHR,Y ($0244,Y) to see if we are up to t.he last one yet. If not, branch to RNGET1. We're at. the last. character so wrap t.he pointer around t . o t.he start again by storing $00 in BUFTAB,X ($99,X). Increment BUFTAB,X ($99,X) t . o point, to t.he next character. Load .A wit.h t.he dat.a bvt.e from BUFTAB,X ($99,X). Save t.he dat.a byt.e in CHNDAT,Y ($023E,Y) Load t.he pointer from BUFTAB,X and compare it to t.he value in LSTCHR,Y ($0244,Y) to see if t.his is the last character we're supposed to get.. If NOT, branch to RNGET3.
GET00
$D3B4 $D3BA
GET0
GET1 GET2
RNDGET
$D3DE $D3E1
296
NAME
ADDRESS $D3FA
DESCRIPTION OF WHAT ROM ROUTINE DOES Since this is the last character, set the channel status in CHNRDY,Y to $00 to indicate an EOI (end of information). Terminate routine wit.h an RTS. JSR to RDBYT ($D156) to read the next. data byte. Load .X wit.h the channel number from LINDX ($82) and store the data byte in CHNDAT,X ($00F2,X). Terminate routine with an RTS. Seems to be a LOAD. Test. if it is a directory listing by seeing if DIRLST ($0254) is a $00. If it is, this is not a directory listing so branch to SEQGET. It is a directory listing so JSR to GETDIR ($ED67) to get. a byt.e from the directory and then JMP to GET3. Get byte from t.he error channel: JSR to GETPNT ($D4E8) t . o read t.he active buffer pointer. If the buffer number is NOT $D4, lo byt.e of the pointer to one byt.e below error buffer, branch t . o GE10. Check if DIRBUF+1 ($95) equals $02, the hi byt.e of the pointer t . o the error buffer. If not., branch to GE10. Store a $0D (#13? RETURN) in DATA ($85) and JSR to ERROFF ($C123) to turn off t.he error LED. Load .A with $00 and JSR t . o ERRTS0 ($E6C1) t . o transfer the error message to the error buffer. Decrement CB+2 ($A5) so this pointer points t . o t.he start, of the message, .l.oad-^A.wltb.._$RQ. tEDJ_out .sjtatvusJ^ a_nrL. branch (always!) t . o GE30. JSR to GETBYT ($D137) to read a byt.e of the error message. Store the b^te in DATA ($85) and, if not. $00, branch to GE20. Load .A wit.h $D4, the lo byte cf the pointer to one byte below t.he error buffer and JSR to SETPNT ($D4C8) to set the pointers to the error buffer. Store the hi byt.e of t.he pointer t . o the error buffer ($02) into BUFTAB+l,X ($9A,X). Load .A wit.h $88, the channel status byt.e for ready-to-t.alk. Store the value in .A as the error channel status in CHNRDY+ERRCHN ($F7).
GET6
$D40E
GETERC
$D414
GE10
$D433
GE15
$D43A
297
NAME
ADDRESS $D447
DESCRIPTION OF WHAT ROM ROUTINE DOES Load .A wit.h the byte from DATA ($85) and store it as the channel data byte for the error channel in CHNDAT+ERRCHN ($0243). Terminate routine with an RTS. Read in the next block of a file by following the track and sector link. Set an EOF (end of file) indicator if the track link (first byte) is $00. JSR t . o GETACT ($DF93) to get the active buffer number (in .A). Multiply the buffer number by 2 (ASL) and transfer it to . X. Store a $00 in BUFTAB,X ($99,X) t . o sett-he buffer pointer to the first byte. Check first byte (track link) in the buffer, (BUFTAB,X). If it is zero, there are no more blocks to get so branch to NXTB1. Decrement the buffer pointer, BUFTAB,X ($99,X) by 1 so it is $FF and JSR to RDBYT ($D156). This forces a read of the next sector because we set- the pointer to the end of the current buffer. Terminate routine with an RTS. Direct block read: Load .A with $80, the job code for read and branch to DRT. Direct block write: Load .A with $90, t.he job code for write OR the job code in .A with the current drive number in DRVNUM ($7F) and store the result in CMD ($024D). Load .A wit.h the number of the buffer t . o use for the job from JOBNUM ($F9) and JSR to SETH ($D6D3) to set up the header image for the job. Load .X with the number of the buffer to use for the job from JOBNUM ($F9) and JMP to DOIT2 ($D593) to do the job. Open internal read channel: (SA=17) Use this entry point, for PRG files. Load .A with $01 (program file type) Open internal read channel (.A = any t.ype) Use t.his entry point for any file type. Store file type (.A) into TYPE ($024A) Store $11 (#17) as the current secondary address in SA ($83) .
$D44C
NXTBUF
$D44D
$D452 $D456
$D45A
NXTB1 DRTRD
$D45F $D460
DRTWRT DRT
$D470
OPNIRD
$D475
OPNTYP
$D477 $D47A
298
NAME
DESCRIPTION OF WHAT ROM ROUTINE DOES JSR to OPNRCH ($DC46) to open a read channel. Set .A to $02 and JMP to SETPNT ($D4C8) t . o set t.he buffer pointer t . o point past the track and sector link. Open internal write channel (SA=18) Store $12 (#18) as the current secondary address in SA ($83) . JMP to OPNWCH ($DCDA) t . o open the write channel. Allocate the next directory block: JSR to CURBLK ($DE3B) set. the TRACK($80) and SECTOR ($81) values from the current header. Set. TEMP ($6F) t . o $01 and save t.he current, value of SECINC ($69) , the sector increment used for sequential files, on the stack. Set. t.he sector increment, SECINC ($69) t . o $03, t.he increment, used for the directory track. JSF t . o NXTDS ($F12D) to determine the next available track and sector, Restore the original sector increment in SECINC ($69) from the stack, Set .A t . o $00 and JSR t . o SETPNT ($D4C8) t . o set. t.he pointer to the first, byte in t.he active buffer (track byt.e) . Load .A with the next, track from TRACK ($80) and JSR to PUTBYT ($CFF1) to store the track link in the buffer. Load .A with the next sector from SECTOR ($81) and JSR to PUTBYT ($CFF1) to store t.he sector link in the buffer. JSR t . o WRTBUF ($D0C7) to write the buffer out to disk. JSR to WATJOB ($D599) t . o wait until t.he write job is complete. Set .A to $00 and JSR t . o SETPNT ($D4C8) t . o set. t.he pointer to the first byte in the active buffer (track byte). Loop to zero the entire buffer. JSR to PUTBYT ($CFF1) t . o store $00 as t.he next. track link. Load .A with $FF and JMP to PUTBYT ($CFF1) to store $FF as the sector link. Set up pointer into active data buffer On entry: .A contains new pointer value Save the new pointer (in .A) int.o TEMP ($6F) and JSR t . o GETACT ($DF93) to find the active buffer number (in .A).
OPNIWR
$D486 $D48A
NXDRBK
$D48D $D490
$D49 7 $D49B $D49E $D4A1 $D4A6 $D4AB $D4B0 $D4B3 $D4B6 NXDB1 $D4BB $D4C0 $D4C3
SETPNT
$D4C8
299
NAME
DESCRIPTION OF WHAT ROM ROUTINE DOES Multiply the buffer number by 2 (ASL) and transfer the result into .X. Move the high byte of the buffer pointer from BUFTAB+1/X ($9A,X) to DIRBUF+l($95) Load the new buffer pointer value from TEMP ($6F) into .A. Store this new value into BUFTAB,X ($99,X) and DIRBUF ($94). Terminate routine with an RTS. Free both internal channels: (SA=17&18) Set SA ($83) to $11 (#17) the internal read channel and JSR to FRECHN ($D227) to free the internal read channel. Set SA ($83) to $12 (#18) the internal write channel and JMP t . o FRECHN ($D227) to free the internal write channel. Get the active buffer pointer: JSR to GETACT ($DF93) to get the active buffer number (in .A). Multiply the buffer number by two (ASL) and transfer the result into .X. Move the hi byte of t.he buffer pointer from BUFTAB+1,X ($9A,X) into the hi byt.e of the directory buffer pointer DIRBUF + 1 ($95) . Move t.he lo byte of the buffer pointer from BUFTAB,X ($99,X) into the lo byt.e of the directory buffer pointer DIRBUF ($94). (.A = lo byte of the pointer) Terminate routine with an RTS. Direct read of a byt.e: (.A = position) On entry:.A = position of byte in buffer On exit:.A = dat.a byte desired Store lo byt.e of pointer to desired byte (in .A) int.o TEMP+2 ($71). JSR t . o GETACT ($DF93) to get the active buffer number (in .A). Transfer buffer number int.o .X and load .A with t.he hi byte of the active buffer pointer from BUFIND,X ($FEEO,X). Store t.his value into TEMP+3 ($72) . This creates a pointer to the byte in $71/72. Zero .Y and load .A with the desired byte from (TEMP+2),Y? ($71),Y. Terminate routine with an RTS. Set up job using last. job's drive: NOTE: For t.his entry, job code is in CMD and .X is buffer number (job #) Load .A with previous job number from LSTJOB,X ($025B,X), AND the job number with $01 t . o leave just the drive number
FREICH
$D4DA $D4E1
GETPNT SETDIR
$D4F1
$D4F5
DRDBYT
$D5 01 $D505
SETLJB
$D5 06
300
NAME
ADDRESS
DESCRIPTION OF WHAT ROM ROUTINE DOES bits, and OR the result with t.he new job code on CMD ($024D). The resulting new job code is in .A. Set. up new job: NOTE: For t.his entry, job code is in .A and .X is buffer number (job #) Save new job code on the stack and store t.he number of the buffer to use (.X) in JOBNUM ($F9). Transfer t.he buffer number from .X t . o .A, multiply it. by 2 (ASL) and transfer it back into .X. Move the desired sector from HDRS+l,X ($07,X) int.o CMD ($024D). Load .A wit.h the desired track from HDRS,X ($06,X). If it is $00, branch t . o TSERR ($D54A). Compare the desired track (in .A) with the maximum track number from MAXTRK ($FED7). If it is too large, branch to TSERR ($D54A). Transfer t.he desired track number from .A to .X. Pull the job code off the stack and immediately push it. back onto the stack. AND t.he job code in .A with $F0 to mask off the drive bits and compare it to $90 (t.he job code for a write) . If this is not a write job, branch to SJB1. Pull the job code off the stack and immediately push it back ont.o the stack. Do an LSR on the job code in .A to find the drive to use. If it is drive 1, branch to SJB2. Use drive 0 so load DOS version from DSKVER ($0101) and branch to SJB3. Use drive 1 so load DOS version from DSKVER+1 ($0102) . If DOS version is $00 (no number) , it. is OK, so branch to SJB4. NOTE: On the 1541 t.he DOS version code (normally 65) is stored in ROM, not in RAM as on the 4040. This means you can not soft set a DOS version number on the 15411 However, a DOS version number of $00 is OK.
SETJOB
301
NAME
ADDRESS $D5 3A
DESCRIPTION OF WHAT ROM ROUTINE DOES Compare the DOS version number with t.he 1541 DOS version number ($65) from VERNUM ($FED5). If the version numbers do not. match, branch to VNERR ($D572). Transfer the desired track number from .X to .A and JSR t . o MAXSEX ($F24B) to calculate the maximum sector number+1 for t.his track (returned in .A) . Compare this value with the desired sector number in CMD. If the desired sector number is legal, branch to SJBJ. Track and/or sector number is illegal so JSR t . o HED2TS ($D552) t . o store the values in TRACK ($80) and SECTOR ($81) . Load. .A wit.h $66 to indicate a bad track and sector and JMP t . o CMDER2 ($E645). Set desired track and sector values: Load .A with the number of t.he buffer to use for this job from JOBNUM ($F9). Multiply t.he buffer number by 2 (ASL) and transfer it to .X. Move the desired track number from HDRS,X ($06,X) to TRACK ($80). Move t.he desired sector number from HDRS+l,X ($07,X) to SECTOR ($81). Terminate routine with an RTS. Check for bad track and sector values: Load .A from TRACK ($80). If the track is $00, branch back to TSER1 ($D54D). Compare the track t . o the maximum track number allowed, MAXTRK ($FED7). If too large, branch back to TSER1. JSR t . o MAXSEC ($F24B) to calculate t.he maximum sector number allowed on t.his track. If too large, branch t . o TSER1. Terminate routine with an RTS. Bad DOS version number: JSR to HED2TS ($D552) t . o store t.he values in TRACK ($80) and SECTOR ($81). Load .A with $73 to indicate a bad DOS version number and JMP t . o CMDER2 ($E645) Conclude job set up: Load .X wit.h the number of the buffer to use for the job from JOBNUM ($F9). Pull t.he job code off the stack. Store the job code as the current command in CMD ($024D) , in t.he job queue at JOBS,X ($00,X) to activate the disk controller, and in LSTJOB,X.
SJB4
$D5 3F
TSERR TSER1
$D54A $D54D
HED2TS
$D5 52
$F5 56 $F5 5A $F5 5E TSCHK $D5 5F $D5 63 $D568 $D571 VNERR $D5 72 $D5 75
SJB1
302
NAME
ADDRESS $D585
DESCRIPTION OF WHAT ROM ROUTINE DOES Terminate routine with an RTS. Do a read job; return when done OK: Load .A with $80, the read job code and branch to DOJOB. Do a write job; return when done OK: Load .A with $90, the write job ccde. OR t.he job code with the current drive number in DF:VNUM ($7F) . Load .X wit.h the number of the buffer to use for the job from JOBNUM ($F9). Store complete job code in CMD ($024D). Lcad .A wit.h job code from CMD ($024D) . JSR t . o SETJOB ($D50E) to start job. Wait. until job is completed: JSR to TSTJOB ($D5A6) t . o check if job is done yet (error code returned in .A). If job not done yet, branch to WATJOB. Save error ccde on the stack. Set job completed flag, JOBRTN ($0298), to $00. Recover error code from stack (in .A). Terminate routine with an RTS. Test. if job done yet: If not done, return. If done OK, then return. If not. OK, redo the job. Load .A with value from the job queue, JOBS,X ($00,X). If .A > 127, job not done yet so branch to NOTYET t . o exit with carry flag set. If .A < 2, job was completed with no errors so branch to OK t . o exit with the carry flag clear. Compare t.he error code to $08. If it. is $08, a fatal write protect error has occured so branch to TJ10 and abort. Compare the error code to $0B. If it. is $0B, a fatal ID mismatch error has occured so branch to TJ10 and abort. Compare the error code to $0F. If it. is NOT $0F, a non-fatal error has occured so branch t . o RECOV and t.ry again. NOTE: an error code of $0F means a fatal drive-not.-available error has occured. Test bit 7 of t.he job return flag, JOBRTN ($0298). If it. is set, the disk has been initialized and t.his is the first attempt t . o carry out. t.he job, so branch t . o OK t . o return with t.he carry flag clear.
DOREAD
$D586
DOWRIT DOJOB
DOIT DOIT2
WATJOB
TSTJOB
TJ10
$D5BA
303
DESCRIPTION OF WHAT ROM ROUTINE DOES JMP to QUIT2 ($D63F) to try to recover. Clear the carry flag and terminate t.he routine wit.h an RTS. Set t.he carry flag and terminate the routine with an RTS. Save .Y value and t.he current drive number from DRVNUM ($7F) on the stack. Load the job code for t.he last. job from LSTJOB,X ($025B,X), AND it with $01 to mask off the non-drive bits, and store the result as the current drive number in DRVNUM ($7F). Transfer t.he drive number from .A t . o .Y and move the LED error mask from . o ERLED ($026D) LEDMSK,Y ($FECA ,Y) t JSR t . o DOREC ($D6A6) t . o do last. job recovery. On return, if the error code (in .A) is $01, it worked so branch t . o REC01. Retry didn't work, JMP t . o REC95 ($D66D) Load .A wit.h the original job code from LSTJOB,X ($025B,X) , AND it wit.h $F0 t . o mask off the drive number bits, and save it on t.he stack. Check if the job code was $90 (a write job). If not., branch t . o REC0. This is a write job. OR the current drive number from DRVNUM ($7F) wit.h $B8 (t.he job code for a sector seek) and st.ore t.he result in LSTJOB,X ($025B,X) . This replaces the original write job with a seek job during recovery. See if the head is on track by checking bit. 6 of REVCNT (6A) . If this bit is set, the head is on track so branch t . o REC5 . Head nct. on track so zero t.he offset table pointer, EPTR ($0299) and t.he total offset TOFF ($029A). Load .Y wit.h t.he offset table point.er EPTR ($0299) and .A wit.h the total offset. TOFF ($029A) . Set. t.he carry flag and subtract the offset OFFSET,Y ($FEDB) from the total offset in .A. Store the result as t.he new total offset in TOFF ($029A). Load .A with t.he head offset, from OFFSET,Y and JSR to HEDOFF ($D676) to move t.he head so it is on track. Increment, the cffset table pointer and JSR to DOREC ($D6A6) t . o attempt t . o recover. On return, if t.he error code in .A < $02, the recovery worked so branch to REC3.
$D5D2 $D5D9
REC01
$D5E0 $D5E3
$D5E9 $D5ED
REC0
$D5F4
REC1
$D600 $D606
$D6 0D $D613
304
NAME
ADDRESS $D61D
DESCRIPTION OF WHAT ROM ROUTINE DOES That. try at recovery did not work so increment the offset table pointer by 1 and load .A wit.h the offset from OFFSET , Y ( $FEDB, Y) . If t.he value loaded is not. $00, branch to REC1 t . o try again. One more try on the offset. Load .A wit.h the total offset from TOFF ($029A) and JSR to HEDOFF ($D676). If no error on return, branch to REC9. Check bit 7 of the error recover count REVCNT ($6A). If this bit. is clear, branch t . o REC7 to do a bump t . o track 1. Pull the original job code off the stack. If it is NOT $90 (a write job) branch to QUIT2. For write jobs only, OR the job code in .A with the drive number from DRVNUM and put the result in LSTJOB,X ($025B,X) to restore the original value. Load .A with the error code from JOBS,X ($00,X) and abort with a JSR t . o ERROR ($E60A). Pull t.he job code off the stack (in .A). Check bit 7 of the job return flag JOBRTN ($0298). If this bit is set, branch to REC95 to exit, with job error. Push the job code back onto the stack. Do a bump to track 1 by loading .A with $C0 (BUMP job code) , ORing it. with the current, drive number from DRVNUM ($7F) , and storing the result in the job queue at JOBS,X ($00,X). Wait for current job t . o be completed. JSR to DOREC ($D6A6) to try one more time. On return, if the error code (.A) is not $01 (no error), give up in disgust, and branch t . o QUIT. Pull t.he original job code off the stack and compare it to $90 (the job code for a write job). If t.his isn't a write job, branch to REC9 5. OR the job code (in .A) with t.he drive number from DRVNUM I $7F) and store the value in LSTJOB,X. . o try one last JSR to DOREC ($D6A6) t time. On return, if the error code (.A) is not $01 (no error), give up in disgust and branch to QUIT2. Pull the original drive number off the stack and store it in DRVNUM ($7F). Pull the original .Y value off t.he stack and restore .Y.
REC3
$D6 25
REC5 QUIT
$D64B
REC8
$D651 $D655
REC9
$D65C
$D661 $D666
REC9 5
$D66D $D6 70
305
NAME
ADDRESS $D672
DESCRIPTION OF WHAT ROM ROUTINE DOES Load .A with t.he error code from JOBS,X ($00,X), clear the carry flag, and exit. wit.h an RTS. Adjust head offset: On entry: .A = OFFSET If .A = 0, no offset required so branch to HOF3. If .A > 127, head needs to be stepped inward so branch to HOF2. We want t . o move head outward 1 track so: load .Y with $01 and JSR to MOVHED ($D693) to move the head. On return, set the carry flag and subtract $01 from the value in .A. If the result is not $00, the head has notfinished so branch back to HOFl. If the head is finished moving, branch to HOF3. We want to move head inward 1 track so: load . Y with $FF and JSR t . o MOVHED ($D693) to move the head. On return, clear the carry flag and add $01 to the value in .A. If the result is not $00, the head has not finished so branch back to HOF2. Terminate routine with an RTS. Step head inward or outward 1 track: Save the value in .A onto the stack. Transfer the number of steps to move (phase) from .Y into .A. Load .Y with the current drive number from DRVNUM ($7F). Store t.he phase into PHASE, Y ($02FE,Y). Compare t.he phase in .A with the value in PHASE,Y ($02FE,Y). If they are equal, the controller hes not yet moved t.he head so branch back to MH10. Store $00 in PHASE,Y ($02FE,Y) so head won't, move any more. Pull original value of .A off t.he stack. Terminate routine with an RTS. Load .A with the retry counter, REVCNT ($6A) , AND it with $3F t . o mask off t.he high order bits, and transfer the result int.o .Y. Load .A with the error LED mask from ERLED ($026D), EOR it with the disk controller port B, DSKCNT ($1C00) and store it back in DSKCNT ($1C00) t . o t.urn the drive light OFF.
HEDOFF
$D676 $D67A
HOFi
$D67C $D681
$D68D
HOF3 MCVHED
MH10
$D697 $D69A
DORECl
$D6AB
i 306
NAME
ADDRESS $D6B4
DESCRIPTION OF WHAT ROM ROUTINE DOES Restart the last job by moving the job code from LSTJOB,X ($025B,X) to the job queue at JOBS,X ($00,X). Loop to wait until the value in the job queue at JOBS,X ($00,X) is less than 127 (indicates job has been completed). Test to see if the error code returned is $01 (successful). If everything was OK, branch to DOREC3. It didn't work. Decrement the error counter in .Y and, if .Y has not counted down to $00 yet, branch to DORECl and keep trying. Save the error code onto the stack. Load .A with the error LED mask from ERLED ($026D), OR it with the disk controller port B, DSKCNT ($1C00) and store it back in DSKCNT ($1C00) to turn the drive light back ON. Pull the error code back off t.he stack. Terminate routine with an RTS. Set up the header for the active buffer: Uses values in TRACK, SECTOR, & DSKID. JSR to GETACT ($DF93) t . o get the number of the active buffer (returned in .A). Multiply the number of the active buffer (in .A) by 2 (ASL) and transfer the result into .Y. Move the track number from TRACK ($80) to HDRS,Y ($0006,Y). Move the sector number from SECTOR ($81) to HDRS+1,Y ($0007,Y). Load .A with t.he current drive number from DRVNUM ($7F), multiply it by 2(ASL) and transfer the result to .X. NOTE: this last bunch of code really does nothing. On the 4040 it is done in preparation for moving the ID characters. However, this is not done here on the 1541! Terminate routine with an RTS. Add new filename t . o the directory: Save t.he following variables ont.o t.he stack: SA ($83), LINDX ($82), SECTOR ($81) , and TRACK ($80) . Set the current secondary address, SA ($83) to $11 (#17), the internal read channel. JSR to CURBLK ($DE3B) to find a read channel and set. TRACK ($80) and SECTOR ($81) from the most recently read header
DOREC2
$D6B9
$D6C1
DOREC3
$D6C4 $D6C5
$D6CE $D6CF
SETHDR
307
NAME
DESCRIPTION OF WHAT ROM ROUTINE DOES Save the file type, TYPE ($024A) of the file t . o be added onto t.he stack. Load .A with the drive number for t.he new file, and it wit.h $01, and store t.he result as the current drive, DRVNUM($7F) Load .X wit.h the last. job number frcm JOBNUM ($F9). EOR the drive number in .A with t.he last job code from LSTJOB,X ($025B,X), divide the result by 2 (LSR), and check if the carry flag is clear. If it is, t.he new file uses the same drive as t.he last. job sc there is no need to change the drive and we can branch to AF08. Store $01 in DELIND ($0292) to indicate that we are searching for a deleted entry and JSR to SRCHST ($C5AC). On return, if .A=0, all directory sectors are full so branch to AF15 t . o start a new sector. If .A<>0, we have found a spot t . o put. t.he new entry so branch t . o AF20. Since we have used this drive before, some of t.he directory information is in memory. Check if DELSEC ($0291) is $00. If it is, we didn't locate a deleted entry the last time we read in t.he directory so branch to AF10. Since DELSEC is not $00, it is the number of the sector containing t.he first available directory entry. See if t.his sector is currently in memory by comparing t.his sector number with the one in SECTOR ($81). If t.hey are equal, the sector is in memory so branch to AF20. Since t.he desired sector is not in memory, set SECTOR ($81) to t.he desired sector number and JSR to DRTRD ($D460) t . o read in the sector. Now branch to AF20. Store $01 in DELIND ($0292) to indicate that we are looking for a deleted entry and JSR to SEARCH ($C617) to find the first deleted or empty directory entry. On return, if .A is not. equal t . o $00, a deleted or empty entry was found so branch t . o AF20. No empty entries so we have to start a new sector so JSR to NXDRBK ($D48D) to find us the next available sector.
$D709
AF0 8
$D715
$D71A
$D71E
AF10
$D726
308
NAME
ADDRESS $D733
DESCRIPTION OF WHAT ROM ROUTINE DOES Move the new sector number from SECTOR ($81) to DELSEC ($0291) and set DELIND ($0292) t . o $02. Load .A with the pointer that points to first character in the directory entry, DELIND($0292) , and JSR to SETPNT($D4C8) t . o set t.he pointers t . o this entry. Pull t.he file type off the stack and store it back in TYPE ($024A). Compare the file t.ype to $04 (REL type) . If t.his is not a relative file, branch to AF25. Since it is a REL file, OR the file t.ype (in .A) wit.h $80 t . o set bit 7. JSR to PUTBYT ($CFF1) to store the file t.ype (in .A) into the buffer. Pull the file's track link off the stack, store it in FILTRK ($0280), and JSR to PUTBYT ($CFF1) to store t.he track link in the buffer. Pull t.he file's sector link off the stack, store it in FILSEC ($0285) , and JSR t . o PUTBYT ($CFF1) to store the sector link in the buffer. JSR t . o GETACT ($DF93) to get the active buffer number (in .A) and transfer the value to .Y Load .X with the file table pointer from FILTAB ($027A). Load .A with $10 (#16) and JSR to TRNAME ($C66E) to transfer the file name to t.he buffer. Loop t . o fill directory entry with $00's from (DIRBUF),16 to (DIRBUF),27. Check the value in TYPE ($024A) to see if this is a relative file. If not., branch to AF50. For REL files only: Load .Y wit.h $10. Move t.he side-sector track number from TRKSS ($0259) t . o (DIRBUF),Y. Increment Y Move the side-sector sector number from SECSS ($025A) to (DIRBUF),Y. Increment Y Move t.he record length from REC ($0258) to (DIRBUF),Y. JSR t . o DRTWRT ($D464) to write out. the directory sector. Pull t.he original value of LINDX off the stack, store it. back in LINDX ($82) , and transfer the value into .X. Pull the original value of SA off the stack, store it back in SA ($83) .
AF2 0
$D7 3D
$D757
$D75E $D762 $D766 $D76D $D776 $D77D $D77F $D785 $D78B AF5 0 $D79 0 $D793 $D797
309
NAME
ADDRESS $D79A
DESCRIPTION OF WHAT ROM ROUTINE DOES Load .A with the number of the directory sector containing the new entry from DELSEC ($0291) and store it in ENTSEC ($D8) and in DSEC,X ($0260,X). Load .A with the pointer to the start of t.he new entry from DELIND ($0292) and store it in DIND,X ($0266,X). Load .A with t.he file t.ype of the new entry from TYPE ($024A) and store it in PATTYP ($E7). Load .A with the current drive number from DRVNUM ($7F) and store it in FILDRV ($E2). Terminate routine with an RTS. Open a channel from serial bus: The open, load, or save command is parsed. A channel is allocated and the directory is searched for the filename specified in the command. Move the current secondary address from SA ($83) to TEMPSA ($024C). JSR to CMDSET ($C2B3) to set the command string pointers. On return, store the .X value in CMDNUM ($022A). Load .X wit.h the first character in the command string CMDBUF ($0200). Load .A with the secondary address from TEMPSA ($024C). If the secondary address is not $00, this is not. a load so branch to OP021. Compare the value in .X with $2A ("*") to check if the command is "load the last referenced program". If not $2A, branch to OP021. Appears to be "load last". Check by loading .A with the last. program's track link from PRGTRK ($7E). If .A=0, there is no last program so branch to OP0415 t . o initialize drive 0. Seems OK, let's load last. program. Store t.he program's track link (in .A) into TRACK ($80) . Move the program's drive number from PRGDRV ($026E) t . o DRVNUM ($7F) . Store $82 (program) as the file type in PATTYP ($E7). Move the program's sector link from PRGSEC ($026F) into SECTOR ($81). JSR to SETLDS ($C100) to turn on the drive active LED. JSR to OPNRCH ($DC46) to open a read channel.
OPEN
$D7C7
$D7CB
OP02
310
NAME
ADDRESS
$D7E7
ENDRD
OP021
$D7F3
$D7F7
$D7FC
OP04
$D7FF
$D812 OP0 41
$D815
$D819
OPQ415
$D81C $D821
311
NAME
OP049 OP05
ADDRESS
$D834 $D837
OPlO
$D83C
$D861
$D866 $D869
$D86F
$D87B
$D87F
$D882
312
NAME
ADDRESS
$D887
$D896
$D89D
OP6O
$D8B1 $D8B4
$D8BA
$D8BF
OP7 5
$D8C6
OP7 7
$D8CD
$D8D3
$D8D6
OP8O
$D8D9
313
NAME
ADDRESS
$D8DE
OP81
$D8E1
OP82
$D8F5
$D8FE
$D917
314
NAME
ADDRESS
$D932
$D937
$D93A $D93D
OP90
$D940
OP95
$D945
OPlOO
$D94A
$D951
OP115
$D965
OP120
$D96A $D96F
$D976
$D97A
$D980
$D987
315
NAME
ADDRESS
$D98A $D98D
OP125
OPREAD $D9A0
$D9B9 $D9BE
OP130
$D9DF
$D9E2
316
NAME
ADDRESS
OPWRIT
$D9E3
$D9F5
CKTM
$DA09
CKM1
$DA11
CKM2
$DA19 $DAlC
CKT2
$DA26 $DA29
317
NAME
ADDRESS
APPEND
$DA3 7
$DA3D
AP3 0
$DA42 $DA45
$DA4B
$DA54
LOADIR
LD01
$DA62
$DA65
LD02
$DA6D
318
NAME
ADDRESS
$DA6F $DA7 8 $DA7C
LD0 5
$DAA1
$DAA4 LD2 0 $DAA7 $DAAA $DAAD $DAAF $DAB2
$DAB7
$DABB $DABF
CLOSE
$DAC0 $DAC5
319
NAME
ADDRESS
$DAC9
CLS05 CLS10
$DAE1
CLSALL CLS20
C l o s e all f i l e s : (when C M D c l o s e d ) Set s e c o n d a r y a d d r e s s , SA ($83) t.o $0E. JSR to C L S C H N ($DB02) to close c h a n n e l . D e c r e m e n t SA ($83). If m o r e s e c o n d a r y a d d r e s s e s to do (SA>=0) loop to C L S 2 0 . C h e c k the e r r o r status in E R W O R D ($026C) If status is not $00, the last c o m m a n d p r o d u c e d an e r r o r so b r a n c h to C L S 2 5 . JMP to E N D C M D ($C194) to end c o m m a n d . Error so JMP to SCRENl ($ClAD)
CLSCHN
$DB02 $DB04
$DB09
C l o s e file w i t h s p e c i f i e d sec. a d d r e s s Load .X w i t h t.he s e c o n d a r y a d d r e s s from SA ($83) . Load .A wit.h the channel status from L I N T A B , X ($022B,X). If the status is not $FF (closed), b r a n c h to C L S C 2 8 . C h a n n e l a l r e a d y closed so t e r m i n a t e r o u t i n e wit.h an RTS. A N D the channel status (in .A) w i t h $0F to leave only the b u f f e r n u m b e r and store the result in LINDX ($82) . JSR to T Y P F I L ($D125) to d e t e r m i n e the file type (returned in .A). If file type is $07 (direct c h a n n e l ) b r a n c h to C L S C 3 0 . If file t.ype is $04 (relative file) b r a n c h to C L S R E L . JSR to FNDWCK ($D107) to find an unused w r i t e c h a n n e l . If none found, b r a n c h to CLSC31 JSR t.o C L S W R T ($DB62) to close off sequential write. JSR t.o C L S D I R ($DBA5) to close d i r e c t o r y
CLSC28
$DBOC
$DB20 $DB2 3
320
NAME
CLSC3 0 CLSC31
ADDRESS
$DB2 6 $DB29
CLSREL
$DB35
$DB3B $DB41
$DB55
Sub t.o close r e l a t i v e file: JSR to SCRUB ($DDF1) to w r i t e out BAM if it is d i r t y (RAM v e r s i o n m o d i f i e d ) . JSR t.o DBLBUF ($CFlE) to set up d o u b l e b u f f e r i n g and read a h e a d . JSR to S S E N D ($ElCB) to p o s i t i o n side sector & b u f f e r table p o i n t e r to the end of the last r e c o r d . Load .X w i t h the side sector n u m b e r from SSNUM ($D5), store this byte in T 4 ( $ 7 3 ) , and i n c r e m e n t T4 by 1. Zero T1 ($70) and T2 ($71). Load .A wit.h the p o i n t e r t.o the side sector v a l u e in the d i r e c t o r y b u f f e r from SSIND ($D6) , set. t.he carry flag, s u b t r a c t $0E (the side sector o f f s e t - 2 ) , and store the r e s u l t in T3 ($72) . JSR to SSCALC ($DF51) to c a l c u l a t e the n u m b e r of side sector b l o c k s n e e d e d . Load .X w i t h the a c t i v e b u f f e r n u m b e r from L I N D X ($82) . Move t.he lo byte of t.he n u m b e r of side sector b l o c k s from T1 ($70) t.o N B K L , X ($B5,X) and the hi b y t e from T2 ($71) to N B K H , X ($BB,X). Load .A w i t h $40 (the d i r t y flag for a r e l a t i v e record flag) and JSR to TSTFLG ($DDA6) to t.est if r e l a t i v e record must be w r i t t e n out.. If not., b r a n c h to C L S R 1 . JSR to C L S D I R ($DBA5) to close the d i r e c t o r y file. JMP to FRECHN ($D227) to clear the channel and t e r m i n a t e r o u t i n e . C l o s e a s e q u e n t i a l file w r i t e c h a n n e l : Lcad .X w i t h the a c t i v e b u f f e r n u m b e r from L I N D X ($82) . Load .A w i t h t.he n u m b e r of b y t e s w r i t t e n in this sector from N B K L , X ($B5,X) and OR .A w i t h t.he n u m b e r of data b l o c k s w r i t t e n from N B K L , X ($B5,X). If t.he result is not $00, at least one b l o c k of t.he file has b e e n w r i t t e n so b r a n c h to C L S W 1 0 . No b l o c k s have been w r i t t e n so JSF to G E T P N T ($D4E8) to get the p o i n t e r into t.he data b u f f e r (returned in .A) . If t.his v a l u e is g r e a t e r than two, at least one byte has been w r i t t e n so b r a n c h to CLSW10.
CLSWRT
$DB62 $DB64
$DB6A
321
NAME
ADDRESS
$DB71
CLSW10
$DB76
CLSW20
$DB90
CLSDIR
$DBA5
$DBAA $DBAD
$DBB2
322
NAME
ADDRESS
$DBB8
$DBD3
$DBD8
$DBEB
$DBEC $DBF0 $DBF2
JSR to T Y P F l L ($D125) to d e t e r m i n e the file type (returned in .A). If file type is $04 (a r e l a t i v e file) b r a n c h to C L S D 6 . Load .A wit.h the file type from R0,Y, A N D it w i t h $8F to mask off the r e p l a c e bit, and store the r e s u l t b a c k in R0,Y. Increment .Y. The p o i n t e r at (R0),Y now points to t.he old track link. Copy the old track link from (R0),Y to into TRACK ($80) . Store the .Y v a l u e into T E M P + 2 ($71). Load .Y w i t h $lB (#27). The p o i n t e r at (R0),Y now p o i n t s to the r e p l a c e m e n t sector link. Load .A w i t h the r e p l a c e m e n t sector link from (R0),Y and save it on the stack. D e c r e m e n t .Y. The p o i n t e r at (R0),Y now p o i n t s to the r e p l a c e m e n t track link. Load .A wit.h t.he r e p l a c e m e n t track link. If this link is N O T $00, b r a n c h to CLSD4 T r o u b l e ! R e p l a c e m e n t track link should never be $00. So put r e p l a c e m e n t track link in T R A C K ($80) .
323
NAME
ADDRESS
$DBFE $DC01
CLSD4
$DC11 $DC13
$DC14 $DC18
$DClB
CLSD5
$DClE $DC21
CLSD6
$DC29 $DC2C
324
NAME
ADDRESS
$DC40 $DC43
OPNRCH
$DC6 0
ORlO
$DC6 5
$DC6A
$DC74 $DC79
$DC7E
OR2Q
325
NAME
ADDRESS
$DC8A $DC8F $DC92 $DC95
OROW
$DCA3 $DCA6
OR3Q
$DCA9 $DCAC
$DCAE $DCB1
$DCB5
INITP
$DCB6 $DCB8
$DCBC
$DCC1
$DCC7 $DCC9
$DCCE
$DCD4 $DCD9
326
NAME
OPNWCH
ADDRESS
$DCDA $DCDD $DCE2 $DCE5 $DCE8 $DCEA $DCEE
$DCF3
$DCF8
$DD09 $DDOE
$DD13
OW20
327
NAME
ADDRESS
$DD2C $DD2E $DD31
$DD3 6 $DD3B
$DD40 $DD45
$DD4B
$DD50
$DD55
$DD5A $DD5D
$DD62
$DD72 $DD74
$DD7B $DD7E
$DD81
328
NAME
ADDRESS
$DD84 $DD87 $DD8A
PUTSS
Set/Clear flag:
SCFLG $DD95 If carry flag clear, branch to CLRFLG SETFLG $DD97 $DD99 $DD9B Set flag: Load .X with the active buffer number from LINDX ($82). OR the byt.e in .A with t.he file type in FILTYP,X ($EC,X). If result CLRFLG $DD9D $DD9F Clear flag: Load .X with t.he active buffer number from LINDX ($82) . EOR t.he byt.e in .A with $FF to flip all t.he bit.s. AND the byt.e in .A with t.he file t.ype in FILTYP,X ($EC ,X). Store the result in .A, as the new file type in FILTYP,X ($EC,X). Terminate routine with an RTS. Test flag: Load .X with the active buffer number from LINDX ($82) . AND the byte in .A wit.h t.he file type in FILTYP,X ($EC,X). Terminate routine with an RTS. Test if t.his is a write job: JSR to GETACT ($DF93) to get. the active buffer number (returned in .A). Transfer t.he buffer number to .X. Load .A with the last job code from LSTJOB,X ($025B), AND the job code with $F0 to mask off the drive bits, and compare the result with $90 (write job code) . This sets t.he Z flag if this is a write job. is not $00, branch t.o CLRF10.
$DDA1
CLRF10 $DDA3 $DDA5
TSTFLG
$DDA6 $DDA8
$DDAA
TSTWRT
329
NAME
ADDRESS
$DDB6
TSTCHN TSTC2 0
TSTC3 0
$DDC2
TSTRTS TSTC4 0
Test for a c t i v e files in LINDX t a b l e s : C=0 if file a c t i v e X = E N T F N D ; Y=LINDX C=1 if file inactive X=18 Load .X w i t h $00 (secondary a d d r e s s ) Save .X v a l u e into TEMP + 2 ($71) . Load .A w i t h the b u f f e r n u m b e r for this s e c o n d a r y a d d r e s s from L I N T A B , X (022B,X) If the b u f f e r n u m b e r is N O T $FF, b r a n c h to TSTC4 0 for further t e s t i n g . R e s t o r e .X v a l u e from T E M P + 2 ($71) and increment it by 1. If t.he r e s u l t i n g .X v a l u e is less than $10 (t.he m a x i m u m sec. a d d r e s s - 2), loop back to T S T C 2 0 . T e r m i n a t e r o u t i n e w i t h an RTS. Save .X v a l u e into TEMP + 2 ($71) . A N D the buffer n u m b e r in .A w i t h $3F to mask off the h i g h e r order b i t s and transfer the result into .Y. Load .A w i t h the file type for t.his s e c o n d a r y a d d r e s s from F I L T Y P , Y ($EC,Y), A N D it wit.h $01 to mask off the n o n - d r i v e b i t s , and store the result in TEMP+1 ($70) . Load .X w i t h the index entry found from E N T F N D ($0253) . Load .A w i t h the drive n u m b e r for this secondary a d d r e s s from F I L D R V , X ($E2,X), A N D it w i t h $01 to mask off the n o n - d r i v e bits, and c o m p a r e the result w i t h the drive n u m b e r in TEMP + 1 ($70) . If the d r i v e s do not m a t c h , b r a n c h to TSTC30. Drive n u m b e r s m a t c h , now check if the d i r e c t o r y e n t r i e s match by c o m p a r i n g the entry sector in D S E C , Y ( $ 0 2 6 C , Y ) w i t h t.he one in E N T S E C , X ($D8,X). If t.hey do not m a t c h , b r a n c h to T S T C 3 0 . Drive n u m b e r s are m a t c h , now check if the d i r e c t o r y e n t r i e s match by c o m p a r i n g the entry index in D I N D , Y ($0266,Y) w i t h the one in E N T I N D , X ($DD,X). If they do not m a t c h , b r a n c h to T S T C 3 0 . Clear the c a r r y flag to indicate that all tests passed and a c t i v e file f o u n d . T e r m i n a t e routine w i t h an RTS. W r i t e out b u f f e r if d i r t y : N O T E : a buffer is dirty if the copy in RAM has been m o d i f i e d so it does not match the copy on d i s k . JSR to G A F L G S ($DF9E) to get active b u f f e r n u m b e r and set in L B U S E D .
$DDCF
$DDD6 $DDD9
$DDE1
$DDE8
$DDEF $DDF0
SCRUB
$DDF1
330
NAME
ADDRESS
$DDF4 $DDF6 $DDF9
SCR1
$DDFC
SETLNK
$DDFD
$DE02 $DE05 $DE09
GETLNK
NULLNK
$DE2A
SET00
331
NAME
ADDRESS
CURBLK GETHDR
WRTAB
RDAB
WRTOUT RDIN WRTSS RDSS RDS5
SJ10
SJ2 0
RDLNK
$DE9 5
332
NAME
ADDRESS
$DE9A $DE9F $DEA4
BOTOBO
$DEB1
$DEB6
B02
$DEB9 $DEC0
CLRBUF
$DEC1 $DEC2
CB10
SSSET
SSDIR
333
NAME
ADDRESS
SETSSP
$DEEE
$DEF1
$DEF4
$DEF7
SSPOS
$DF01 $DF03 $DF06 $DF09 SSP10 $DFOB $DFOE SSP2 0 $DF12 $DF14 $DF17
IBRD
$DFlB
334
NAME
ADDRESS
$DFlD
IBWT
($F9)
IBOP
$DF2C
$DF4 0
Push the job code onto the stack. Load .A w i t h the file's drive n u m b e r from F I L T Y P , X ($EC,X), A N D it w i t h $01 to mask off the n o n - d r i v e b i t s , and use it to set the d r i v e , D R V N U M ($7F) Pull t.he job code off the stack, OR it wit.h the drive n u m b e r in D R V N U M ($7F) , and store the result in C M D ($024D). Move the track n u m b e r from (DIRBUF),Y ($94),Y to T R A C K ($80). Increment .Y Move the sector n u m b e r from (DIRBUF),Y ($94) ,Y to SECTOR ($81) . Load .A w i t h the b u f f e r n u m b e r from JOBNUM ($F9) and JSR t.o SETH ($D6D3) t.o set. up the h e a d e r . Load .X w i t h the b u f f e r n u m b e r from JOBNUM ($F9) and JMP to DOIT2 ($D593) to do the job. Get side sector p o i n t e r s : Load .X w i t h t.he a c t i v e b u f f e r n u m b e r from LINDX ($82) . Load .A w i t h t.he side sector b u f f e r number from SS,X ($CD,X) JMP t.o SETDIR ($D4EB) to set. the DIRBUF pointers. C a l c u l a t e side s e c t o r s : Load .A w i t h $78, the n u m b e r of side sector p o i n t e r s in a b u f f e r . JSR to A D D T 1 2 ($DF5C) t.o add the n u m b e r of side sectors needed * 120. Decrement. .X. If .X >= $00, b r a n c h to SCAL1. Load .A w i t h the n u m b e r of SS indices needed from T3 ($72) and m u l t i p l y it by 2 (ASL) since two b y t e s (track & sec) are needed for each index. JSR to A D D T 1 2 to add .A to T1 & T2. Load .A wit.h the n u m b e r of SS b l o c k s needed from T4 ($73) C l e a r t.he carry flag. Add the c o n t e n t s of T1 ($70) to c o n t e n t s of the a c c u m u l a t o r and the result, back in T1 ($70).
GSSPNT
SCAL1
$DF4C $DF4E
SSCALC
$DF51 $DF54
$DF5 7 $DF5A
ADDT12
$DF5C $DF5D
the store
335
NAME
ADDRESS
$DF61 $DF6 3 $DF6 5
ADDRTS
SSTEST
$DF6 6 $DF6 9
$DF6D $DF6F
$DF73
ST10
$DF7 7
ST20
$DF7B
Load .A w i t h the SS n u m b e r from SSNUM ($D5) and c o m p a r e it w i t h $06, the n u m b e r of side sector links. If the v a l u e in SSNUM > $06, b r a n c h to ST30. M u l t i p l y the SS n u m b e r in .A by 2 (ASL) and t r a n s f e r the result int.o .Y. Load .A w i t h $04, and store this v a l u e in DIRBUF ($94), lo b y t e of the p o i n t e r . Load .A w i t h the v a l u e from (DIRBUF),Y ($94),Y. If t.his v a l u e is not $00, b r a n c h to S T 4 0 . Way out of range so BIT w i t h E3 and exit w i t h an R T S . ($FED0)
ST3 0
$DF8B
ST40
$DF8F
Not in r e s i d e n c e and range is u n k n o w n so BIT w i t h El ($FECE) and exit. w i t h RTS Get a c t i v e b u f f e r n u m b e r : On e x i t : .A = a c t i v e b u f f e r n u m b e r .X = LINDX Flag N = 1 if no a c t i v e b u f f e r Load .X w i t h the c u r r e n t b u f f e r n u m b e r from LINDX ($82) . Load .A w i t h the b u f f e r n u m b e r from BUF0,X ($A7,X). If bit. 7 is not set, this b u f f e r is a c t i v e so b r a n c h to G A 1 .
GETACT
$DF9 3 $DF95
336
NAME
ADDRESS
$DF99
GA1
>DF9B $DF9D
GAFLGS GA2
$DFA7
$DFB6
GETINA
$DFB7 $DFB9
PUTINA
337
NAME
ADDRESS
$DFCA $DFCC
PI1
$DFCD $DFCF
NXTREC
$DFD0
$DFD5
$DFDC $DFDE
$DFF3
NXTR20
$E006
Set up next r e l a t i v e record: Load .A w i t h $20 (overflow flag) and JSR t.o C L R F L G ($DD9D) to clear t.he record o v e r f l o w flag. Load .A wit.h $80 (last record flag) and JSR to TSTFLG ($DDA6) to test. if we are out beyond the last r e c o r d . If not., b r a n c h to N X T R 4 0 . Load .X wit.h the c u r r e n t channel n u m b e r from LINDX ($82) . Increment, the lo byte of the record c o u n t e r in RECL,X ($B5,X). If the result is not $00, b r a n c h to N X T R 1 5 . Increment, the hi byt.e of t.he record c o u n t e r in RECH,X ($BB,X). Load .X w i t h t.he current channel n u m b e r from LINDX ($82) . Load .A wit.h t.he p o i n t e r to the next record from NR,X "($Cl,X). If t.he next. record p o i n t e r is $00, there is no next. record so b r a n c h to N X T R 4 5 . JSR t.o G E T P N T ($D4E8) to get the b u f f e r pointer. Load .X w i t h the c u r r e n t channel n u m b e r from LINDX ($82) . C o m p a r e the b u f f e r p o i n t e r in .A w i t h the p o i n t e r in NR,X ($Cl,X). If BT<NR then b r a n c h t.o N X T R 2 0 . Not. in this b u f f e r , must, be in the nextone so JSR to NRBUF ($E03C) t.o set up t.he next. one. Load .X w i t h the c u r r e n t channel n u m b e r from LINDX ($82) . Load .A wit.h t.he p o i n t e r t.o t.he next record from NR,X ($Cl,X). JSR to SETPNT ($D4C8) to a d v a n c e to the next. r e c o r d . Load .A w i t h t.he first byte of the record from (BUFTAB,X) ($99,X). Save t.he first dat.a byte into DATA ($85) Load .A wit.h $20 (overflow flag) and JSR to CLRFLG ($DD9D) to clear the record o v e r f l o w flag. JSR t.o A D D N R ($E304) t.o a d v a n c e the NR pointer.
338
NAME
NXOUT
ADDRESS
$E009
$E00C
$E013
NXTR45 NXTR4 0
$E018
$E01D
$E020 $E025 $E029
NXTR5 0
NXTR30 NXTR35
$E034 $E035
$E039
Load .X w i t h the channel n u m b e r from LINDX ($82). Store the byte in .A int.o NR,X ($Cl,X). T e r m i n a t e routine w i t h a JMP to SETLST ($E16E) to set the p o i n t e r to the last character. Set up next record in b u f f e r : JSR to S E T D R N ($DlD3) to set drive n u m b e r to a g r e e w i t h the last job. JSR to RDLNK ($DE95) t.o set TRACK and SECTOR from the track & sector link. JSR to G A F L G S ($DF9E) to test, if the current b u f f e r is d i r t y (changed). If V flag clear, it is clean? b r a n c h to N R B U 5 0 so we don't, w r i t e it. out. JSR to W R T O U T ($DE5E) t.o w r i t e it. out. JSR to DBLBUF ($CFlE) t.o toggle the active and inactive b u f f e r s . Load .A wit.h $02 and JSR to SETPNT ($D4C8) to set the p o i n t e r to point to the first data byte in t.he new sector. JSR to T S T W R T ($DDAB) to test if the last job was a w r i t e . If it was not a w r i t e job, b r a n c h to N R B U 2 0 ($E07B) since buffer is OK.
NRBUF
$E052
339
NAME
ADDRESS
$E057 $E0 5A
NRBU5 0
$E05D $E0 60
RELPUT
$E08D $E08F
RELP0 6 RELP0 5
340
NAME
RELP0 7
ADDRESS
$E09E
RELP10
$E0A3
$E0A7 RELP2 0
$EOAA
$EOAB
WRTREL
WR2 0
$E0B7
$EOCl $E0C3
WR4 0
$E0C8
$EOCB
$EOCE $E0D3 WR4 5 WR50 $E0D6 $E0D9
$EODD
WR51 WR60
$EOEl
$E0E2
A N D t.he error flag in .A w i t h $80 (t.he last, record flag) . If the result is not $00, the last. record flag was set so b r a n c h t.o W R 6 0 to add t.o file. Load .A w i t h the EOIFLG ($F8). If t.his is $00, an EOI was not. sent, so b r a n c h t.o WR3 0. T e r m i n a t e routine w i t h an RTS. Load .A w i t h t.he data byt.e from DATA ($85) and push it. onto the stack.
341
NAME
ADDRESS
$E0E5 $E0E9 $EOEB $E0F0
CLREC
CLR10
SDIRTY
$E104
CDIRTY
RDREL
$E120 $E125
RD10
$E127 $E12A
342
NAME
ADDRESS
$E12C
$E131
$E145
$E152 RD40 $E153 $E156 $E159 $E15B RD05 $E15E $E160 $E165
$E169
SETLST
$E16E $E170
343
NAME
ADDRESS
$E174
$E18D $E190
SETL05
$E19D $E1A0
SETL10
$ElA4
FNDLST
$ElB2 $ElB5
FNDL10
$ElB7
$ElBB
344
NAME
ADDRESS
$E1C0
FNDI3 0
$ElC4
FNDL20
$ElC8 $ElC9
SSEND
SE10 SE20
$ElD8 $ElDC
$E1E0 $ElEl
SE3 0
$ElEF $ElF3
$ElF7 $ElF8
345
NAME
ADDRESS
$ElFF
BREAK
$E202
Load .A wit.h $67 to indicate a SYSTEM TRACK OR SECTOR error and JSR t.o C M D E R R 2 ($E645).
RECORD COMMAND
RECORD
$E207 $E20A
Not.e: set to last, record if out. of range JSR t.o C M D S E T ($C2B3) t.o i n i t i a l i z e t.he p o i n t e r s and t a b l e s . Load .A wit.h the second c h a r a c t e r in the c o m m a n d from C M D B U F + 1 ($0201) and use it. t.o set the s e c o n d a r y a d d r e s s in SA ($83) JSR t.o F N D R C H ($D0EB) t.o find an unused read c h a n n e l . If carry flag c l e a r , channel found so b r a n c h to R20. Load .A wit.h $70 t.o i n d i c a t e a N O C H A N N E L error and JSR to C M D E R R ($ClC8). Load .A w i t h $A0 (last, record flag p l u s o v e r f l o w flag) and JSR t.o CLRFLG ($DD9D) t.o clear t.hese flags. JSR to T Y P F I L ($D125) to d e t e r m i n e the file type. If the Z flag is set., it. is a r e l a t i v e file so b r a n c h to R30. Load .A wit.h $64 to i n d i c a t e a FILE TYPE M I S M A T C H error and JSR to C M D E R R ($ClC8) Load .A wit.h t.he file type from F I L T Y P , X ($EC,X) , A N D t.he type wit.h $01 t.o mask off the n o n - d r i v e bit.s, and store the result as the d r i v e # in D R V N U M ($7F). Load .A w i t h the third c h a r a c t e r in the c o m m a n d from C M D B U F + 2 ($0202) and use it t.o set. t.he lo byt.e of t.he record n u m b e r in R E C L , X ($B5,X). Load .A w i t h the fourth c h a r a c t e r in the c o m m a n d from C M D B U F + 3 ($0203) and use it. to set the hi byte of t.he record n u m b e r in RECH,X ($BB,X). Load .X wit.h t.he channel n u m b e r from LINDX ($82) . Store $89 (random a c c e s s - ready) as the channel status in C H N R D Y , X ($F2,X). Load .A wit.h t.he fifth c h a r a c t e r in t.he c o m m a n d from C M D B U F + 4 ($0204). This is the byt.e point.er int.o t.he r e c o r d . If the byt.e point.er is $00, b r a n c h t.o R40. Set. the carry flag and subtract $01 from t.he byt.e p o i n t e r . If the result is $00, b r a n c h to R40.
R20
$E219
$E21E
$E2 23
R30
$E2 28
$E22E
$E233
$E243
346
NAME
ADDRESS
$E2 48
$E2 5D $E262
R50
$E265 $E268
$E26F
R60
$E2 72
POSITN
$E275 $E2 78
$E286 P2 $E289
P o s i t i o n to r e c o r d : Moves r e l a t i v e record int.o active b u f f e r and the next b l o c k into inactive b u f f e r . JSR t.o POSBUF ($E29C) to p o s i t i o n dat.a b l o c k s into b u f f e r s . Load .A wit.h the p o i n t e r from RELPNT ($D7) and JSR t.o SETPNT ($D4C8) to set up the b u f f e r p o i n t e r s . Load .X w i t h the channel n u m b e r from LINDX ($82). Load .A w i t h the record size from RS,X (C7,X) and set the carry flag. Subtract the p o i n t e r in R E C P N T ($D4) from the record size in .A t.o find the o f f s e t . If offset > $00, b r a n c h to P2. T r o u b l e ! JMP to BREAK ($E202). Clear the carry flag and add the p o i n t e r in R E L P N T ($D7). If there is no carry, b r a n c h to P30. Add a n o t h e r $01 and set the carry flag. JSR to N X O U T ($E009) to set up the next. record.
P3 0
$E28E $E291
347
NAME
ADDRESS
$E294
$E297
- * - * UNUSED CODE - * - * Load .A w i t h $51 (record overflow) and JSR to C M D E R R ($ClC8). P o s i t i o n proper data b l o c k s into b u f f e r s Save the lo byte of the DIRBUF ($94/5) p o i n t e r into R3 ($89). Save t.he hi byte of the DIRBUF ($94/5) p o i n t e r into R4 ($8A). JSR t.o B H E R E ($E2D0) to check if d e s i r e d block is in the b u f f e r . If not, b r a n c h to P10 t.o read it in. T e r m i n a t e r o u t i n e w i t h an RTS. JSR to SCRUB ($DDF1) to clean the buffer JSR to G E T L N K ($DEOC) t.o set TRACK and SECTOR from t.he link. If TRACK ($80) is $00, there is no next. track so b r a n c h to P80. JSR to B H E R E ($E2D0) to check if d e s i r e d b l o c k is in the b u f f e r . If not, b r a n c h to P75 t.o read it. in. JSR to DBLBUF ($CFlE) to toggle t.he a c t i v e and inactive b u f f e r s . JMP t.o F R E I A C ($D2DA) to free the inactive b u f f e r . JSR t.o FREIAC ($D2DA) to free the inactive b u f f e r . Load .Y w i t h $00. Move t.he desired track from (R3),Y ($89),Y int.o TRACK ($80). Increment .Y Move the d e s i r e d sector from (R3),Y ($89),Y into S E C T O R ($81). JMP to S T R D B L ($D0AF) to read in the d e s i r e d b l o c k and the next, one t.oo. Check if d e s i r e d block is in b u f f e r : JSR to G E T H D R ($DE3E) to set. TRACK and SECTOR from t.he h e a d e r . Load .Y wit.h $00 C o m p a r e the desired track from (R3),Y ($89) ,Y wit.h the v a l u e in TRACK ($80) . If they are e q u a l , b r a n c h to BH10 t.o c o m p a r e the s e c t o r s . No match (Z=0) so exit w i t h an RTS Increment. .Y. C o m p a r e the d e s i r e d sector from (R3),Y ($89) ,Y wit.h the v a l u e in SECTOR ($81) . This sets Z=1 if they are e q u a l . T e r m i n a t e r o u t i n e wit.h an RTS.
POSBUF
$E2B9 $E2BC
P75 P80
BHERE BHERE2
$E2E1
348
NAME
ADDRESS
NULBUF
NB2 0
ADDNR
AN0 5
$E318 $E31B
AN10
ADDREL
349
NAME
ADDRESS
$E330 $E334 $E338
ADDR1
$E344
$E347 $E349
$E34F
AR10
$E355
$E35D
AR2 0
$E363
AR25
AR30
$E372 $E374
$E37A
350
NAME
ADDRESS
$E380
$E384
$E388
AR35
$E38D $E38F
$E396
AR40
$E3B3 AR45 $E3B6 $E3B9 $E3BC $E3BF $E3C2 $E3C5 AR50 $E3C8
351
NAME
ADDRESS
$E3CB $E3CE $E3D1
AR55
$E3EF $E3F2
$E3FD
JSR to W R T O U T ($DE5E) to w r i t e the current, block to d i s k . JSR t.o G E T L N K ($DEOC) t.o set. TRACK and SECTOR from the track & sector link. Save t.he v a l u e of TRACK ($80) and SECTOR ($81) onto the stack. JSR to G E T H D R ($DE3E) to set TRACK and SECTOR from the last, sect.or read. Save the value of T R A C K ($80) and S E C T O R ($81) ont.o t.he stack. JSR to G S S P N T ($DF45) to c a l c u l a t e the side sector p o i n t e r (returned in .A) T r a n s f e r the point.er in .A to .X. If the p o i n t e r v a l u e is not. $00, we don't, need a n o t h e r side sector so b r a n c h to A R 6 0 . JSR to N E W S S ($E44E) t.o get a n o t h e r side sector. Load .A w i t h $10, side sector o f f s e t , and JSR t.o SETSSP ($DEE9) to set. t.he side sector p o i n t e r . Increment t.he side sect.or count, in R0 ($86) by 1. Pull t.his s e c t o r ' s track off t.he stack and JSR t.o P U T S S ($DD8D) to w r i t e it. into the side sector b u f f e r . Pull t.his s e c t o r ' s sector off t.he stack and JSR to P U T S S ($DD8D) t.o writ.e it. int.o the side sector b u f f e r . Pull this s e c t o r ' s sector link off t.he stack and store it in SECTOR ($81) . Pull this s e c t o r ' s track link off t.he stack and store it. in T R A C K ($80) . If track link is $00, there are no more b l o c k s in this file so b r a n c h to AR65 C o m p a r e the side sector c o u n t e r in R0 ($86) wit.h t.he end count, in SSNUM ($D5). If t.hey are not e q u a l , we haven't done e n o u g h new b l o c k s yet. so b r a n c h to A R 4 5 . Almost, done so JSR to G S S P N T ($DF45) to get. the side sector p o i n t e r . C o m p a r e the point.er in .A wit.h the end p o i n t e r in S S I N D ( $ D 6 ) . If S S I N D > . A , we are a l m o s t d o n e so b r a n c h to A R 4 5 . If S S I N D = . A there is one more block left. so b r a n c h t.o A R 5 0 .
$E409
$E40F $E412
352
NAME
AR65
ADDRESS
$E418
$E41C
$E421
$E427
$E441
AR70
$E444
NEWSS
$E457
$E4 5B $E45E
353
NAME
ADDRESS
$E460
$E46F
$E4 74
$E479
$E47F
$E485 $E48A
$E490 $E491
$E497 $E49C
$E4A1
$E4A6 $E4A9
354
NAME
NS20
ADDRESS
$E4AC $E4AE
$E4B8
$E4BE
$E4C3
$E4CA
($81) .
$E4CE NS40 $E4D1 $E4D4 $E4D6 $E4DB JMP to NS5 0 ($E4DE). JSR to G E T A C T ($DF93) t.o get. the a c t i v e b u f f e r n u m b e r (returned in .A). Load .X wit.h t.he channel n u m b e r from LINDX ($82). JSR to IBRD ($DFlB) to read t.he next SS. b u f f e r n u m b e r (returned in .A). Zero .A and JSR to SETPNT ($D4C8) to set the b u f f e r p o i n t e r to t.he start of the buffer. Decrement, the p o i n t e r in R4 ($8A) t w i c e . Load .Y w i t h the p o i n t e r into the b u f f e r from R3 ($89) . Load .A w i t h the new SS track p o i n t e r from R1 ($87) and store this v a l u e int.o the data b u f f e r at (DIRBUF),Y. Increment. .Y. Load .A wit.h the new SS sector p o i n t e r from R2 ($88) and store t.his value int.o the data b u f f e r at (DIRBUF),Y. JSR to W R T O U T ($DE5E) to w r i t e out the revised side sector b l o c k . JSR t.o W A T J O B ($D599) to wait. for the w r i t e job t.o be c o m p l e t e d .
NS5 0
$E4E8 $E4E9
$E4ED $E4F0
355
NAME
ADDRESS
$E4F3
$E4F9
E R R O R M E S S A G E TABLE
Each entry c o n s i s t s of the a p p l i c a b l e error n u m b e r s followed by the m e s s a g e test w i t h t.he first and last c h a r a c t e r s OR'ed w i t h $80. The key w o r d s in t.he text are t o k e n i z e d (values $80 - $8F) . The tokenized word list. f o l l o w s t.he main error m e s s a g e table. Address $E4FC $E500 $E50B $E517 $E522 $E52F $E533 $E540 $E546 $E552 $E556 $E55F $E567 $E570 $E589 $E58D $E592 $E59F $E5AA $E5AF $E5B6 $E5C8 Error numbers Error Message
$00 $20 , $21,$2 2,$23,$2 4,$2 7 $52 $50 $51 ,$28 $25 ; $26 $29 |,$31,$32,$33,$34 $30 ( $60i $63 $64 $65i $6 61 , f $ 6 7
$61
$39 $01 $70 $71 $72 $73 $74 TABLE OF T O K E N I Z E D W O R D S
OK R E A D ERROR FILE TOO L A R G E R E C O R D N O T PRESENT O V E R F L O W IN R E C O R D WRITE ERROR W R I T E P R O T E C T ON DISK ID M I S M A T C H SYNTAX E R R O R W R I T E F I L E OPEN FILE E X I S T S FILE TYPE M I S M A T C H N O BLOCK ILLEGAL TRACK OR SECTOR FILE N O T OPEN FILE NOT FOUND FILES S C R A T C H E D NO CHANNEL DIR E R R O R DISK FULL CBM DOS V2.6 4030 D R I V E N O T READY $E5D5 $06 $07 $08 $0B - $E609 NOT FOUND DISK RECORD
356
NAME
ADDRESS
ERROR
$E610
$E618
$E619
$E61D $E621
$E635 $E6 3A
$E63E $E641
357
NAME
ADDRESS
$E65D
$E663
$E667 $E668
$E66C
$E6 70 $E672
$E679
TLKERR
$E680 $E683
LSNERR
$E688 $E68B
TLERR ERR10
$E68E $E698
Terminate ($EBE7).
HEXDEC HEX0
HEX5
$E6AA
Convert hex to BCD: On e n t r y : .A c o n t a i n s hex n u m b e r On e x i t : .A c o n t a i n s BCD n u m b e r T r a n s f e r hex from .A to .X. Zero .A and set decimal mode (SED). C o m p a r e .X v a l u e to $00. If e q u a l , b r a n c h t.o HEX5 to exit. C l e a r carry flag, add 1 to v a l u e in d e c r e m e n t .X, and JMP b a c k to H E X 0 . C l e a r decimal mode (CLD).
.A,
358
NAME
ADDRESS
BCDDEC
BCD2
$E6BA $E6BB
OKERR
ERRTS0 ERRMSG
$E6DF
359
NAME
ADDRESS
$E6F7
$E6FF
$E701
$E705
ERMOVE
$E706 $E707
E10
$E72F
E40
$E735
360
NAME
E45
ADDRESS
$E73 9
E50
$E73D
E60
$E754
$E758 $E759 $E75D $E75E $E75F $E7 62 E70 $E763 $E765 $E766
EADV1
$E767
$E76B
EA10
$E76D $E76F
$E770
361
NAME
ADDRESS
$E772 $E774
EADV2
$E7 75 $E7 78
This u t i l i t y is used to load and e x e c u t e user p r o g r a m s or system u t i l i t i e s from d i s k . This u t i l i t y may be used in two w a y s : a) On p o w e r - u p : If the dat.a and clock lines are g r o u n d e d at power up, the r o u t i n e is e n t e r e d . It w a i t s until t.he ground clip is removed and then loads the first file found in t.he d i r e c t o r y into disk R A M using the first two b y t e s of the file as the load a d d r e s s . Once the file is loaded, it. is e x e c u t e d s t a r t i n g at the first b y t e . b) N o r m a l e n t r y : The disk c o m m a n d " & : f i l e n a m e " will load and e x e c u t e the file w h o s e f i l e n a m e is s p e c i f i e d . For e x a m p l e : PRINT# 15 , 11 &0 : DISK TASK" File s t r u c t u r e : The u t i l i t y or p r o g r a m must be of the f o l l o w i n g form. File type: USR Bytes 1/2: Load a d d r e s s in disk RAM (lo/hi). Byte 3: Lo byte of the length of the r o u t i n e Bytes 4/N: Disk r o u t i n e m a c h i n e c o d e . Byte N + 1: C h e c k s u m . N o t e that the c h e c k s u m includes all b y t e s including the load a d d r e s s , f o r m u l a : C H E C K S U M = C H E C K S U M + BYTE + C A R R Y N O T E : R o u t i n e s may be there MUST be a n u m b e r of bytes each subsequent BOOT2 BOOT | $E77F $E780 $E784 | Exit longer than 256 b y t e s . H o w e v e r , valid c h e c k s u m byte after the specified in byte #3 and after 256 b y t e s ! r o u t i n e w i t h an RTS.
Load .A wit.h input port data from PB ($1800). T r a n s f e r data from .A to .X. A N D t.he data b y t e (in .A) w i t h $04 to see if clock is g r o u n d e d . If not, b r a n c h to BOOT2 to e x i t .
362
NAME
ADDRESS
$E788 $E7 89
$E78D
BOOT3
$E78F $E791
PB
($1800) .
A N D the data byte (in .A) w i t h $05 to see if clip has been r e m o v e d . If not, b r a n c h to BOOT3 t.o wait until it is. Set. the n u m b e r of files to $01 by i n c r e m e n t i n g F2CNT ($0278) . Set the c o m m a n d string length to $01 by i n c r e m e n t i n g C M D S I Z ($0274). Set t.he first c h a r a c t e r in the c o m m a n d b u f f e r , C M D B U F ($0200), to $2A ("*") to match any file name. JMP to BOOT4 ($E7A8) t.o c o n t i n u e . N O R M A L E N T R Y POINT Load . A w i t h $8D and JSR t.o PARSE ($C268) to p a r s e the c o m m a n d string. JSR to KILLP ($F258) t.o kill p r o t e c t . D o e s n o t h i n g on the 1541! Load .A w i t h t.he file count from F2CNT ($0278) and save it. on the stack. Set file count in F2CNT ($0278) t.o $01. Set. f i r s t - b y t e flag in R0 ($86) to $FF. JSR to L O O K U P ($C44F) to locate the file name on the d i s k . C h e c k the track link for the file found in FILTRK ($0280). If it is $00, the file w a s not found so b r a n c h to U T L D 0 0 . Load .A wit.h $39 t.o indicate a FILE N O T F O U N D e r r o r and JSR to C M D E R R ($ClC8) to exit. Pull o r i g i n a l file count off the stack and r e s t o r e it. int.o F2CNT ($0278) . Set TRACK ($80) from t.he track link for the file from FILTRK ($0280). Set SECTOR ($81) from the sector link for t.he file from FILSEC ($0285). Load .A w i t h $03 (USER FILE TYPE) and JSR to O P N T Y P ($D477) to open the file. Load .A w i t h $00 and st.ore it in Rl($87) to i n i t i a l i z e the c h e c k s u m . JSR to G T A B Y T ($E839) to get the first byt.e from the file (lo of load a d d r e s s ) . Store the lo byt.e of t.he load a d d r e s s in R2 ($88) .
$E7A0
UTLODR BOOT4
$E7C0
UTLD00
UTLD10
363
NAME
ADDRESS
$E7E1 $E7E4 $E7E7 $E7E9 $E7EC
UTLD30
$E81B $E81E
364
NAME
ADDRESS $E83 0
DESCRIPTION
OF W H A T ROM R O U T I N E
DOES
R o u t i n e all loaded so pull load a d d r e s s off the stack (lo/hi), set. up a jump v e c t o r in R2/3 ($88/9), and do an indirect JMP to the r o u t i n e via (R2). Subroutines for UTLODR
GTABYT
$E839 $E83C
$E840
$E843
Get a byt.e from the file opened using the internal read c h a n n e l . There is an e n d - o f - f i l e check d o n e . If EOI o c c u r s , a #51 DOS e r r o r is r e p o r t e d . JSR to G I B Y T E ($CA35) to fetch a b y t e and store it in DATA ($85) . Test the end of i n f o r m a t i o n flag, E O I F L G ($F8). If N O T $00, we have not. come to the end so b r a n c h to G T A B Y E . W e have an EOI c o n d i t i o n . JSR t.o G E T H D R ($DE3E) to set. TRACK and SECTOR from t.he h e a d e r . Load .A w i t h $51 to i n d i c a t e a R E C O R D SIZE e r r o r and JSR to C M D E R 2 ($E645). Load .A w i t h t.he byte from DATA T e r m i n a t e r o u t i n e wit.h an RTS. ($85)
GTABYE
$E848 $E84A
ADDSUM
$E84B $E84C
$E850 $E852
C o m p u t e the running c h e c k s u m in R1: On e n t r y : .A = new byte to add Clear the carry flag. Add the b y t e in R1 ($8 7) to the byt.e in .A and t.hen add $00 to the result to add in the carry b i t . Store the new c h e c k s u m into R1. T e r m i n a t e r o u t i n e w i t h an RTS. ROUTINES
S E R I A L BUS C O M M U N I C A T I O N
E N T R Y POINT FOR IRQ R O U T I N E TO S E R V I C E A T T E N T I O N (ATN) S I G N A L S FROM THE C - 6 4 . ATNIRQ $E853 $E856 $E85A Load .A w i t h the c o n t e n t s of PA1 ($1801) to clear the interrupt (IRQ) flag (CA1). Store $01 in A T N P N D ($7C) to indicate that. an ATN request is p e n d i n g . T e r m i n a t e r o u t i n e w i t h an RTS. S e r v i c e t.he a t t e n t i o n request from t.he C-64. Set. the interrupt flag (SEI) to prevent any i n t e r r u p t s . Store $00 in A T N P N D ($7C) t.o indicate t.hat. no ATN request is p e n d i n g . Zero the listener and talker active flags L S N A C T ($79) and T L K A C T ($7A).
ATNSRV
NAME
ADDRESS
$E864 $E867 $E86B $E86D $E870 $E8 73
ATNS15
$E87B
$E880
$E8 84 $E887
$E88B
ATN35
$E891
$E895
ATN40
$E89B
366
NAME
ADDRESS
$E89F
ATN4 5
$E8A9
$E8AD
ATN50
$E8B7 $E8B8
$E8C5
$E8CD $E8CE
Set the interrupt flag (SEI) to prevent any i n t e r r u p t s . Test if the ATN signal is still p r e s e n t . If it. is, b r a n c h back t.o A T N 3 0 .
367
NAME
ADDRESS
ATSN2 0
$E8D7 $E8DB
$E8E3
ATN110
ATN120 ATN12 2
$E8FD $E902
TALK
$E909 $E90A
TALK1
$E90F $E911
Set the interrupt flag (SEI) to preventany i n t e r r u p t s . JSR to F N D R C H ($D0EB) to find an unused read c h a n n e l . If no channel is a v a i l a b l e b r a n c h to NOTLK to e x i t . Load .X wit.h the c u r r e n t channel n u m b e r from LINDX ($82) . Load .A wit.h the channel status from C H N R D Y , X ($F2,X). If bit. 7 is set, t.he status is OK so b r a n c h t.o T L K 0 5 . Terminate routine wit.h an RTS.
NOTLK
$E905
TLK0 5
$E906
N O T E : CODE A D D E D TO FIX V E R I F Y ERROR JSR to TSTATN ($EA59) to test for an ATN signal.
368
NAME
ADDRESS
$E909
TALK2
$E92F $E931
TLK0 2
TLK0 3
NOEOI
369
NAME
ADDRESS
$E958
ISR01
$E95C $E95F
ISR02
$E963 $E965
$E96C
ISR04
$E987 $E98A
$E991
JSR to CLKHI ($E9B7) to set. the clock line hi. (rising e d g e ) . Load .A w i t h the speed flag from D R V T R K + 1 ($23). If the flag is not $00, no slow down is r e q u i r e d so b r a n c h to ISR03. JSR t.o S L O W D ($FEF3) to slow down the data t r a n s m i s s i o n . JSR to C L K D A T ($FEFB) to pull the clock low and r e l e a s e the d a t a . D e c r e m e n t the bit count in CONT ($98) . If the count is not $00, there are more b i t s to send from this byte so b r a n c h b a c k to ISRG1. JSR t.o DEBNC ($E9C0) to test if the data line has been set. AND t.he test b y t e (in .A) w i t h $01. If the result e q u a l s $00, the line has not been set. lo (no r e s p o n s e ) so b r a n c h back to ISR04 to w a i t for lo r e s p o n s e . Clear the interrupt flag (CLI) to a l l o w i n t e r r u p t s in p r e p a r a t i o n for sending the next. b y t e . JSR t.o GET ($D3AA) to get. the nextdata byt.e to send. Set. the interrupt flag (SEI) to preventany i n t e r r u p t s . JMP to TALK1 to keep on t a l k i n g .
370
NAME
ADDRESS
DESCRIPTION TALK
OF W H A T
ROM R O U T I N E
DOES
SUBROUTINES: all
FRMERX
$E999
DATHI
$E99C
$E9A4
Set data out line h i g h . Load .A w i t h t.he byte from port. B, PB ($1800), A N D it w i t h $FD ($FF-DATOUT), and store the result back in PB ($1800). T e r m i n a t e routine wit.h an RTS. Set dat.a out line lo. Load .A w i t h the byte from port. B, PB ($1800), OR it w i t h $02 (DATOUT), and store t.he result back in PB ($1800) . T e r m i n a t e routine w i t h an RTS. Set clock line lo. Load . A wit.h the byte from port. B, PB ($1800), OR it wit.h $08 (CLKOUT), and store the result back in PB ($1800). T e r m i n a t e routine wit.h an RTS. Set c l o c k line hi. Load . A wit.h the byt.e from port. B, PB ($1800) , A N D it wit.h $F7 ($FF-CLKOUT) , and store the result back in PB ($1800). T e r m i n a t e routine w i t h an RTS. W a i t for response on b u s . Load . A wit.h the byte from port. B, PB ($1800). C o m p a r e the old port value (.A) wit.h t.he current v a l u e of PB ($1800) . If there is no c h a n g e , branch t.o DEBNC. T e r m i n a t e routine w i t h an RTS. SERIAL BUS L I S T E N ROUTINES
DATLOW
$E9A5
$E9AD
CLKLOW
$E9AE
$E9B6
CLKHI
$E9B7
$E9BF
DEBNC
$E9C0
$E9C8
ACPTR ACP00A
Store $08 in CONT ($98) to set. up the bit c o u n t e r . JSR to TSTATN ($EA59) to t.est for an ATN signal. JSR t.o DEBNC ($E9C0) t.o test if the clock line has been set. AND the test byte (in .A) with $04. If the result is not $00, the line has not been set. hi (no response) so b r a n c h back to ACP00A to wait for hi r e s p o n s e . JSR to DATHI ($E99C) to make data line high. Store $01 in T1HC1 ($1805) to set up for a 255 microsecond d e l a y . JSR to TSTATN ($EA59) to t.est for an ATN signal.
371
NAME
ADDRESS
$E9E2
$E9E9 $E9EC
ACP00B
$E9F2 $E9F5
$EAOB
$EA15
$EA18
ACP0 3A
$EAlA $EAlD
372
NAME
ADDRESS
$EA2 0
$EA2 4
LISTEN
$EA2E $EA2F
$EA34 $EA36
LSN15
$EA3 9
Set interrupt mask [SEI) t.o prevent any interrupts. JSR to F N D W C H ($D107) to find an unused If none a v a i l a b l e , b r a n c h w r i t e channel to L S N 1 5 . Load .A w i t h the w r i t e channel status from C H N R D Y , X ($F2,X). Rotate the status byte right (ROR). If the carry bit. is set, the w r i t e channel is inactive so b r a n c h to L S N 3 0 . Test if this is an OPEN c o m m a n d by: loading .A w i t h the original secondary a d d r e s s from ORGSA ($84) and A N D ' i n g it w i t h $F0. If the result is $F0, it is an OPEN c o m m a n d so b r a n c h t.o L S N 3 0 . Not an a c t i v e channel so JMP t.o ILERR ($EA4E) to a b o r t . JSR to A C P T R ($E9C9) to get a data b y t e . Clear interrupt mask (CLI) to allow interrupts. JSR to PUT ($CFB7) to put the data byt.e int.o its proper place (DATA, EOI, SA). JMP to L I S T E N ($EA2E) t.o keep on listening. R e l e a s e all bus lines and go idle: Store $00 into port. B, PB ($1800) and JMP to IDLE ($EBE7). LISTEN SUBROUTINES to service ATN
$EA4E
ATNLOW
$EA5 6
JMP to A T N S R V request.
($E85B)
TSTATN
$EA59
Test if in ATN mode: Load .A w i t h the a t t e n t i o n mod.e flag from A T N M O D ($7D). If $00, we are not in a t t e n t i o n mode so b r a n c h to TSTA50,
373
NAME
ADDRESS
$EA5D
TSTRTN
$EA62
TSTA5 0
$EA63
TATN2 0
$EA6B
FLASH LED TO S I G N A L N o - e r r o r status: Load .X wit.h $00. .BYTE $2C skips next
ERROR
PEZRO
t.wo b y t e s .
PERR
PE2 0 PE30
$EA74 $EA75
Error status: Load .X w i t h the error n u m b e r from TEMP ($6F). T r a n s f e r t.he error n u m b e r from .X into the stack p o i n t e r to use the stack as a storage r e g i s t e r . T r a n s f e r t.he v a l u e of t.he stack pointer (the error n u m b e r ) int.o .X Load .A wit.h $08 (the LED mask) , OR it wit.h t.he dat.a port. c o n t r o l l i n g the L E D ' s L E D P R T ($1C00) . and JMP t.o PEA7A ($FEEA) to turn on L E D . NOTE: this is a patch to be sure t.he dat.a d i r e c t i o n register for the LED line is set. to o u t p u t . T r a n s f e r the byt.e in .Y to .A Clear t.he carry flag. Add $01 t.o the c o n t e n t s of .A. If t.he result, is not. $00, branch t.o PD20. Decrement .Y (t.he hi byte of t.he timer) . If value of .Y is not $00, b r a n c h to PD10. Turn off L E D ( s ) . Load .A w i t h t.he byt.e from t.he dat.a port c o n t r o l l i n g the LED, L E D P R T ($1C00). A N D the byt.e wit.h $F7 ($FF - LED mask) and store t.he result back int.o L E D P R T ( $ 1 C 0 0 ) to t.urn OFF the L E D .
$EA86
374
DESCRIPTION
OF W H A T
ROM R O U T I N E
DOES
$EA97
$EA9A
T r a n s f e r the byt.e in .Y to .A Clear the carry flag. Add $01 t.o the c o n t e n t s of .A. If the result is not $00, b r a n c h to PD21. D e c r e m e n t .Y (t.he hi byt.e of the timer) . If v a l u e of .Y is not. $00, b r a n c h to PD11. D e c r e m e n t t.he count in .X. If t.he result is g r e a t e r than or equal to $00, b r a n c h to PE30 to flash a g a i n . C o m p a r e .X t.o $FC t.o see if we have w a i t e d long enough b e t w e e n g r o u p s of f l a s h e s . If .X <> $FC b r a n c h to PE40 t.o wait some more. If .X = $FC, b r a n c h to PE20 to repeat the s e q u e n c e . INITIALIZATION OF DISK
DSKINT
Set. the interrupt flag (SEI) t.o prevent interrupts. Clear t.he decimal mode flag (CLD) . Store $FF into the data d i r e c t i o n r e g i s t e r D D R A 1 ($1803). Load .X and .Y w i t h $00. Fill zero page wit.h a s c e n d i n g pat.t.ern T r a n s f e r t.he byt.e from .X into .A. Store the byte from .A into $00,X. Increment .X. If .X is not $00, b r a n c h b a c k to PV10. C h e c k zero page bits. T r a n s f e r the byte from .X into .A. C o m p a r e the byte in .A w i t h $00,X. If no m a t c h , b r a n c h to PEZRO ($EA6E). Increment t.he c o n t e n t s of $00,X by 1. Increment .Y. If .Y is not $00, branch back t.o PV3 0. C h e c k if $00,X e q u a l s byte in .A. If no match, something is w r o n g so b r a n c h t.o PEZRO ($EA6E). Store t.he $00 byt.e from .Y into $00,X. Check if $00,X equals $00. If it. does not, something is wrong so b r a n c h to PEZRO ($EA6E). Increment t.he counter in .X. If the result is not $00, we have more of zero page to check so branch back to PV20. Test. the two 64K bit ROM's.
PV10
PV2 0
PV3 0
$EAC0 $EAC2
$EAC6
RM10
$EAC9
375
NAME
ADDRESS
$EACB $EACF $EAD1 $EAD4 $EAD5 $EAD7
RT10
RT2 0
$EADC
$EAE6
CR20 CR30
RAMTST RA10
$EAF0
$EAF2 $EAF4
$EAF8 $EAFB
RA30 RA4 0
$EBOD
Load .A wit.h $01 (start of first block) . Save c o n t e n t s of .A (page number) into IP+1 ($76) as hi byte of p o i n t e r . Increment. TEMP ($6F) to burnp t.he error n u m b e r ($03=RAM problem) Load .X w i t h $07 (number of RAM p a g e s ) . T r a n s f e r .Y value to .A and clear c a r r y . Add t.he hi byt.e of the p o i n t e r , IP + 1 ($76) to t.he a c c u m u l a t o r and store the result in (IP,Y). Increment .Y and if .Y is not. $00, b r a n c h to RA10 t.o fill RAM page. Increment the hi byt.e of the p o i n t e r in IP+1 ($76) and decrement- the page countin .X. If .X is not $00, we have more pages t.o do so b r a n c h back to R A 1 0 . Load .X wit.h $07 (number of RAM pages) . Decrement, the hi byte of the point.er in IP+1 ($76). W e ' l l check b a c k w a r d s . Decrement .Y, transfer t.he .Y value int.o .A and clear the c a r r y . Add the hi byt.e of the p o i n t e r , IP + 1 ($76) to the a c c u m u l a t o r and c o m p a r e t.he result wit.h (IP,Y). If they don't, match, branch t.o PERR2 t.o report the e r r o r . EOR the c o n t e n t s of .A w i t h $FF to flip the bits and store t.he result int.o the RAM at (IP),Y.
376
NAME
ADDRESS
$EB11
$EB17
$EB2D $EB32
Load . X wit.h $45 and t r a n s f e r this v a l u e t.o t.he stack p o i n t e r to reset the stack. Load .A w i t h the byte from the LED control port, L E D P R T ($1C00), A N D it wit.h $F7 ($FF-LED mask) and store the result back in L E D P R T to turn off L E D . Store $01 in PCR1 ($180C) to cause interrupt on the n e g a t i v e edge of ATN. Store $82 (10000010) in IFR1 ($180D) and IER1 ($180E). COMPUTE DEVICE # FROM BITS 5/6 OF PORT B
$EB3A
Load .A w i t h the dat.a byte from Port B, PB ($1800). A N D the byte w i t h $6 0 (%01100000). Do one ASL and three ROL's to c o n v e r t from bits 6/5 to b i t s 1/0. NOTE: 0 X X 0 0 0 0 0 b e c o m e s 000000XX OR .A wit.h $48 (the talk address) and store the result in T L K A D R ($78) . EOR .A wit.h $60 (the listen address) and store t.he result in L S N A D R ($77) . Initialize b u f f e r p o i n t e r table Zero .X and .Y Zero .A and store the $00 byt.e in .A in the b u f f e r table at B U F T A B , X ($99,X). Increment .X and load .A w i t h the hi byt.e of the p o i n t e r t.o the b u f f e r from B U F I N D , Y ($FEE0) and store it into the b u f f e r table at. B U F T A B , X ($99,X). Increment .X and .Y and c o m p a r e the new v a l u e of .Y w i t h $05 (the n u m b e r of b u f f e r s ) . If there are more b u f f e r s t.o do, b r a n c h t.o INTT1. Store the lo byte of t.he p o i n t e r to the c o m m a n d buffer ($00) int.o the b u f f e r table at B U F T A B , X ($99,X). Increment .X.
$EB43 $EB43
INTTAB INTT1
$EB5 9
$EB5F
377
NAME
ADDRESS
$EB64
$EB69
$EB6E
$EB72
DSKIN1
$EB76
$EB87 $EB8B $EB8F $EB93 $EB95 $EB9A $EB9F $EBA4 $EBA8 $EBAC $EBB6 $EBBC $EBBF $EBC2 $EBC5
BUF0+ERRCHN
($AC)
$EBCD $EBD1
Store $FF into B U F 0 + B L I N D X ($AD) Store $FF into B U F l + B L I N D X ($B4) Store $05 (the error channel #) into L I N T A B + E R R S A ($023B). Store $84 ($80 + the c o m m a n d channel #) int.o L I N T A B + C M D S A ($023A) . Store $0F (LINDX 0 to 5 free) into L I N U S E ($0256) . Store $01 (ready to listen) into C H N R D Y + C M D C H N ($F6). Store $01 (ready to talk) into C H N R D Y + E R R C H N ($F7) . Store $E0 into B U F U S E ($024F) and $FF into B U F U S E + 1 ($0250) . Store $01 int.o W P S W ($lC) and W P S W + 1 ($lD) to set up the w r i t e protect status JSR to U S R I N T ($CB63) to initialize the user jump table. JSR to L R U I N T ($CEFA) to i n i t i a l i z e t.he least r e c e n t l y used table. JSR to C N T I N T ($F259) to i n i t i a l i z e the disk c o n t r o l l e r . Set up the indirect NMI v e c t o r at. V N M I ($65/6) to point to the d i a g n o s t i c r o u t i n e , DIAGOK ($EB22). Store $0A int.o SECINC ($69) as t.he normal next sector i n c r e m e n t . Store $05 into R E V C N T ($6A) as the normal r e c o v e r y c o u n t e r .
378
NAME
SETERR
ADDRESS
$EBD5
$EBDA
$EBDF $EBE4
W A I T FOR S O M E T H I N G
TO DO.
$EBE8
$EBF0
IDL02
Clear interrupt mask (CLI) to allow interrupts. R e l e a s e all the bus lines: Load .A w i t h the byte from port B, PB ($1800) , AND it. w i t h $E5 t.o set. CLOCK, DATA, and ATNA lines high, and store the result back in PB ($1800). Check the v a l u e of C M D W A T ($0255) to see if there is a c o m m a n d w a i t i n g . If it. is $00, there is none w a i t i n g so b r a n c h to IDL1. Store $00 in C M D W A T ($0255) to clear the c o m m a n d w a i t i n g flag. Store $00 in N M I F L G ($67) to clear the debounce. JSR t.o P A R S X Q ($C146) to parse and then e x e c u t e the c o m m a n d . Clear interrupt mask (CLI) t.o allow interrupts. C h e c k t.he v a l u e of A T N P N D ($0255) t.o see if there is an a t t e n t i o n p e n d i n g . If itis $00, there is nothing p e n d i n g (such as the d r i v e running or an open file) so b r a n c h to IDL01. JMP to A T N S R V ($E85B) to service the a t t e n t i o n request-. Clear interrupt mask (CLI) to a l l o w interrupts. Store $0E (#14), the m a x i m u m secondary a d d r e s s for files in TEMP+3 ($72). Zero TEMP ($6F) and TEMP+1 ($70). Load .X w i t h the secondary a d d r e s s c o u n t e r from TEMP+3 ($72). Load .A w i t h the channel n u m b e r for this s e c o n d a r y a d d r e s s from L I N T A B , X ( $ 0 2 2 B , X ) If it is $FF, there is no active file for t.his SA so b r a n c h to IDL3.
379
NAME
ADDRESS
$EClB
DESCRIPTION OF WHAT ROM ROUTINE DOES We've found an active file so AND the channel number with $3F and store the result as the current channel number in LINDX ($82) .
JSR t.o G E T A C T ($DF93) to get the active buffer number (returned in .A). Transfer the buffer n u m b e r from .A to .X D e t e r m i n e w h i c h drive is to be used by loading t.he old job n u m b e r from LSTJOB,X ($025B,X), AND'ing it with $01, and t r a n s f e r r i n g the result into .X. Increment the count of the number of active files on drive X in T E M P , X ( $ 6 F , X ) D e c r e m e n t the SA count in TEMP + 3 ($72) . If there are more secondary addresses left t.o check, branch back to IDL2. Load .Y wit.h $04 (the n u m b e r of buffers less 1). Load .A wit.h t.he current job code for this buffer from the job queue, JOBS,Y ($00,Y) . If bit. 7 is not set., no job is in p r o g r e s s so branch t.o IDL5. There is a job in p r o g r e s s so AND t.he job code in .A w i t h $01 to mask off the non-drive bits and transfer the resultto .X. Increment the count of the number of active files on drive X in T E M P , X ( $ 6 F , X ) Decrement the buffer counter in .Y. If there are more b u f f e r s t.o check, branch to IDL4.
$EC3 6
$EC3E $EC3F
Set. the interrupt mask (SEI) to prevent. interrupts while reading LEDPRT ($1C00). Load .A with the data byt.e from t.he port. controlling t.he LED, AND the byt.e with $F7 ($FF - LED mask), and save the result onto the stack.
Load .A wit.h the current drive number from D R V N U M ' ($7F) and save it in R0($86) Zero D R V N U M ($7F). Test. the active file count for drive 0 in TEMP ($6F). If $00, branch to IDL7. Load the write protect switch byte from WPSW ($lC). If it is $00 branch to IDL6. JSR to CLDCHN ($D313) to close all files Pull the LED data byte off the stack, OR it- w i t h $08 (LED mask) to turn on the LED since drive 0 is active, and save the byt.e back ont.o the stack. Increment the D R V N U M ($7F). (to $01) Test the active file count for drive 1 in TEMP+1 ($70). If $00, branch to IDL9. Load the w r i t e protect switch byte from WPSW ($lC). If it is $00 branch to IDL8.
IDL6
IDL7
380
NAME
IDL8
ADDRESS
$EC6 6 $EC69
IDL9
$EC7 7 $EC7A
$EC7E
IDL10
$EC81
$EC90
STDIR
$EC9E $ECA2
$ECA7
381
NAME
ADDRESS
$ECB6 $ECB7
$ECBC
$ECC1
$ECC6
$ECCE
$ECD4
DIR1
$ECEA
$ECF2
$ECF7
$ECFD
$ED03 $ED06
382
NAME
ADDRESS
$EDOB
DIR10
DIR3
$ED2 3
$ED2 9
383
NAME
ADDRESS $ED53
DESCRIPTION
OF W H A T
ROM R O U T I N E
DOES
$ED56
D e c r e m e n t the p o i n t e r in L S T C H R , X ($0244,X) by 1 so it. does actually point to the last c h a r a c t e r in the b u f f e r . JMP to DIR10 ($EDOD) to set the channel status and flags and exit. T r a n s f e r file name to listing b u f f e r Zero .Y Load . A wit.h the c h a r a c t e r from N A M B U F , Y ($02Bl,Y) and JSR to P U T B Y T ($CFF1) to store it. in t.he listing b u f f e r . Increment .Y. If .Y is not $lB (#27) yet., b r a n c h to M O V B l . T e r m i n a t e r o u t i n e w i t h an RTS. Get. c h a r a c t e r for d i r e c t o r y load JSR to G E T B Y T ($D137) to get a byte from the data b u f f e r (loads next block if necessary). On r e t u r n , if the Z flag is set, we are at t.he e n d - o f - f i l e so b r a n c h to G E T D 3 . T e r m i n a t e r o u t i n e w i t h an RTS. Store the byt.e (in .A) into DATA ($85). Load .Y w i t h the channel n u m b e r from LINDX ($82). Load .A w i t h t.he lo byt.e of the p o i n t e r into the d i r e c t o r y b u f f e r from L S T C H R , Y ($0244,Y) If the lo byte of the p o i n t e r is $00, we have e x h a u s t e d the c u r r e n t b u f f e r so b r a n c h to G D I . We must. be at the e n d - o f - f i l e so load .A w i t h $80 (EOI) and store it as the channel status in C H N R D Y , Y ($00F2,Y). Load .A wit.h the byte from DATA ($85). T e r m i n a t e routine w i t h an RTS. Save the null byte in .A onto t.he stack. JSR to DIR1 ($ECEA) to c r e a t e p s e u d o program listing in t.he listing b u f f e r . Pull the null dat.a byte off the stack. T e r m i n a t e r o u t i n e w i t h an RTS. (COLLECT) DISK COMMAND
MOVBUF MOVBl
$ED59 $ED5B
$ED61 $ED66
GETDIR
$ED67
$ED74
VALIDATE
VALDAT VERDIR
C r e a t e a new BAM to m a t c h the sectors used by the c u r r e n t d i r e c t o r y e n t r i e s . JSR t.o S I M P R S ($C1D1) to parse the c o m m a n d string and e x t r a c t the drive #. JSR to INITDR ($D042) to i n i t i a l i z e the drive specified. Store $40 in W B A M ($02F9) to mark BAM as d i r t y (needs to be w r i t t e n out).
384
NAME
ADDRESS
$ED8F $ED92
$ED9A
VD10
VD15
$EDB3 $EDB4
$EDB7 $EDB8
$EDBB $EDBD
$EDC8
VD17
385
NAME
VD2 0
ADDRESS
$EDD4 $EDD7
VD2 5
$EDD9 $EDDB
$EDDF
$EDE2
VMKBAM
$EDE5 $EDE8
$EDEB
MRK2
$EDEE
MRK1
$EE04
$EE07 $EEOA
A full, or long NEW m a r k s off the tracks and sectors on a d i s k e t t e , w r i t e s null data b l o c k s in all sectors, and c r e a t e s a new BAM and d i r e c t o r y on track 18. A short. NEW m e r e l y c r e a t e s a new BAM and d i r e c t o r y on track 18.
386
NAME NEW
ADDRESS $EEOD
$EE10
$EE14
N101
$EE19
$EElD $EE20
$EE2 4
$EE2 7
A N D the d r i v e n u m b e r (in .A) w i t h $01 to mask off the non d r i v e b i t s and store the r e s u l t as the c u r r e n t drive in D R V N U M ($7F). JSR to SETLDS ($C100) to turn on the drive active LED. Load .A w i t h the drive n u m b e r from D R V N U M ($7F), m u l t i p l y it by 2 (ASL), and t r a n s f e r it int.o .X. Load .Y w i t h the p o i n t e r to the start of t.he new d i s k ID in the c o m m a n d b u f f e r from F I L T B L + 1 ($027B). C o m p a r e the ID p o i n t e r in .Y wit.h the length of the c o m m a n d string in C M D S I Z ($0274). If these v a l u e s are e q u a l , there is no new disk ID. T h e r e f o r e this must be a short new so b r a n c h t.o N 1 0 8 . T r a n s f e r new d i s k ID from the c o m m a n d b u f f e r C M D B U F , Y ($0200,Y) and C M D B U F + l , Y ($0201,Y) to t.he m a s t e r disk ID area DSKID,X ($12,X) and D S K I D + l , X ($13,X). JSR to C L R C H N ($D307) to clear all channels while formatting. Store $01 into TRACK ($80) as first track to do. JSR to F O R M A T ($C8C6) to set up JMP c o m m a n d in b u f f e r t.hat p o i n t s to the f o r m a t t i n g r o u t i n e to be used by the disk c o n t r o l l e r . JSR to C L R B A M ($F005) to clear the B A M . JMP to N 1 1 0 ($EE56) to c o n t i n u e . Clear directory only. JSR to INITDR ($D042) to init. the drive Load .X w i t h the d r i v e n u m b e r from D R V N U M ($7F). Load .A wit.h the DOS v e r s i o n n u m b e r as given in the B A M , D S K V E R , X ($0101,X) and c o m p a r e it w i t h t.he 1541 DOS v e r s i o n n u m b e r ($41) from V E R N U M ($FED5). If the v e r s i o n n u m b e r s m a t c h , b r a n c h to N 1 1 0 .
$EE2C
$EE4 0 $EE4 3
N108
387
NAME
ADDRESS
$EE53
N110
$EE63
$EE6B $EE6D
$EE75
$EE78
$EE7D
$EE8D
JSR to N E W M A P ($EEB7) t.o c r e a t e a new BAM. Load .A wit.h the c u r r e n t job code from J O B N U M ($F9) and t r a n s f e r it to .Y. M u l t i p l y t.he job code in .A by 2 (ASL) and t r a n s f e r the result t.o .X. Load .A w i t h $90, the offset of the disk name in the BAM from D S K N A M ($FE88) and store this p o i n t e r in B U F T A B , X ($99,X). Load .X w i t h the b u f f e r n u m b e r from F I L T B L ($027A), load .Y w i t h $27 (the name length) and JSR t.o T R N A M E ($C66E) to t r a n s f e r t.he new disk name from the c o m m a n d b u f f e r int.o the BAM a r e a . Load .Y w i t h $12 (position of disk ID). Load .X w i t h the d r i v e n u m b e r from D R V N U M ($7F) and copy the DOS v e r s i o n n u m b e r ($41) from V E R N U M ($FED5) int.o D S K V E R , X ($0101,X). T r a n s f e r the d r i v e n u m b e r from .X to .A, m u l t i p l y it by 2 (ASL), and transfer the result b a c k into .X. T r a n s f e r the first disk ID c h a r a c t e r from D S K I D , X ($12,X) into (DIRBUF),Y ($94) ,Y. Increment. .Y. T r a n s f e r the second d i s k ID c h a r a c t e r from D S K I D + l , X ($13,X) int.o (DIRBUF),Y ($94),Y. Increment .Y t w i c e . Store the d i r e c t o r y DOS v e r s i o n ($32; ASCII 2) int.o (DIRBUF),Y; ($94),Y. Increment .Y. T r a n s f e r the format type ($41? ASCII A) from V E R N U M ($FED5) into (DIRBUF),Y ($94),Y. Load .Y w i t h $02 so it. p o i n t s to the third b y t e in the BAM and store the format type ($41; in .A) into the BAM at. (BMPNT) ,Y; ($6D),Y. T r a n s f e r the d i r e c t o r y track n u m b e r , $12 from DIRTRK ($FE85) into TRACK ($80) . JSR t.o U S E D T S ($EF93) t.o mark track 18 sector 0 as used in the B A M . Set SECTOR ($81) t.o $01. JSR to U S E D T S ($EF93) to mark track 18 sector 1 as used in the B A M . JSR to S C R B A M ($EEFF) to w r i t e out. t.he new BAM to d i s k . JSR t.o C L R B A M ($F005) to set all of BAM area to $00.
388
NAME
ADDRESS
$EEA6
$EEAC $EEAF
$EEB4
NEWMPV NEWMAP
$EEB7 $EEBA
NM10
$EEC4 $EEC7
$EECF
$EED2
$EEE0
NM3 0 $EEE3
$EEED
389
NAME
ADDRESS $EEF1
MAPOUT
W r i t e out B A M t.o the d r i v e s p e c i f i e d in LSTJOB. J S R to G E T A C T ($DF93) to find the a c t i v e b u f f e r n u m b e r (returned in .A). T r a n s f e r the b u f f e r n u m b e r to .X. L o a d .A w i t h the job c o d e for the lastjob from L S T J O B , X ( $ 0 2 5 B , X ) , A N D it w i t h $01 to m a s k off the n o n - d r i v e b i t s , and s t o r e the result- in D R V N U M ($7F) . W r i t e o u t B A M to the dr ive s p e c i f i e d in DRVNUM. L o a d .Y w i t h t.he d r i v e n u m b e r from D R V N U M ($7F). L o a d .A w i t h the B A M - d i rty flag from M D I R T Y , Y ( $ 0 2 5 1 , Y ) . If t.he flag is n o t $00, the B A M is d i r t y ( the c o p y in R A M d o e s N O T m a t c h the c o p y on d i s k ) so b r a n c h to S B 1 0 to w r i t e it out. to d i s k , B A M is c l e a n so t h e r e i s no r e a s o n to w r i t e it o u t . T e r m i n a t e r o u t i n e w i t h an R T S . Z e r o t h e B A M - d i r t y flag in M D I R T Y , Y ($0251,Y). J S R to S E T B P T ($EF3A) to set up t_he p o i n t e r to the B A M . L o a d .A w i t h t h e d r i v e n u m b e r from D R V N U M ($7F), m u l t i p l y it by 2 (ASL), and s a v e t.he r e s u l t o n t o the s t a c k . J S R to P U T B A M ($F0A5) to put t.he m e m o r y i m a g e s to the B A M . Pull the (drive n u m b e r x 2) off the s t a c k , c l e a r t.he c a r r y f l a g , add $01, and J S R to P U T B A M ($F0A5) to put the m e m o r y i m a g e s to the B A M . V e r i f y t h a t t h e b l o c k c o u n t for the t r a c k m a t c h e s the b i t m a p for the t r a c k . L o a d .A f r o m T R A C K ($80) and p u s h the t r a c k n u m b e r o n t o t.he s t a c k . L o a d .A w i t h $01 and s t o r e it. in T R A C K . M u l t i p l y the t r a c k n u m b e r in .A by 4 (2 x A S L ) and s t o r e t h e r e s u l t as the lo b y t e of the b u f f e r p o i n t e r in B M P N T ($6D). J S R to A V C K ($F220) to c h e c k t h a t t.he b l o c k s f r e e for the t r a c k a g r e e s w i t h t.he b i t m a p .
SCRBAM
$EEFF $EF01
$EF0 6
SB10
$EF13 $EF16
SB20
$EF2 8
390
NAME
ADDRESS
$EF2B
$EF3 4 $EF3 7
SETBPT
$EF3A
$EF48 $EF4C
NUMFRE
$EF4D $EF4F
$EF5 5
$EF5B
WFREE
$EF5C
FRETS
$EF5F
FRETS2
$EF62 $EF63
391
NAME
ADDRESS
$EF6 5
$ED6C
$ED6F
$EF72
$EF78
$EF7F
$EF8 4
FRERTS
$EF87
DTYBAM
WUSED
USEDTS
$EF9 0
$EF9 3
$EF96
392
NAME
ADDRESS
$EF9 8
$ED9F
$EDA2
$EFA4
$EFAB
$EFB2
$EFB7
USE10
$EFBA
USE20
$EFBD
$EFC2
FREUSE
$EFCF
FREUS2
$EFD2 $EFD3
393
NAME
FREUS3
ADDRESS
$EFD5
$EFDA
$EFDE
$EFE3
$EFE8
BMASK
FIXBAM
FBAM10
$FC04
W r i t e out BAM to disk if v a l u e in W B A M i n d i c a t e s that it is n e c e s s a r y . Load .A wit.h $FF and BIT this v a l u e wit.h t.he v a l u e in W B A M ($02F9). If Z flag set (WBAM w a s $00) b r a n c h to F B A M 1 0 to e x i t . If N flag clear (bit 7 of W B A M was 0) b r a n c h to F B A M 1 0 to e x i t . If V flag set (bit 6 of W B A M was 0) b r a n c h t.o F B A M 1 0 t.o e x i t . Set W B A M ($02F9) to $00 and JSR to D O W R I T ($D58A) to w r i t e BAM to d i s k . T e r m i n a t e r o u t i n e w i t h an RTS. Zero the BAM a r e a : JSR to SETBPT ($EF3A) to set the p o i n t e r s to t.he B A M . Zero .Y and .A. Loop, using .Y as an index, to store $00's in all 256 locations in the BAM buffer. T e r m i n a t e routine w i t h an RTS.
CLRBAM
$F005 $F008
CLB1
$F00B
$F010
394
NAME
ADDRESS
SETBAM
$F011
$F017
$F019
SBM10
$F02E
$F033
$F036
$F03B
SBM30
$F045
395
NAME
ADDRESS
$F04C
$F052 $F054
$F05A
SWAP
$F05B
$F060
$F063
BAM.
$F070 Load .A w i t h the b u f f e r n u m b e r from J O B N U M ($F9) , m u l t i p l y it. by two (ASL) , and t r a n s f e r the result int.o .X. Load .A w i t h the track n u m b e r from TRACK ($80), m u l t i p l y it. by four (2 x ASL), and store the result as the lo byte of the p o i n t e r in B U F T A B , X ($99,X). Load .A w i t h the v a l u e from T1 ($70), m u l t i p l y it by four (2 x A S L ) , and t r a n s f e r the result into .Y. T r a n s f e r one b y t e of t.he BAM from it.s p o s i t i o n in R A M , (BUFTAB,X) ($99,X), to its p r o p e r p o s i t i o n B A M , Y ($02Al,Y). Zero the m e m o r y location that held t.he BAM b y t e (BUFTAB,X)? ($99,X). Increment t.he lo byte of the p o i n t e r to the o r i g i n a l BAM image B U F T A B , X ($99,X). Increment .Y, t.he p o i n t e r t.o the new BAM image. T r a n s f e r this v a l u e into .A, A N D it wit.h $03 to mask off the high order bit.s, and if the result is not $00, b r a n c h back to SWAP3 t.o move the next. byte. Load .X wit.h the d r i v e n u m b e r from T1 ($70). Load .A w i t h the c u r r e n t track n u m b e r from TRACK ($80) and store the track n u m b e r into T B A M , X ($029D,X) t.o set t.he track n u m b e r for the image.
$F074
$F07A
SWAP3
$F07F
$F090
396
NAME
ADDRESS
$F097
$F09C
SWAP4
$F09F
$F0A4
PUTBAM
$F0A5 $F0A6
$F0B5
$FOBA
SWAP1
SWAP2
$FODO
CLNBAM
$F0D1
$F0D5 $FODA
397
NAME
ADDRESS $FODE
REDBAM
$FODF
$F0E5 $F0E7
$FOEB $FOED
Read BAM from disk if not already in RAM Load .A w i t h the v a l u e from BUFO,X and c o m p a r e it. w i t h $FF. If it. is not. $FF, t.he BAM is in memory so b r a n c h to R B M 2 0 . T r a n s f e r the channel n u m b e r from .X int.o .A and save it onto the stack. J S R t o G E T B U F ($D28E) to find a free b u f f e r . On r e t u r n t r a n s f e r the b u f f e r n u m b e r from .A into .X. If a b u f f e r w a s found (bit 7 of b u f f e r n u m b e r not set), b r a n c h to R B M 1 0 . Load .A w i t h $70 to i n d i c a t e a N O C H A N N E L ERROR and JSR to C M D E R R ($ClC8). Store the b u f f e r n u m b e r assigned (in .X) into J O B N U M ($F9) . Pull t.he channel n u m b e r off the stack and t r a n s f e r it into .Y. T r a n s f e r the b u f f e r n u m b e r from .X t.o .A, OR it. w i t h $80 to set. it as inactive for s t e a l i n g , and store the result into B U F 0 , Y ($00A7,Y). M u l t i p l y the b u f f e r n u m b e r (in .A) by two (ASL) and t r a n s f e r the result into
RBM10
$FOFC
.X.
$FOFE Load .A wit.h the d i r e c t o r y track n u m b e r (#18) from DIRTRK ($FE85) and store it. in t.he h e a d e r table at. HDRS,X ($06,X). Store $00 as the BAM sector n u m b e r in t.he h e a d e r table at H D R S + l , X ($07,X). JMP to D O R E A D ($D58 6) to read in the BAM and t e r m i n a t e r o u t i n e . A N D t.he channel n u m b e r (in .A) w i t h $0F and store the result in JOBNUM ($F9) to set. the B A M ' s job n u m b e r . T e r m i n a t e r o u t i n e wit.h an RTS. Load .A w i t h t.he channel # for the BAM Load .A w i t h $06, the B A M ' s channel # Load .X wit.h the current d r i v e n u m b e r from D R V N U M ($7F). If the drive n u m b e r is not $00, b r a n c h to B 2 X 1 0 . Clear the carry flag and add $07 to find t.he BAM channel n u m b e r for drive #1. T e r m i n a t e r o u t i n e w i t h an RTS. Load .X wit.h t.he c h a n n e l # for t.he BAM JSR TO BAM2A ($F10F) to load .A w i t h the B A M ' s channel n u m b e r . T r a n s f e r t.he channel # from .A to .X.
$F10 3 $F10 7
RBM20
$FlOA
$FlOE
BAM2A
$F10F $F111
BAAM2X
$F119 $FllC
398
NAME
ADDRESS
$FllD
NXTTS
NXTDS NXT1
$F13A
Next a v a i l a b l e track and sector: G i v e n c u r r e n t track and sector, this r o u t i n e r e t u r n s the next a v a i l a b l e track and s e c t o r . JSR to G E T H D R ($DE3E) to set. TRACK and SECTOR from the most recent h e a d e r . Store $03 into TEMP ($6F). Load .A w i t h $01, OR it. w i t h the v a l u e of the w r i t e - B A M flag, W B A M ($02F9), and store t.he result back into W B A M to p r e v e n t a w r i t e of the B A M . Load .A wit.h the v a l u e from TEMP ($6F) and save it onto the stack. JSR to SETBAM ($F011) to set the BAM image into m e m o r y . Pull t.he original v a l u e of TEMP off the stack and store it back in TEMP ($6F). Load .A w i t h t.he BAM v a l u e from (BMPNT) ,Y; ($6D,Y) . If the v a l u e is not. $00 (no sectors free) , b r a n c h t.o FNDNXT ($F173). Load .A wit.h the c u r r e n t track n u m b e r from TRACK ($80). If the track n u m b e r is #18 (directory track) , b r a n c h t.o N X T E R R to a b o r t . If the c u r r e n t track is less than #18, b r a n c h to N X T 2 . Increment the track n u m b e r in T R A C K ( $ 8 0 ) C o m p a r e t.he v a l u e of TRACK to $24 (#36) , the m a x i m u m track v a l u e . If they are not e q u a l , b r a n c h t.o NXT1 to check out this track. Load .X w i t h $12 (#18), t.he d i r e c t o r y track n u m b e r from DIRTRK ($FE85). Decrement, t.he track n u m b e r in .X. Store the track n u m b e r (in .X) into TRACK ($80). Store $00 as the sector n u m b e r int.o SECTOR ($81). D e c r e m e n t the c o u n t e r in TEMP ($6F). If the count is not $00 yet., b r a n c h to NXT1. Load .A w i t h $72 t.o indicate a DISK FULL e r r o r and JSR to C M D E R R ($ClC8). D e c r e m e n t the track n u m b e r in T R A C K ( $ 8 0 ) If t.he value in TRACK is not. $00, b r a n c h to NXT1 t.o check out this track. Load .X w i t h $12 (#18) , the d i r e c t o r y track n u m b e r from DIRTRK ($FE85). Increment t.he track n u m b e r in .X.
NXT2
399
NAME
ADDRESS
$F167 $F16 9 $F16D $F16F $F171
FNDNXT
$F173 $F175
$F178 $F17A
$F17F $F18 5
$F191
$F193
FNDN0
$F19 5
$F198
JSR t.o G E T S E C ($FlFA) to set t.he BAM into memory and find t.he first a v a i l a b l e sector f o l l o w i n g t.he revised sector #. If no sector is a v a i l a b l e on t.his t.rack (Z flag = 1), b r a n c h t.o F N D N 2 . Exit w i t h a JMP t.o W U S E D ($EF90) to set. this new sector as in use. Set the sector n u m b e r in SECTOR ($81) t.o $00.
FNDN1 FNDN2
$F19A $F19D
400
NAME
ADDRESS
$FlAl
$F19 8 $FlA6
INTTS
$FlA9
$F1C0
ITS2
$FlDA
ITS3
$FlDF $FlE2
401
NAME
ADDRESS
$FlE4
FNDSEC
DERR
$FlF5
GETSEC
$F214
GS3 0
$F21F
AVCK
$F2 20
402
NAME
ADDRESS
$F223 $F227 $F2 2A
AC10 AC20
$F2 2B $F2 2D
$F234
AC3 0
$F236
$F239
$F2 3C
$F2 42 $F2 45
AC4 0
$F246
MAXSEC MAX1
$F24B $F24E
KILLP
$F258
403
NAME
ADDRESS
CNTINT
$F2A8
$F2AC LCC
Controller initialization Store % 0 1 1 0 1 1 1 1 in D D R B 2 ($lC02) to set the data d i r e c t i o n for Port B. Store % 0 1 1 0 0 0 0 0 in D S K C N T ($1C00) to turn off t.he motor & LED and set phase A Set the p e r i p h e r a l control r e g i s t e r ($1C0C) for neg edge latch mode, CA2 hi t.o d i s a b l e the SO line to the 6502, CB1 is input, and CB2 is R/W mode c o n t r o l , set. T l H L 2 ($ 1C0 7) to $3A and T l L L 2 ( $ l C 0 6 ) to $00 so there is 20ms b e t w e e n IRQ's store $7F in IER2 ($1C0E) t.o clear all IRQ s o u r c e s . store $C0 in IFR2 ($1C0D) t.o clear the bit. and then into IER2 ($1C0E) t.o e n a b l e t.he timer IRQ. store $FF as the c u r r e n t d r i v e , C D R I V E ($3E) and as init. flag, FTNUM ($51). set h e a d e r b l o c k ID, H B I D ($39) to $08 set. data b l o c k ID, D B I D ($47) to $07 set N X T S T ($62/3) to point t.o INACT ($FA05). set M I N S T P ($64) to 200 to indicate the m i n i m u m n u m b e r of st.eps required t.o invoke the fast stepping mode, store 4 into AS ($5E) to indicate the n u m b e r of steps needed to a c c e l e r a t e and d e c e l e r a t e t.he head, store 4 into AF ($5F) as the a c c e l e r a t i o n / d e c e l e r a t i o n factor. Main c o n t r o l l e r loop: Scans the job q u e u e for job r e q u e s t s Finds job on current track if it e x i s t s Save stack pointer in S A V S P ($49) . reset IRQ flag set b i t s 3,2,& 1 of PCR2 ($1C0C) t.o enable S.O. to 6502, hi output top of loop to scan job q u e u e . Load .Y with #$05 as p o i n t e r t.o top of q u e u e . Load .A w i t h b y t e from q u e u e , JOBS,Y ($0000,Y). Test. if bit. 7 is set. If not., b r a n c h to C O N T 2 0 since no job h e r e . Check if job is a jump code ($D0). If not., b r a n c h to C O N T 3 0 . T r a n s f e r q u e u e p o s i t i o n from .Y to .A and JMP to EX2 ($F370) to do jump job. A N D job code w i t h $01. If result is 0, the drive # is valid so b r a n c h to C O N T 3 5 Load .A w i t h $0F to indicate a bad d r i v e n u m b e r and JMP to ERRR ($F969)
TOP CONTlO
$F2BE $F2C3
404
NAME
CONT35
ADDRESS
$F2D8 $F2DB
$F2ED
CONT2Q
$F2F3
QUE
$F2F9 $F2FD
QUE05
$F306 $F315
QUE20
GOTU
$F3 3C
$F34D
405
NAME BMP
ADDRESS
$F3 7C $F380 $F388 $F38C $F390 $F393
SETJB
SEAK
$F3B1
SEAK
SEEK15
$F3C8 $F3D5
SEEK3 0
406
NAME
ADDRESS
$F3F2
$F4 04
SEEK20
$F407
ESEEK DONE
$F410 $F418
BADID
CSERR
$F41B $F41E
L460 L480
$F455
L465
$F45E $F461
$F465
D e t e r m i n e best. sector on this track t.o service (optimum is current sector + 2) Store $7F as t.he c u r r e n t sector in $4C Load .A w i t h the sector n u m b e r from the h e a d e r just read from H E A D E R + 3 ($19). Add 2 C o m p a r e sum to the n u m b e r of sectors on this track in SECTR ($43) . If sum is t.oo big, subtract t.he n u m b e r of sectors. Store sum as next. sector t.o be serviced in N E X T S ($4D). JSR to SETJB ($F393) t.o set. p o i n t e r s . C h e c k t.o be sure job is for this d r i v e . If not, b r a n c h to L470 ($F483). C h e c k t.o be sure job is for t.his track. If not., b r a n c h t.o L470 ($F483). C o m p a r e job code in JOB ($45) wit.h $60 t.o see if it. is an execute job. If itis, b r a n c h t.o L465. Load .A with job's sector, (HDRPNT),Y and subtract the upcoming sector from N E X T S ($4D). If result is p o s i t i v e , b r a n c h to L465 since sector c o m i n g up. Add v a l u e from N E X T S ($4D) back in. C o m p a r e t.o d i s t a n c e to other sector r e q u e s t . If further away, b r a n c h to L 4 7 0 since other job is c l o s e r . Save d i s t a n c e to sector on t.he stack. C h e c k job code in JOB ($45). If a read job, b r a n c h t.o T S T R D J .
407
NAME
ADDRESS
$F46A
DOITT
$F473
TSTRDJ
$F47E
L470
$F483 $F487
fNVRTN
P 4.9 7..
$F4ED $F4F0
$F4F4
408
NAME
ADDRESS
$F5F6
READ28
$F4FB
$F4FE
DSTRT
SRCH
$F510
$F529
409
NAME
ADDRESS
$F556
$F562
$F567
WPIGHT $F56E
$F5 75
$F5 7A
WRT10
$F594 $F59 9
Store $FF in D D R A 2 ($lC03) t.o make Pcrt A an output portLoad .A from PCR2 ($1C0C), A N D the v a l u e w i t h $lF, OR it wit.h $C0, and store the result in PCR2 to t.urn on w r i t e mode. Store $FF in D A T A 2 ($lC01) as the SYNC mark c h a r a c t e r L o o p t.o w r i t e out. 5 c o n s e c u t i v e $FF b y t e s (5x8 = 40 1's).
410
NAME
ADDRESS
$F5B1
WRT3 0
$F5B3
WRT4 0
$F5BF
$F5CA $F5CC
$F5D4 $F5D9
$F5DC $F5E6
CHKBLK
$F5E9
WTOBIN
$F5F2
$F608
$F60A
411
NAME
ADDRESS
$F60D $F611
WTOB14
$F624
$F629
WTOB5(J WTOB5 3
$F629
WTOB52
$F66E
WTOB5 7
Move d e c o d e d b y t e s in lower part. of the data b u f f e r up into their p r o p e r p l a c e s in the b u f f e r . Move d e c o d e d b y t e s from the o v e r f l o w b u f f e r to the b o t t o m of the dat.a b u f f e r . Set. GCRFLG ($50) to 0 to indicate that the d a t a in b u f f e r is in normal form. Exit w i t h an R T S . V e r i f y a data b l o c k This r o u t i n e c o n v e r t s t.he data in the dat.a b u f f e r into its 10 bit encoded form (GCR). It then c o m p a r e s the GCR image w i t h w h a t is r e c o r d e d on t.he d i s k . The encoded data is then changed back into normal 8 bit b i n a r y form.
VRFY
$F691
C o m p a r e job c o d e in .A w i t h $20 to check that t.his is a v e r i f y job. If not, JMP to SECTSK (F6CA) t.o do a sector seek.
412
NAME
ADDRESS
$F698
VRF3 0
$F6B3
SECTSK
PUT4GB
$F6CA
$F6D0
C o n v e r t b i n a r y to GCR This r o u t i n e is used to c o n v e r t 4 normal 8 bit b y t e s into the 10 bit encoded form used for r e c o r d i n g ont.o d i s k . E n c o d i n g i n v o l v e s b r e a k i n g up each 8 bit normal b y t e into t.wo 4 - b i t n y b b l e s . The 5-bit e q u i v a l e n t for each n y b b l e is found by looking in a t a b l e . The 10 bit.s that r e s u l t are stored in two c o n s e c u t i v e m e m o r y l o c a t i o n s . W h e n four 8 - b i t b y t e s are e n c o d e d , t.he r e s u l t i n g 40 b i t s are stored like this:
Four normal 8 bit b y t e s stored in $52/3/4/5 A A A A B B B B CCCCDDDD^ E E E E F F F F G G G G H H H H Four 10 bit encoded aaaaabbb bbcccccd $F6D0 b y t e s stored in b u f f e r ddddeeee efffffgg ggghhhhh
$F6D8
C l e a r c r i t i c a l areas of the b u f f e r w h e r e the encoded b y t e s are to be stored. G T A B to G T A B + 4 ($56-5A) Load first 8 - b i t byte ($52), A N D it w i t h $F0 (11110000) to mask off the low n y b b l e (AAAA0000), do four L S R ' s to c o n v e r t the hi n y b b l e to a low nybble ( 0 0 0 0 A A A A ) , look up the c o r r e s p o n d i n g five bit. GCR v a l u e (000aaaaa) in BGTAB B G T A B ($F7 7F+) , do three ASL's on it (aaaaa000), and store it in the first p o s i t i o n in t.he encoded dat.a area ($56)
413
NAME
ADDRESS
$F6E9
$F6FE
$F70F
$F72 5
$F73D
$F74D
414
NAME
ADDRESS
$F76F
BGTAB
$F7 7F
5 b i t s are used t.o e n s u r e that. not more t.han 2 c o n s e c u t i v e 0's are recorded on d i s k .
BINGCR
C r e a t e w r i t e image of dat.a This r o u t i n e c o n v e r t s 260 normal 8 - b i t b y t e s int.o their 10-bit e q u i v a l e n t s to p r o d u c e an image for w r i t i n g to d i s k . A total of 325 GCR b y t e s are p r o d u c e d .
415
NAME
ADDRESS
The first 69 GCR b y t e s are stored in the o v e r f l o w b u f f e r ($10BB-FF) . The rest. of the GCR b y t e s are stored in b u f f e r X and replace the original data bytes $F78F $F797 $F7A5 Initialize p o i n t e r s to b u f f e r s Set. p o i n t e r to start of o v e r f l o w $01bb Move data b l o c k ID code from D B I D ($47) and first 3 dat.a c h a r a c t e r s into a w o r k area ($52/3/4/5) for input by the P U T 4 G B r o u t i n e ($F6D0) Store p o i n t e r to next byt.e t.o c o n v e r t (in .Y) int.o B Y T C N T ($36). JSR to P U T 4 G B ($F6D0) to convert the four b y t e s in $52/3/4/5 into their five GCR e q u i v a l e n t s and store in b u f f e r . Use the o v e r f l o w b u f f e r first and then use t.he dat.a b u f f e r . Move next. four b y t e s int.o the w o r k area ($52/3/4/5) . If more b y t e s to c o n v e r t (.Y is count) b r a n c h back t.o B I N G 0 7 . Move data b l o c k c h e c k s u m from D B I D ($3A) and two off b y t e s ($00) int.o the w o r k area ($53/4/5) N O T E : THE L A S T DATA BYTE IS IN $52. JSR t.o P U T 4 G B ($F6D0) to c o n v e r t t.he four b y t e s in $52/3/4/5 int.o their five GCR e q u i v a l e n t s and store in b u f f e r . C o n v e r t GCR t.o b i n a r y This r o u t i n e is used to d e c o d e 5 GCR b y t e s (used for r e c o r d i n g on disk) into 4 normal 8-bit. b i n a r y b y t e s . D e c o d i n g involves e x t r a c t i n g 5 bit.s from one or t.wo GCR b y t e s . The 4-bit. nybble that is e q u i v a l e n t to it. is found by looking in a table. The p a t t e r n of 5-bit s e g m e n t s in the 5 GCR b y t e s and t.he e q u i v a l e n t 4 - b i t n y b b l e s in t.he four b i n a r y b y t e s are indicated b e l o w : Four 10 bit. encoded aaaaabbb bbcccccd byt.es stored in b u f f e r ddddeeee efffffgg ggghhhhh
BINGO 7
$F7BA $F7BC
$F7E3
GET4GB
416
NAME
ADDRESS
Four normal 8 bit b y t e s stored in $56/7/8/9 AAAABBBB CCCCDDDD EEEEFFFF GGGGHHHH $F7E6 Load the first GCR b y t e (aaaaabbb) from (BUFPNT) ,Y , A N D it w i t h $F8 (11111000) to mask off the low b i t s (aaaa000), do three L S R ' s and store the result (000aaaaa) in G T A B ($56) Load the first GCR b y t e (aaaaabbb) from (BUFPNT),Y, A N D it w i t h $07 (00000111) to m a s k off the high b i t s (00000bbb), do two A S L ' s and store the r e s u l t ( 0 0 0 b b b 0 0 ) in $57. I n c r e m e n t Y and check if Y = 0 . If so, c h a n g e B U F P N T so it p o i n t s to the data b u f f e r rather than the o v e r f l o w b u f f e r . Load the second GCR b y t e (bbcccccd) from (BUFPNT) , Y, A N D it. w i t h $C0 (11000000) to mask off the low b i t s (bb000000), do three ROL's (000000bb), OR it w i t h the v a l u e in $57 (000bbb00), and store the result (000bbbbb) back in $57. Load t.he second GCR b y t e ( b b c c c c c d ) from (BUFPNT) ,Y, A N D it. w i t h $3E (00111110) to mask off u n w a n t e d b i t s (00ccccc0), do one LSR and store the result (000ccccc) in $58. Load the second GCR byte (bbcccccd) from (BUFPNT),Y, A N D it w i t h $01 (00000001) to mask off u n w a n t e d b i t s (0000000d), do four A S L ' s and store the result (000d0000) in $58. Load the third GCR byt.e (ddddeeee) from (BUFPNT) ,Y, A N D it. wit.h $F0 (11110000) to mask off the low b i t s (dddd0000), do four L S R ' s (0000dddd) , OR it. w i t h the v a l u e in $59 (000d0000), and store the result (000ddddd) back in $59. Load the third GCR b y t e (ddddeeee) from (BUFPNT) , Y, A N D it. w i t h $ 0 F ( 0 0 0 0 1 1 1 1 ) to mask off hi bit.s (0000eeee) , do one ASL and store the result (000eeee0) in $5A. Load the f o u r t h GCR byt.e (efffffgg) from (BUFPNT),Y, A N D it w i t h $80 (10000000) to mask off the low b i t s (e0000000), do two ROL's (000e0000), OR it w i t h the v a l u e in $5A (0000eeee), and store the r e s u l t (000eeeee) b a c k in $5A. Load t.he fourth GCR b y t e (efffffgg) from (BUFPNT) ,Y, A N D it wit.h $7C (01111100) to mask off u n w a n t e d bit.s (0fffff00), do two L S R ' s and store the result.(000fffff) in $5B.
$F7F1
$F7F9
$F802
$F80D
$F814
$F81F
$F82B
$F833
$F840
417
NAME
ADDRESS
$F848
$F854 $F85A
$F866
$F86D
$F87B
$F887
$F893
Load .X wit.h the first 5-bit. v a l u e from $56, load .A w i t h 4 - b i t high nybble from G C R H I , X , load X w i t h a second five bit. v a l u e from $57, OR .A wit.h the four bit. low n y b b l e from G C R L O , X , and store t.he r e s u l t in $52. Load X wit.h the third 5-bit. v a l u e from $58, load .A w i t h 4 - b i t high n y b b l e from G C R H I , X , load X w i t h t.he fourth 5-bit. v a l u e from $59, OR .A w i t h the 4-bit. low n y b b l e from G C R L O , X and store t.he result in $53. Load X wit.h t.he fifth 5-bit. v a l u e from $5A, load .A wit.h 4 - b i t high n y b b l e from from G C R H I , X , load X wit.h t.he second five bit v a l u e from $5B, OR .A wit.h the four bit low n y b b l e from G C R L O , X , and store t.he r e s u l t in $54. Load .X wit.h the seventh 5 value from $5C, load .A w i t h 4 - b i t high n y b b l e from G C R H I , X , load X w i t h the second 5-bit. value from $5D, OR .A w i t h t.he four bit low n y b b l e from G C R L O , X , and store the r e s u l t in $55.
418
NAME
ADDRESS
N O T E : The five bit to four bit tables b e l o w h a v e many $FF e n t r i e s . These are the five bit codes that are not. u s e d . If one of these is found, it. c a u s e s a byte d e c o d i n g error GCRHI($F8A0) bit. GCR $00 $01 $02 $03 $04 $05 $06 $07 $08 $09 $0A $0B $0C $0D $0E $0F $10 $11 $12 $13 $14 $15 $16 $17 $18 $19 $lA $lB $lC $lD $lE $ 1F & GCRLO($F8CO) High $FF $FF $FF $FF $FF $FF $FF $FF $FF $80 $00 $10 $FF $C0 $40 $50 $FF $FF $20 $30 $FF $F0 $60 $70 $FF $90 $A0 $B0 $FF $D0 $E0 $FF nybble 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 1000 0001 11111111 1100 0100 0101 11111111 11111111 0010 0011 11111111 1111 0110 0111 11111111 1001 1010 1011 11111111 1101 1110 11111111 T a b l e s of 5 bit GCR to ] Low nybble ($F8A0 + I ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR $FF $FF $FF $FF $FF $FF $FF $FF $FF $08 $00 $01 $FF $0C $04 $05 $FF $FF $02 $03 $FF $0F $06 $07 $FF $09 $0A $0B $FF $0D $0E $FF binary
code
($F8C0+ ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR
00000 00001 00010 00011 00100 00101 00110 00111 01000 01001 01010 01011 01100 01101 01110 01111 10000 10001 10010 10011 10100 10101 10110 10111 11000 11001 11010 11011 11100 11101 11110 11111
ERROR
ERROR ERROR
ERROR
ERROR
ERROR
ERROR
11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 1000 0000 0001 11111111 1100 0100 0101 11111111 11111111 0010 0011 11111111 1111 0110 0111 11111111 1001 1010 1011 11111111 1101 1110 11111111
ERROR
ERROR ERROR
ERROR
ERROR
ERROR
ERROR
GCRBIN
D e c o d e GCR data image This r o u t i n e d e c o d e d the 69 GCR b y t e s stored in t.he o v e r f l o w b u f f e r ($10BB-FF) into n o r m a l 8 - b i t b y t e s . The decoded b y t e s are stored in a d a t a b u f f e r . $F8E0 $F8E8 Zero b y t e c o u n t e r & lo bit of Set lo b y t e of p o i n t e r , N X T B F $BA and set the hi b y t e N X T P N T $01 so they p o i n t to the first the G C R image in the o v e r f l o w pointers ($4E) t.o ($4F) to byte of buffer.
419
NAME
ADDRESS
$F8F0
$F8F4
$F8F7 $F8FB
GCRB10
$F90C $F90E
CONHDR
Save c u r r e n t v a l u e of the b u f f e r p o i n t e r B U F P N T + 1 ($31) in S A V P N T + 1 ($2F). Make B U F P N T + 1 ($31) point to >STAB ($00) Make G C R P N T ($34) point to <STAB ($24) Move hdr blk ID from H B I D ($39) to $52 Move c h e c k s u m from $lA to $53 Move sector from $19 to $54 Move track from $18 t.o $55 JSR to P U T 4 G B ($F6D0) to c o n v e r t the four b y t e s in $52-5 to 5 GCR b y t e s and store them at the start of STAB ($24-8).
420
NAME
ADDRESS
$F953 $F957 $F95B $F961
$F964
ERRR
Disk c o n t r o l l e r error h a n d l i n g This r o u t i n e is used to t e r m i n a t e all of the major d i s k c o n t r o l l e r r o u t i n e s . The inputs to this r o u t i n e are: the error code (see table) in .A, the job b u f f e r n u m b e r in JOBN ($3F), and the G C R F L G ($50) (tells if the data in the b u f f e r has b e e n left in w r i t e image (1) or b i n a r y (0) form). The r o u t i n e s t u f f s t.he e r r o r c o d e into the job q u e u e , c o n v e r t s the d a t a b a c k to b i n a r y (if n e c e s s a r y ) , starts t i m e - o u t to turn off the d r i v e m o t o r , r e s e t s the stack p o i n t e r , and e x i t s to $F2BE to b e g i n scanning the job q u e u e a g a i n . S t o r e e r r o r code in .A into job q u e u e C h e c k G C R F L G ($50) to see if data left in GCR f o r m a t . If not, b r a n c h to E R R R 1 0 . JSR to W T O B I N ($F5F2) to c o n v e r t dat.a from G C R to n o r m a l . JSR to T R N O F F ($F98F) to start the timeout to turn off the d r i v e m o t o r . Use v a l u e from S A V S P ($49) to reset the stack p o i n t e r . JMP to TOP ($F2BE) to scan job q u e u e . Turn on d i s k d r i v e m o t o r Store $A0 int.o d r i v e status, D R V S T ($20) to indicate that the d r i v e is ON but. not yet up to speed (accelerating). Set bit 2 (00000100) of D S K C N T ($1C00) to turn ON the drive m o t o r . Store $3C into a c c e l e r a t i o n t i m e r , A C L T I M ($48) to cause drive status to be set to u p - t o - s p e e d a f t e r 1.5 s e c o n d s . (60 i n t e r r u p t s at .025 seconds each) Turn off disk drive m o t o r Load .X w i t h c u r r e n t d r i v e # (0) Set. bit 4 (00010000) of the drive status D R V S T ($20) to indicate D R I V E IS O F F !
TURNON
$F97E
$F982 $F98A
TRNOFF
$F98F $F991
421
NAME
ADDRESS
$F997
END
$F99C
$F9A5
$F9B1
$F9CB
$F9CF
END3 3X END10
$F9D6 $F9D9
END20
$F9E4
422
NAME
ADDRESS
$F9E8
$F9F0 $F9F4
END3 0
$F9FA
INAa
$FA05
INAC10
$FAOE
$FA12
INAC2 0
$FAlC
DOSTEP
$FA2E
STPOUT
$FA32
423
NAME SHORT
ADDRESS
$FA3B
SETLE
$FA4E
STPIN STP
$FA63
$FA69
in out
SSACL
$FA7B
$FA88
SSA10 SSRUN
$FA94 $FA97
A c c e l e r a t e head r o u t i n e . Set. c a r r y flag, load the 6522 T i m e r l hi latch T l H L 2 ($lC07), s u b t r a c t the v a l u e in AF ($5F; a c c e l e r a t i o n f a c t o r ) , and store the r e s u l t in TlHC2 ($lC'05? timerl hi c o u n t e r ) . D e c r e m e n t the n u m b e r of a c c e l e r a t i o n st.eps left in A C L S T P ($60) and if any steps left, b r a n c h to S S A 1 0 . No steps left, so reset the n u m b e r of a c c e l e r a t i o n steps left A C L S T P ($60) using the v a l u e in AS ($5E) and set the N X T S T p o i n t e r ($62/3) to point to t.he fast s t e p p i n g r o u t i n e , SSRUN ($FA97). JMP to D O S T E P ($FA2E) Fast stepping mode r o u t i n e . D e c r e m e n t n u m b e r of steps left to do in R S T E P S ($61) . If any left, b r a n c h t.o D O S T E P ($FA2E). Since none left, set the N X T S T p o i n t e r ($62/3) to point t.o the
424
NAME
ADDRESS
SSDEC
$FAA5
END3 3
$FABE
D e c e l e r a t e head r o u t i n e . Load .A from the 6522 T i m e r l hi latch T l H L 2 ($lC07), clear the carry flag, add the a c c e l e r a t i o n factor AF ($5F), and store the result in TlHC2 ($lC05; timerl hi c o u n t e r ) . D e c r e m e n t the n u m b e r of d e c e l e r a t i o n steps left A C L S T P ($60) and if any steps left., b r a n c h t.o S S A 1 0 . Since no st.eps left, set the N X T S T p o i n t e r ($62/3) to point to the settle r o u t i n e , SETLE ($FA4E) . Set. the n u m b e r of a c c e l e r a t i o n steps left to $03 to a l l o w settling time. T e r m i n a t e the motor and st.epper control r o u t i n e by c l e a r i n g bit 1 of the 6522's p e r i p h e r a l control r e g i s t e r , P C R 2 ( $ l C 0 C ) This force C A 2 low w h i c h d i s a b l e s the SO line to the 6502. F i n a l l y , do an RTS t.o t r a n s f e r control b a c k to t.he main IRQ r o u t i n e at $FE7C. This r o u t i n e is used to format (NEW) a d i s k e t t e . The code is e x e c u t e d in p l a c e (rather than moved int.o RAM and then executed as in the 4040). The IP F O R M A T routine ($C8C6) sets up a JMP $FAC7 at. t.he start of b u f f e r #0, put.s an E X E C U T E ($E0) job int.o t.he job q u e u e at $03, and then w a i t s for the job to be c o m p l e t e d . Load .A from FTNUM ($51) to check if f o r m a t t i n g has b e g u n . If F T N U M > 0 , t.he f o r m a t t i n g has b e g u n so b r a n c h t.o L213 ($FAF5). If not, b e g i n f o r m a t t i n g by: S e t t i n g D R V S T ( $ 2 0 ) to $60 (head is now s t e p p i n g ) , storing $01 into D R V T R K ($22) to set the c u r r e n t track and int.o FTNUM ($51? format b e g u n f l a g ) . Do B U M P to track 1 by stepping head out 46 t r a c k s . Store -92 (256-2*46) int.o STEPS ($4A) and c l e a r bit.s 0 & 1 of D S K C N T ($1C00) to set. head phase t.o 00. Set. CNT ($0620) to $0A t.o a l l o w up to 10 e r r o r s b e f o r e a b o r t . Set N U M ( $ 0 6 2 1 / 2 ) to 4000 ($0FA0) as a first, g u e s s at n u m b e r of b y t e s that, can be r e c o r d e d on half a track. Exit w i t h a JMP t.o END ($F99C)
FORMT
$FAC7
FORMT
$FAC7
$FAD7
$FAE3 $FAE8
425
NAME
L213
ADDRESS
$FAF5
L214
$FB00
TOPP
1111111100110011001100110011001111111
SYNC 4000 n o n - s y n c b y t e s SYNC The f o l l o w i n g r o u t i n e s time the SYNC and n o n - s y n c segment.s to d e t e r m i n e h o w many c h a r a c t e r s can be w r i t t e n on t.he t r a c k . This is used to c a l c u l a t e the length of the gap b e t w e e n s e c t o r s (int.er-sector). $FBlA $FBlD $FB20 JSR to KILL ($FE00) to kill w r i t e m o d e . JSR t.o SYNC ($F556) to wait. for the start of the SYNC s e c t i o n . Set. bit 6 of the 6522's ACR1 ($180B) t.o set it up as a free r u n n i n g 100 m i c r o second t.imer. Set .X and .Y to $00. They will hold the t.imer c o u n t . .X=least s i g n i f i c a n t byte .Y=most s i g n i f i c a n t bit. Loop t.o wait. for SYNC area Loop to w a i t for n o t - s y n c area Reset interrupt flags t.o start, the t.imer Loop to t.ime the n o n - s y n c a r e a . Check if SYNC h e r e yet.. If h e r e , b r a n c h to F005 ($FB5C). If no SYNC yet, check IFR1 ($1804) to see if timer has timed out.. If time not up yet, b r a n c h back t.o F001 ($FB46). If t.ime is up, increment .X by 1 (and .Y if .X=0) and b r a n c h back to F000 ($FB43) t.o reset, the t i m e r . If .Y is 0, we have a count of 65535 w h i c h
426
NAME
ADDRESS
F005
$FB5C
F006 F007
$FB64 $FB67
F009
$FB7D
COUNT
$FBB6
CNT10
$FBBB
CNT20 DS0 8
$FBCE $FBE0
MAK10
$FC36 $FC3F
427
NAME
ADDRESS
$FC3F $FC4 4
$FC4C
$FC7A $FC84
CRTDAT
$FC8 6 $FC8E
$FC9 5
$FC9E
L o o p to put. 255 d u m m y dat.a b y t e s ($01's) into d a t a b u f f e r #2 ($0500+) Set. the b u f f e r p o i n t e r B U F P N T ($30/1) to point t.o the h e a d e r b l o c k images ($0300) and JSR to F B T O G ( $ F E 3 0) to c o n v e r t the h e a d e r images to a GCR w r i t e image wit.h no h e a d e r b l o c k ID c o d e . Pull # of s e c t o r s from stack, t r a n s f e r the v a l u e to .Y, and JSR t.o M O V U P ( $ F D E 5 ) to move the GCR h e a d e r image stored in in b u f f e r #0 69 b y t e s up in m e m o r y . Then JSR t.o M O V O V R ($FDF5) to move the 69 h e a d e r image b y t e s from t.he o v e r f l o w b u f f e r into the low end of b u f f e r #0. Set the b u f f e r p o i n t e r B U F P N T ($30/1) to point t.o the d u m m y data b l o c k , JSR to C H K B L K ( $ F 5 E 9 ) to c a l c u l a t e the data blk c h e c k s u m , store it in C H K S U M , and JSR to B I N G C R ( $ F 7 8 F ) to c o n v e r t the d u m m y data b l o c k int.o its GCR w r i t e image.
428
NAME
ADDRESS
$FCAA
WRTSYN
$FCAE $FCB1
WRTS10
$FCB8 $FCBE
WRTS20
$FCC2 $FCCF
WRTS3 0
$FCD1 $FCDC
DBSYNC
$FCE0 $FCE9
WRTS40 WRTS5 0
WGP2
$FD09 $FD12
$FD19
Set the p o i n t e r to the h e a d e r GCR image H D R P N T ($32) to $00 so it p o i n t s to the start of the first h e a d e r image. JSR to C L E A R ($FE0E) to w i p e the t r a c k . Store $FF in PORT2 ($lC01) to be ready to w r i t e a sync c h a r a c t e r . Load .X w i t h $05 (5 S Y N C ' s c o m i n g up!) W r i t e out 5 sync m a r k s I n i t i a l i z e .X to $0A (output 10 b y t e s ) and set .Y w i t h the v a l u e from H D R P N T ($32) so it p o i n t s to the start of the h e a d e r G C R image. W r i t e out the 10 h e a d e r c h a r a c t e r s Load .X w i t h $08 (HARD SET V A L U E ! ) N O T E : This m e a n s you can not easily c h a n g e the h e a d e r gap size! L o o p to output eight $55 b y t e s to form the h e a d e r gap (gapl). S t o r e $FF in PORT2 ($lC01) to be ready to w r i t e a sync m a r k . Load .X w i t h $05 (5 S Y N C 1 s c o m i n g up!) W r i t e out 5 sync marks I n i t i a l i z e .X to $BB to point to the first b y t e of the o v e r f l o w b u f f e r (the start of the d u m m y dat.a b l o c k ) L o o p to w r i t e out the 69 GCR b y t e s in the o v e r f l o w b u f f e r L o o p to w r i t e out the 256 G C R b y t e s in data b u f f e r #2 ($0500+) Load .A w i t h $55 and .X wit.h the tail (inter-sector) gap from DTRCK ($0626) L o o p to w r i t e .X $55 c h a r a c t e r s to form the tail (inter-sect.or) g a p . A d v a n c e t.he h e a d e r p o i n t e r H D R P N T ( $ 3 2 / 3 ) by 10 so it. p o i n t s to the st.art. of the next. h e a d e r image. D e c r e m e n t the sector count.er S E C T ( $ 0 6 2 8 ) by l and t.est. t.o see if any more sectors t.o do. If more, b r a n c h back t.o W R T S Y N t.o do t.he next. s e c t o r . If no more, wait, for the last byt.e t.o be w r i t t e n out. and then JSR to KILL ($FE00) t.o switch t.o read m o d e . Formatting done. Verify it!
Set TRYS ($0623) t.o $C8 t.o limit, t.he n u m b e r of a t t e m p t s t.o v e r i f y to 200. Set B U F P N T ($30/1) to point, t.o the start of t.he h e a d e r s in b u f f e r #0 ($0300) and set. SECT ($0628) wit.h the # of sectors on this track from SECTR ($43) .
429
NAME
CMPR10
ADDRESS
$FD3 9
CMPR15
$FD40
TSTDAT
$FD6 2
TST05
$FD67
$FD7 5
TST10
$FD7 7
$FD86
FMTEND
$FD9 6
Subroutines
W i p e track by w r i t i n g 40*256 SYNC marks Set. bit.s 6 & 7 of t.he 6522's p e r i p h e r a l control r e g i s t e r PCR2 ($1C0C). This latches the signal on the CB2 line.
430
NAME
ADDRESS
$FDAD
WRTNUM
$FDC3 WRTN10 $FDC9
$FDD2
FMTERR
$FDD3
FMTE10
$FDDB
MOVOVR
$FDF5 $FDF7
KILL $FE0 0
$FE2 2
431
NAME CLER10
ADDRESS
$FE2 6 $FE2F
FBTOG
$FE30 $FE30
$FE38
$FE3C
FBG10
$FE44
IRQ R O U T I N E
(IRQ V E C T O R
POINTS
HERE)
3 are g e n e r a t e d in two w a y s : IRQ 1 ' 1) by an ATN signal from the V I C - 2 0 or the C - 6 4 on the serial bus, or by a time out of the 6522's timer 2) This h a p p e n s every 10 m i l l i s e c o n d s This r o u t i n e tests for the source of the IRQ signal and b r a n c h e s to the c o r r e c t ROM r o u t i n e . Save .A, .X, and .Y on the stack Test if IRQ caused by an ATN signal on the serial bus by c h e c k i n g bit. 1 of the interrupt, flag r e g i s t e r of t.he 6522 that h a n d l e s the bus IFR1 ($180D). If this bit. is not set (1) , there was no ATN signal so b r a n c h to IRQ10 ($FE76). If it is set, JMP to the bus h a n d l i n g r o u t i n e ATNIRQ ($E85F). Test if the 6522 timer has timed out by testing bit. 7 of t.he interrupt flag r e g i s t e r of the 6522 that serves as a disk c o n t r o l l e r IFR2 ($1C0D). If t.he bit. is not set., b r a n c h to IRQ20 ($FE7F) . If it. is set., do a JSR to the floppy disk controller routines, LCC($F2B0). Pull .A, .X, and .Y from the stack and do an RTI.
$FE67 $FE6C
IRQ10
$FE76
IRQ20
$FE7F
432
ADDRESS
$FE8 5 $FE86 $FE87 $FE88
VALUE
$12 $04 $04 $90
Search
$FE89 $FE8A $FE8B $FE8C $FE8D $FE8E $FE8F $FE90 $FE91 $FE92 $FE93 $FE94
$56 $49 $44 $4D $42 $55 $50 $26 $43 $52 $53 $4E
V I D M B U P & C R S N
= = = = = = = = = = = =
V a l i d a t e or c o l l e c t disk I n i t i a l i z e BAM & d i r e c t o r y D u p l i c a t e or b a c k u p disk (N.A.) M e m o r y o p e r a t i o n (M-R,M-W,M-E) Block o p e r a t i o n (B-R,B-A,B-W,et.c) User jump c o m m a n d s (except U + & U-) P o s i t i o n (for REL files) U t i l i t y loader Copy file (copy disk N . A . on 1541) R e n a m e file S c r a t c h file N e w or format a d i s k e t t e Command Jump V I D M B U P & C R S N = = = = = = = = = = = = Table
(Lo Byte) $FE95 $FE96 $FE97 $FE98 $FE99 $FE9A $FE9B $FE9C $FE9D $FE9E $FE9F $FEA0 $84 $05 $C1 $F8 $lB $5C $07 $A3 $F0 $88 $23 $0D
(Hi Byte) $FEA1 $FEA2 $FEA3 $FEA4 $FEA5 $FEA6 $FEA7 $FEA8 $FEA9 $FEAA $FEAB $FEAC STRUCTURE $ED $D0 $C8 $CA $CC $CB $E2 $E7 $C8 $CA $C8 $EE
Validate Initialize BAM D u p l i c a t e (N.A.) Memory operation Block o p e r a t i o n User jump c o m m a n d s P o s i t i o n (for REL) U t i l i t y loader Copy file R e n a m e file S c r a t c h file New a diskette
I M A G E S FOR C O M M A N D S d i s k copy r e n a m e a file (not parsed) scratch a file (not parsed) new a d i s k e t t e (not parsed) load a file Not g r e a t e r than one Not d e f a u l t drive(s) Required filename (R/W/A/M) file
MODE T A B L E $FEB2 $FEB3 $FEB4 $FEB5 $52 $57 $41 $4D R W A M = = = = Read mode W r i t e mode Append M o d i f y (read
i m p r o p e r l y closed
file)
433
ADDRESS
VALUE
;ist. Byte) $FEB6 $FEB7 $FEB8 $FEB9 $FEBA $FECA $FECB $44 $53 $50 $55 $4C D S P U L $08 $00
$FEBB $44 D $FEBC $53 S $FEBD $50 P $FEBE $55 U $FEBF $52 R
ERROR FLAG V A R I A B L E S FOR USE BY $FECC $FECD $FECE $FECF $FED0 $00 $3F $7F $BF $FF ER00 ER0 ER1 ER2 ER3 IN EACH in in in in zone 4 zone 3 zone 2 zone 1
N U M B E R OF S E C T O R S / T R A C K $FED1 $FED2 $FED3 $FED4 $FED5 $FED6 $11 $12 $13 $15 $41 $04 17 18 19 21 sectors/track sectors/track sectors/track sectors/track
DOS v e r s i o n n u m b e r (65) N u m b e r of d i f f e r e n t zones (HIGHEST TRACK end end end end of of of of zone 4 zone 3 zone 2 zone 1 # + 1) (31- 35) (25- 30) (18- 24) (01- 17)
ZONE B O U N D A R I E S $FED7 $FED8 $FED9 $FEDA $24 $lF $19 $12 Track Track Track Track OFFSETS $FEDB $FEDC $FEDD $FEDE $FEDF $01 $FF $FF $01 $00 #36 #31 #25 #18
FOR E R R O R
RECOVERY
HI BYTE OF P O I N T E R S TO DATA $FEE0 $FEE1 $FEE2 $FEE3 $FEE4 $FEE5 $FEE6 | | $03 $04 $05 $06 $07 $07 $FD Data Dat.a Data Data Data Data buffer buffer buffer buffer buffer buffer #0 #1 #2 #3 #4 #5
BUFFERS
| Checksum
for $E and
434
ADDRESS
VALUE
$FEE7
NMI
Do indirect jump to t.he address stored in VNMI ($0065). This vector points to XXXXXX ($XXXX) PATCH FOR POWER-ON ERRORS
$FEEA
PEA7A
Store the value that is in .A on entry into the 6522's data port 2, LEDPRT ($1C00; also called DSKCNT) and in t.he data direction register, LEDOUT ($lC02; also called DDRB2). Exit with a JMP to REA7D ($EA7D) t.o return to the LED blink routine. RECEIVE
Produce a 40 microseconds delay with a loop that counts .X down from 5 t.o 1. Exit with an RTS. JSR $E9AE JMP $E99C unused unused junk junk
$FEFB $FEFE
PATCH TO NMI ROUTINE TO CHECK FOR U + AND U- C O M M A N D S $FF01 NNMI Load .A with the second character in the command buffer CMDBUF+2 ($0202). Compare it with "-" and, if equal, branch to N N M I 1 0 ($FF0D). If not a "-", subtract a " + " from it. If not zero, command mustbe a real UI command so branch back to NMI ($FEE7) to do normal N M I . Store .A (contains zero or a "-") into DRVTRK+1 ($23) and do an RTS to continue UNUSED GARBAGE (LO BYTE/HI $C8C6 $F98F $CD5F $CD97 $0500 $0503 $0506 $0509 $050C $050F $FF01 $EAA0 $FE67 BYTE)
$FFOD
NNMI10
$FF10 - $FFE6
TABLE OF JUMP VECTORS TO ROUTINES $FFE6 $FFE8 $FFEA $FFEC $FFEE $FFF0 $FFF2 $FFF4 $FFF6 $FFF8 $FFFA $FFFC $FFFE $C6/$C8 $8F/$F9 $5F/$CD $97/$CD $00/$05 $03/$05 $06/$05 $09/$05 $0C/$05 $0F/$05 $01/$FF $A0/$EA $67/$FE F O R M A T ROM routine TRNOFF ROM routine UBLKRD ROM routine UBLKWT ROM routine Link to buffer #2 Link to buffer #2 Link to buffer #2 Link to buffer #2 Link to buffer #2 Link to buffer #2 NNMI ROM routine DSKINT ROM routine SYSIRQ ROM routine
435
PROGRAM LISTINGS
APPENDIX C
437
NOTE: Lines 830 and 930 contain a special character #166. This character can be typed by holding down the Commodore logo key in the lower left corner and pressing the + key.
100 REM DISPLAY A BLOCK AVAILABILITY MAP - 1541 110 DIMN*<16> 120 DEFFNS ( I ) =2' x < S - I N T ( S / 8 ) * 8 ) AND<B<INT ( S/8))) 130 PRINT" {CLR3-DISPLAY A BAM - 1541" 140 PRINT"tDOWN>INSERT DISKETTE IN DRIVE 150 PRINT"{DOWN>PRESS <RVS>RETURN<ROFF> TO CONTINUE" 160 G E T C * : I F C * = " " T H E N 1 6 0 170 IFC*< >CHR*(13)GOTO160 180 PRINT"OK" 190 0 P E N 1 5 , 8 , 1 5 200 P R I N T # 1 5 , " 1 0 " 210 I N P U T # 1 5 , E N * , E M * , E T * , E S * 220 IFEN*="00"OREN*="22"OREN*="23"G0T026 0 230 PRINT"<DOWN>"EN*", "EM*","ET*","ES* 240 CL0SE15 250 END 260 O P E N 2 , 8 , 2 , " # " 270 P R I N T # 1 5 , " U l " ; 2 ; 0 ; 1 8 j 0 280 I N P U T # 1 5 , E N * , E M * , E T * , E S * 290 REM GET DOS 300 P R I N T # 1 5 , " B - P " ; 2 ; 2 310 G E T # 2 , B * 320 I F B * = " " T H E N B * = C H R * ( 0 ) 330 DOS=ASC<B*) 340 IFD0S=65THEND0S*="V2.6":G0T0380 350 IFDOS=1THENDOS*="V1.2":G0T0380 360 D O S * = " V ? . ? " 370 REM GET BLOCKS FREE 380 BF=O 390 B=4 400 F 0 R I = l T 0 3 5 410 I F I = 1 8 T H E N I = I + l : B = B + 4 420 P R I N T # 1 5 , " B - P " ; 2 ; B 430 G E T # 2 , B * 440 IFB*=""THENB*=CHR*(0) 450 A=ASC<B*) 460 BF=BF+A 470 B=B+4 480 NEXTI 490 REM GET DISK NAME
439
500 P R I N T # 1 5 , " B - P " ; 2 ; 1 4 4 510 F 0 R I = l T 0 1 6 520 G0SUB1140 530 N$<I>=CHR*<A> 540 NEXTI 550 REM GET COSMETIC ID 560 I D S = " " 570 P R I N T # 1 5 , " B - P " ; 2 ; 1 6 2 580 F 0 R I = l T 0 2 590 G0SUB1140 600 ID*=ID*+CHR*(A> 610 NEXTI 620 PRINT"CCLR3 CRVS>TRACKCROFF> 11 111111112222222222333333" 630 PRINT" 123456789012345678901234567 89012345" 640 PRINT"CRVS>S<R0FF>0 "N*<1>; 650 PRINT" {RVS>E<ROFF3-1 "N*<2); 660 PRINT"{RVS>C{R0FF>2 "N*<3>; 670 PRINT"{RVS>T{R0FF>3 "N$<4); 680 PRINT"{RVS>CKR0FF>4 "N*(5>; 690 PRINT"tRVS>R<R0FF>5 "N$<6); 700 PRINT" 6 "N$(7>; 710 PRINT" 7 "N$<8>; 720 PRINT" 8 "N$<9>; 730 PRINT" 9 "N*(10); 740 PRINT"10 "N*(11); 750 PRINT"11 "N$(12>; 760 PRINT"12 "N$<13); 770 PRINT"13 "N$(14>; 780 PRINT"14 "N*<15); 790 PRINT"15 "N$<16>; 800 P R I N T " 1 6 " 810 P R I N T " 1 7 "
440
820
PRINT"18 ";D0S$;" ";LEFT$(ID*,1); 830 PRINT"19 <R VS> CROFF>OR<!# 166> =EMPTY " ; R I G H T $ < I D * , 1 > 840 PRINT"20 "; 850 BF*=RIGHT*<" "+RIGHT*<STR*<BF>,LEN< STR*<BF>)-l>,3> 860 IFBF=1THENPRINT" " ? B F * ; " BLOCK FREE" :G0T0880 870 P R I N T B F * ; " BLOCKS FREE" 880 A * = " . " 890 CR*="{RIGHT 35>" 900 P R I N T # 1 5 , " B - P " ; 2 ; 4 910 F 0 R T = l T 0 3 5 920 I F T / 2 < > I N T ( T / 2 ) T H E N F $ = " { R V S > CROFF>" :G0T0940 930 F $ = " { # 1 6 6 > " 940 G E T # 2 , B * 950 F 0 R I = 0 T 0 2 960 G E T # 2 , B * 970 IFB$=""THENB*=CHR*(0) 980 B < I ) = A S C ( B $ ) 990 NEXTI lOOO PRINT"CHOME>CDOWN 2><RIGHT 2 > " ; L E F T *<CR$,T); 1010 N S = 2 0 + 2 * ( T > 1 7 ) + ( T > 2 4 ) + ( T > 3 0 ) 1020 FORS=OTONS 1030 IFFNS(S)=OTHENPRINTA*;:GOTO1050 1040 P R I N T F * ; 1050 PRINT"{DOWN> <LEFT>"; 1060 NEXTS 1070 NEXTT 1080 PRINT"{HOME> {DOWN 2 2 > " ; 1090 CLOSE2 llOO INPUT#15,EN*,EM*,ET$,ES* 1110 CLOSE15 1120 END 1130 REM GET A BYTE 1140 G E T # 2 , B * 1150 IFB*=""THENB*=CHR* < O) 1160 A=ASC(B*> 1170 IFA >127THENA=A-128 1180 IFA<320RA>95THENA=63 1190 IFA=34THENA=63 1200 RETURN
n
441
1541
160 PRINT"CCLR>VIRTUAL
DIRECTORY -
1541"
170 PRINT"xDOWN>INSERT DISKETTE IN DRIVE I I 180 PRINT" CDOWN>PRESS <RVS>RETURN<.ROFF> TO CONTINUE" 190 GETC$:IFC$=""THEN190 200 IFC*< >CHR* <13 > GOTO190 210 PRINT"OK" 220 O P E N 1 5 , 8 , 1 5 230 P R I N T # 1 5 , " I 0 " 240 I N P U T # 1 5 , E N * , E M * , E T * , E S $ 250 IFENS="OO"G0T0300 260 PRINT"CDOWN>"EN$", "EM*","ET$","ES$ 270 CLOSE15 280 END 290 REM FORMATTING ID 300 PRINT#15,"M-R"CHR*<22>CHR*<0>CHR*<2> 310 320 330 340 350 360 370 380 ) 390 400 410 420 430 440 450 460 470 480 490 500 510 520 530 540 GET#15.B* GOSUB1370 FI*=FI$+CHR$(A> GET#15,B* GOSUB1370 FI*=FI*+CHR*<A> REM BLOCKS FREE PRINT#15,"M-R"CHR$ <250)CHR* < 2 ) C H R * ( 3 GET#15.B* L=ASC(B*+CHR*(0)) GET#15,B* GET#15,B* H=ASC(B*+CHR*(0)) BF=L+(H*256) BA=664-BF 0PEN4,3 0PEN2,8,2,"#" 0PEN3,8,3,"$0,P,R" GET#3,B* DOS=ASC(B*+CHR*(0)) FORI=3TO143 GET#3,B$ NEXTI FORI=144T0159
443
550 GOSUB1360 560 DN*=DN*+CHR*<A) 570 NEXTI 580 GET#3,B* 590 GET#3,B$ 600 F 0 R I = 1 6 2 T 0 1 6 3 610 G0SUB1360 620 ID*=ID*+CHR*<A) 630 NEXTI 640 FORI=164T0255 650 G E T # 3 , B * 660 NEXTI 670 F 0 R I = l T 0 6 680 PRINT#4 690 NEXTI "DN* 700 PRINT#4, "DISK NAME: "ID* 710 PRINT#4, "DISK I D : "FI* 720 PRINT#4, "FORMATTING I D : "DOS 730 PRINT#4, "DOS TYPE: 740 PRINT#4, "BLOCKS ALLOCATED: " BA "BF 750 PRINT#4, "BLOCKS FREE: 760 PRINT#4 770 PRINT#4,"BLOCKS F I L E NAME TYP E TS LOAD" 780 I F F / 8 = I N T ( F / 8 > THENPRINT#4 790 G E T # 3 , B * 800 FT=ASC(B*+CHR* < O ) ) 810 FT*=FT*<7ANDFT) 820 G E T # 3 , B * 830 T=ASC<B*+CHR*<0)) 840 T * = R I G H T * ( " 0 " + R I G H T * <STR$(T) ,LEN(STR *<T))-l),2) 850 G E T # 3 , B * 860 S=ASC<B*+CHR* <0)> 870 S * = R I G H T * ( " 0 " + R I G H T * ( S T R * ( S ) , L E N ( S T R *<S))-l),2) 880 L A * = " " 890 IF(7ANDFT)<>OAND < 7ANDFT)<>2G0T01020 900 P R i N T # i 5 , " U l " ; 2 ; o ; T ; s 910 P R I N T # 1 5 , " B P " ; 2 ; 2 920 G E T # 2 , B * 930 A=ASC<B*+CHR*<0)) 940 H = I N T ( A / 1 6 ) 950 L = A - 1 6 * H 960 L A * = M I D * ( H * , H + 1 , 1 ) + M I D * < H * , L + 1 , 1 ) 970 G E T # 2 , B * 980 A=ASC(B*+CHR*(0)> 990 H = I N T ( A / 1 6 ) 1000 L = A - 1 6 * H
lOlO
LA*=MID*<H*,H+1,l)+MID*<H*,L+l,l)+L
444
A* 1020 F * = " " 1030 NULL=0 1040 F 0 R I = l T 0 1 6 1050 GOSUB1360 1060 IFB*=CHR*(0)THENNULL=NULL+1 1070 F*=F*+CHR*<A> 1080 NEXTI 1090 IFNULL=16G0T01270 1100 F 0 R I = l T 0 9 1110 G E T # 3 , B * 1120 NEXTI 1130 G E T # 3 , B * 1140 B = A S C ( B * + C H R * ( 0 ) ) 1150 G E T # 3 , B * 1160 B=B+256*ASC(B$+CHR*(0)) 1170 B*=RIGHT*<" "+RIGHT*<STR*(B>,LEN<S TR*<B>)-1>,3) 1180 IFST=64THENEOI=1 1190 I F F T < 1 2 8 T H E N P R I N T # 4 , " t R V S > " ; 1200 P R I N T # 4 , " " B * " "F*" "FT*" " T * " - " S$" "LA$ 1210 F=F+1 1220 I F F / 8 < > I N T ( F / 8 > THENGET#3,B*:GET#3,B * 1230 1240 1250 1260 1270 1280 1290 1300 1310 1320 1330 1340 1350 1360 1370 1380 1390 1400 1410 1420 GETC*:IFC*=""G0T0125C> GETC*:IFC*=""THEN1240 IFEOI=1GOTO1270 G0T0780 CL0SE4 CLOSE3 CL0SE2 INPUT#15,EN*,EM*,ET*,ES$ CLOSE15 END REM F I L E TYPES DATA D E L , S E Q , P R G , U S R , R E L , ? ? ? REM GET A BYTE GET#3,B* IFB*=""THENB*=CHR$ < O) A=ASC<B*> IFA >127THENA=A-128 IFA<320RA >95THENA=63 IFA=34THENA=63 RETURN
445
100 REM FIND A F I L E 110 PRINT"<CLRJFIND A F I L E - 1541" 120 PRINT"<D0WN>INSERT DISKETTE IN DRIVE I 130 PRINT"CDOWN>PRESS <RVS>RETURN<ROFF> TO CONTINUE" 140 G E T C * : I F C * = " " T H E N 1 4 0 150 IFC*< >CHR*(13 > GOTO140 160 PRINT"OK" 170 O P E N 1 5 , 8 , 1 5 180 P R I N T # 1 5 , " I 0 " 190 I N P U T # 1 5 , E N * , E M * , E T * , E S * 200 I F E N * = " 0 0 " G 0 T 0 2 4 0 210 PRINT"tDOWN>"EN*", "EM*","ET*","ES* 220 CL0SE15 230 END 240 INPUT"{DOWN>FILENAME";F* 250 I F L E N ( F * ) < >OANDLEN< F * ) < 1 7 G 0 T 0 2 8 0 260 CL0SE15 270 END 280 0 P E N 2 , 8 , 2 . " 0 : " + F * + " , ? , R " 290 I N P U T # 1 5 , E N * , E M * , E T * , E S * 300 IFEN*="00"G0T0320 310 G0T0530 320 P R I N T # 1 5 , " M - R " C H R * ( 9 7 ) C H R * ( 2 ) 330 G E T # 1 5 , D * 340 D=ASC(D*+CHR*(0 >) 350 P R I N T # 1 5 , " M - R " C H R * ( 2 4 ) C H R * ( 0 > C H R * < 2 > 360 370 380 390 400 410 420 430 440 450 460 470 480 490 500 510 520 530 540 550 560 570 580 GET#15,T* T=ASC(T*+CHR*(0)) GET#15,S* S=ASC(S*+CHR*(0)> D*=RIGHT*(STR*(D),LEN(STR*(D))-1) IFD<1OTHEND*="O"+D* T*=RIGHT*(STR*(T),LEN(STR*<T))-1) IFT<1OTHENT*="0"+T* S*=RIGHT*(STR*(S),LEN(STR*(S))1) IFS<1OTHENS*="O"+S* PRINT"CDOWN3TRACK 18 - SECTOR " D * PRINT"<DOWN>TRACK " T * " - SECTOR " S * CLOSE2 INPUT#15,EN*,EM*,ET*,ES* CL0SE15 PRINT"CDOWN>DONE" END PRINT"{DOWN>"EN*", "EM*","ET*","ES* CLOSE2 INPUT#15,EN*,EM*,ET*,ES* CL0SE15 PRINT"vDOWN>CRVS>FAILED<ROFF>" END
447
100 REM DISPLAY TRACK & SECTOR - 1541 110 CLR 120 HD$="0123456789ABCDEF" 130 PRINT"<CLRJ DISPLAY TRACK & SECTOR 1541" 140 PRINT"{DOWN>INSERT DISKETTE IN DRIVE
l l
150 INPUT"{DOWN>DISPLAY TRACK & SECTOR ( T,S) " ; T , S 160 IFT<1ORT >35THENEND 170 N S = 2 0 + 2 * < T > 1 7 ) + ( T > 2 4 ) + ( T > 3 0 > 180 IFS< OORS >NSTHENEND 190 INPUT"tDOWN>OUTPUT TO SCREEN OR PRIN TER ( S / P ) S{LEFT 3 I " ; 0 * 200 I F 0 * < >"S"ANDO*< >"P"THENEND 210 INPUT"tDOWN>ARE YOU SURE YCLEFT 3>" ;Q* 220 IFQ*< >"Y"THENEND 230 0 P E N 1 5 , 8 , 1 5 240 T * = R I G H T * < S T R $ ( T ) , L E N < S T R * ( T ) ) - 1 ) 250 IFT<1OTHENT*="O"+T* 260 S*=RIGHT$<STR*<S>,LEN<STR*<S>>-1> 270 IFS<1OTHENS$="0"+S* 280 REM SEEK 290 J0B=176 300 G0SUB850 310 IFE< >1G0T0360 320 REM READ 330 J0B=128 340 G0SUB850 350 IFE=1G0T0470 360 I F E >1ANDE<12THENEN$=RIGHT*(STR$ < E+18 ),2):G0T0380 370 E N * = " 0 2 " : E M * = " ? T I M E O U T " : G 0 T 0 3 9 0 380 EM*="READ ERROR" 390 E T * = T * 400 E S * = S * 410 PRINT"CDOWN>"EN$", "EM*","ET$","ES$ 420 IFE<>4ANDE<>5G0T0450 430 GOSUB1020 440 G0T0470 450 CL0SE15 460 END 470 I F 0 4 = " S " G 0 T 0 5 5 0 480 0PEN4,4 490 F 0 R I = l T 0 6 500 PRINT#4 510 NEXTI 520 P R I N T # 4 , " DISPLAY TRACK & SE CTOR"
448
SECT TRACK "T$ 530 P R I N T # 4 , " 0R " S * 540 PRINT#4 550 F0RK=0T01 DISPLAY TRA 560 PRINT"xCLR> <RVS> {ROFF> CK & SECTOR TRA 570 PRINT"CHOME> xDOWN> xRVS> <ROFF>" CK "T$" - SECTOR " S * " 580 PRINT"CHOME> {DOWN 2>" 590 FORJ=OTOi5 600 D=K*128+J*8 610 G0SUB970 620 BP*=" - "+DH$+": " 630 H * = " " 640 A * = " " 650 F 0 R I = 0 T 0 7 660 PRINT#15,"MR"CHR*(K*128+J * 8 + 1 ) C H R * < 4) 670 G E T # 1 5 , B * 680 D=ASC<B*+CHR*<0>> 690 G0SUB970 700 H*=H*+DH*+" " 710 IFD>127THEND=D-128 720 IFD<320RD >90THEND=46 730 A*=A*+CHR*<D> 740 NEXTI 750 P R I N T B P * ; H * ; A * 760 I F O * = " P " T H E N P R I N T # 4 , B P * ; H * ; A * 770 NEXTJ 780 I F 0 * = " P " G 0 T 0 8 0 0 790 G0SUB1020 800 NEXTK 810 IFO*="P"THENCLOSE4 820 CLOSE15 830 GOTO110 840 REM JOB QUEUE 850 TRY=0 860 PRINT#15"M-W"CHR* < 8 ) C H R * ( O ) C H R * ( 2 ) C HR*<T)CHR*(S) 870 P R I N T # 1 5 , " M - W " C H R * ( 1 ) C H R * ( 0 ) C H R * ( 1 ) C HR*(JOB) 880 TRY=TRY+1 890 PRINT#15,"M-R"CHR*<1)CHR*<0> 900 G E T # 1 5 , E * 910 I F E * = " " T H E N E * = C H R * ( 0 ) 920 E=ASC<E*> 930 IFTRY=500G0T0950 940 I F E >127G0T0880 950 RETURN 960 REM DECIMAL TO HEXADECIMAL
449
970 H = I N T ( D / 1 6 ) + 1 980 L = D - < H - 1 > * 1 6 + 1 990 D H $ = M I D * ( H D $ , H , 1 ) + M I D $ ( H D $ , L , 1 ) 1000 RETURN 1010 REM DELAY 1020 PRINT"{DOWN>PRESS <RVS>RETURNCROFF> TO CONTINUE" 1030 GETC*:IFC$=""THEN1030 1040 IFC$<>CHR$(13)GOTO1030 1050 PRINT"OK" 1060 RETURN
450
REM DISPLAY A CHAIN - 1541 CLR PRINT"CCLR>DISPLAY A CHAIN - 1541" PRINT"CDOWN>INSERT DISKETTE IN DRIVE
140 INPUT"CDOWN>TRACK & SECTOR ( T , S ) " ; T , S 150 IFT<1ORT>35THENEND 160 NS=20+2*<T>17> + (T>24) + (T>30> 170 IFS< OORS >NSTHENEND 180 INPUT"xDOWN>OUTPUT TO SCREEN OR PRIN TER <S/P) S<LEFT 3 3 " ; 0 * 190 I F 0 * < >"S"ANDO$< >"P"THENEND 200 INPUT"CDOWN>ARE YOU SURE YCLEFT 3>" ;Q* 210 IFQ*< >"Y"THENEND 220 0 P E N 1 5 , 8 , 1 5 230 P R I N T # 1 5 , " I 0 " 240 I N P U T # 1 5 , E N * , E M * , E T * , E S * 250 IFEN$="00"G0T0290 260 PRINT"CDOWN>"EN$", "EM$","ET*"?"ES$ 270 CL0SE15 280 END 290 I F 0 $ = " S " G 0 T 0 3 9 0 300 PRINT"{DOWN>{RVS3PRINTINGCRQFF} A CH AIN" 310 0PEN4 ? 4 320 F 0 R I = l T 0 6 330 PRINT#4 340 NEXTI 350 P R I N T # 4 , " DISPLAY A CHAI N" 360 P R I N T # 4 , " BLOCK TRACK - SE CTOR" 370 PRINT#4 380 G0T0420 390 PRINT" xCLR3- xRVS3DISPLAY A CHAIN {ROFF3" 400 PRINT"{HOME3-{DOWN3 {RVS> BLOC K TRACK - SECTOR {ROFF3" 410 PRINT"CHOME3CDOWN 2>" 420 B=B+1 430 GOSUB1030 440 REM SEEK 450 J0B=176 460 G0SUB910 470 IFE< >1G0T0520 480 REM READ 490 J0B=128 500 G0SUB910
451
510 I F E = l G 0 T 0 6 3 0 520 IFE>lANDE<12THENEN*=RIGHT*<STR*(E+18 ),2):G0T0540 530 E N * = " 0 2 " : E M * = - ? T I M E O U T " : G 0 T G 5 5 0 540 EM*="READ ERROR" 550 ET*=T$ 560 E S * = S * 570 I F 0 * = " P" THENPRI NT#4, " "EN*" , "EM*","ET*","ES*:G0T0590 580 PRINT" "EN*", "EM*","ET*"," ES* 590 IFE=40RE=5G0T0630 600 I F 0 * = " P " G 0 T 0 8 1 0 610 G0SUB1090 620 GOT0820 630 B * = R I G H T * ( S T R * ( B ) , L E N ( S T R $ ( B > > 1) 640 IFB<1OTHENB*=" " + B * 650 IFB<lOOTHENB*=" " + B * 660 IFO*="P"THENPRINT#4," "B*" "T*" "S*:G0T0680 670 PRINT" "B*" " T * " - "S * 680 P R I N T # 1 5 , " M - R " C H R * < 0 ) C H R * ( 4 ) C H R * ( 2 ) 690 G E T # 1 5 , T * 700 T = A S C ( T * + C H R * ( 0 ) ) 710 IFT=0G0T0760 720 G E T # 1 5 , S * 730 S=ASC(S*+CHR* < 0 > > 740 I F T >350RS>20+2*(T>17) + (T>24) + ( T > 3 0 ) G 0T0850 750 I F 0 * = " S " A N D B / 1 6 < > I N T ( B / 1 6 > G0T0420 760 I F O * = " P " G 0 T 0 7 8 0 770 GOSUB1090 780 IFT=0G0T0810 790 I F 0 * = " S " G 0 T 0 3 9 0 800 G0T0420 810 IF0*="P"THENCL0SE4 820 CLOSE15 830 GOTO110 840 REM ILLEGAL TRACK OR SECTOR 850 GOSUB1030 860 I F 0 * = " P " T H E N P R I N T # 4 , " 66, IL LEGAL TRACK OR S E C T 0 R , " T * " , " S * : G 0 T 0 8 1 0 870 PRINT" {DOWN3-66, ILLEGAL TRACK OR SEC TOR,"T*","S* 880 GOSUB1090 890 G0T0820 900 REM JOB QUEUE 910 TRY=O 920 P R I N T # 1 5 , " M - W " C H R * < 8 ) C H R * ( 0 ) C H R * ( 2 ) C
452
HR*<T)CHR*<S> 930 P R I N T # 1 5 , " M - W " C H R * ( 1 ) C H R * ( 0 ) C H R $ ( 1 > C HR*<J0B> 940 TRY=TRY+1 950 P R I N T # 1 5 , " M - R " C H R * ( 1 ) C H R * ( 0 > 960 G E T # 1 5 , E * 970 IFE*=""THENE*=CHR*(O) 980 E=ASC(E*) 990 IFTRY=500G0T01010 lOOO I F E >127G0T0940 1010 RETURN 1020 REM STR*<T,S> 1030 T*=RIGHT* <STR* < T ) , L E N ( S T R * ( T ) ) 1 > 1040 IFT<1OTHENT*="0"+T* 1050 S * = R I G H T * ( S T R * ( S ) , L E N ( S T R * ( S ) ) - 1 ) 1060 IFS<1OTHENS*="0"+S* 1070 RETURN 1080 REM DELAY 1090 PRINT" {DOWN>F'RESS CRVS>RETURN{ROFF> TO CONTINUE" 1100 G E T C * : I F C * = " " T H E N 1 1 0 0 1110 IFC*< >CHR$(13)GOTO11OO 1120 RETURN
453
100 REM EDIT TRACK & SECTOR - 1541 110 P0KE56,159 120 CLR 130 HD*="0123456789ABCDEF" 140 CD*="CHOME>CDOWM 20>" 150 PRINT" <CLR3-EDIT A SECTOR - 1541" 160 PRINT"CDOWN>REMOVE CRVS>WRITE PROTEC T TAB{ROFF>" 170 PRINT"{DOWN>INSERT DISKETTE IN DRIVE
ii
180 INPUT"CDOWN>EDIT TRACK & SECTOR (T,S )";T,s 190 IFT<1ORT>35G0T01580 200 N S = 2 0 + 2 * ( T > 1 7 ) + < T>24) + (T>30) 210 IFS< OORS >NSGOTO1580 220 INPUT"CDOWN>STARTING BYTE < 0 0 / 8 0 > " ; S B* 230 IFLEN< SB*)=OGOTO1580 240 SB=VAL<SB*> 250 IFSB<>OANDSB<>80G0T01580 260 IFSB=0THENBP=0:G0T0230 270 BP=128 280 INPUT"{DOWN>ARE YOU SURE YxLEFT 33" ; Q* 290 IFQ*< >"Y"GOTO1580 300 0 P E N 1 5 , 8 , 1 5 310 T * = R I G H T * < S T R * < T > , L E N ( S T R * < T ) ) - 1 ) 320 IFT<1OTHENT*="0"+T* 330 S * = R I G H T * ( S T R * ( S ) ? LEN(STR* < S ) > - l ) 340 IFS<1OTHENS*="O"+S* 350 REM SEEK 360 J0B=176 370 G0SUBI620 380 IFE< >1G0T0430 390 REM READ 400 J0B=128 410 G0SUB1620 420 IFE=1G0T0520 430 IFE>lANDE<12THENEN*=RIGHT* <STR*<E+18 ),2):G0T0450 440 E N * = " 0 2 " : E M * = " ? T I M E O U T " : G 0 T 0 4 7 0 450 IFE=70RE=8THENEM*="WRITE ERROR":GOTO 470 460 EM*="READ ERROR" 470 ET*=T* 480 ES*=S* 490 PRINT"tDOWN>"EN*", "EM*","ET$%"ES* 500 CLOSE15 510 G0T01580 520 PRINT" <!CLR> {RVS> EDIT TRACK
455
S < SECTOR <RDFF>" 530 PRINT"xHOME>{DOWN> CRVS> TRA CK " T * " - SECTOR " S * " {ROFF>" 540 PRINT"xHOME> xDOWN 2>" 550 F0RJ=0T015 560 D=J*8+BP 570 G0SUB1740 580 BP*=" . "+DH$+": " 590 H * = " " 600 A * = " " 610 FORI=0T07 620 P R I N T # 1 5 , " M - R " C H R * ( J *8+1+BP)CHR*(4) 630 GET#15,B* 640 D=ASC(B*+CHR*(0)) 650 POKE <40704+J*8+I) , D 660 G0SUB1740 670 H*=H*+DH*+" " 680 IFD >127THEND=D-128 690 IFD< 320RD >95THEND=46 700 IFD=34THEND=46 710 A*=A*+CHR*<D) 720 NEXTI 730 PRINTBP$HS"{RVS>"A*"CROFF>" 740 NEXTJ 750 PRINT"CDOWN>CRVS>EDITCROFF> TRACK "T * " - SECTOR "SS" ( Y / N ) ? " 760 G0SUB1790 770 IFQ*<>"Y"G0T01390 780 PRINTCD$"PRESS {RVS>CLR{ROFF> TO EXI T 790 PRINT"CHOME> xDOWN 3>CRIGHT 7 > " ; 800 S=1151 810 C=1 820 A=PEEK(S):IFA>127THENA=A-128 830 M=S 840 P0KEM,A+128 850 G E T I * : I F I * = " " T H E N 8 5 0 860 I=ASC<I$> 870 IFI=147THENP0KEM,A:GOTO1360 880 IFI=19THENP0KEM,A:G0T0790 890 I F I = 1 4 1 T H E N I = 1 3 900 I F I < >13G0T0930 910 IFC=23ANDS< >I773THENPRINT"CRIGHT>";: G0T01230 920 IFS<1751THENPOKEM,A:FORI=CT023:PRINT "CRIGHT>";:S=S+1:NEXTi:s=s-l:c=23:G0T0i2 30 930 I F I = 3 2 T H E N I = 2 9 : I $ = C H R * ( 2 9 ) 940 IFI<>29G0T0970 950 IFC<>23THENC=C+1:S=S+1: GOTO 1290
456
960 IF3< > 1773THENPRINT" iRIGHT> " ; :G0T0123 0 970 I F I < > 1 5 7 G 0 T 0 1 0 0 0 980 IFC<>1THENC=C-1:S=S-1:GOTG1290 990 IFC=1ANDS<>1151THENFORI = i TO18:PRINT" CLEFT> n ; : N E X T I : C = 2 3 : S = S - 1 8 : G O T O 1 3 0 0 1000 I F I < > 1 7 G 0 T 0 1 0 2 0 1010 IFS+40<1774THENS=S+40:GOTO1290 1020 I F I < > 1 4 5 G 0 T 0 1 0 4 0 1030 IFS-40>1150THENS=S-40:GOTO1290 1040 IFA=320RA=160G0T0850 1050 I F I < 4 8 0 R I > 5 7 A N D I < 6 5 0 R I > 7 0 G 0 T 0 8 2 0 1060 P R I N T I $ ; 1070 A = I : I F I > 6 4 T H E N A = A - 6 4 1080 IFA<7THENL=A+9 1090 IFA >47THENL=A-48 1100 I F I N T ( ( C + 1 ) / 3 ) = < C + 1 ) / 3 T H E N R = P E E K ( S 1):GOTO1120 1110 R=PEEK<S+1) 1120 IFR>127THENR=R-128 1130 IFR< 7THENR=R+9 1140 IFR>47THENR=R-48 1150 I F I N T ( ( C + l ) / 3 ) < > ( C + l ) / 3 T H E N I = L * 1 6 + R :G0T01170 1160 I = R * 1 6 + L 1170 P 0 K E 4 0 7 0 4 + 8 * I N T ( ( M - 1 1 5 1 ) / 4 0 ) + I N T < C / 3) 5 I 1180 I F I > 1 2 7 T H E N I = I - 1 2 8 1190 I F I < 320RI>95THENI=46 1200 I F I = 3 4 T H E N I = 4 6 1210 I F I > 6 4 T H E N P 0 K E M + 2 5 - C + I N T < C / 3 ) , 1 - 6 4 + 128:GOTO1230 1220 P O K E M + 2 5 - O I N T < C / 3 > , I + 128 1230 IFC=23ANDS< >1773THENF0RI = 1TO17:PRIN T"{RI6HT>";:NEXTI:c=l:s=s+18:G0T01300 1240 IFS=1773THENPRINT"CLEFT>";:G0T01300 1250 1260 1270 1280 1290 1300 1310 1320 1330 1340 1350 1360 T*" 1370 S=S+1 C=C+1 POKEM,A G0T0820 PRINTI$; A=PEEK(M):IFA>127THENA=A-128 POKEM,A G0T0820 PRINTCD*"EXIT ( Y / N ) ? " G0SUB1790 IFQ*="N"G0TQ780 PRINTCD*"CRV3>REWRITECR0FF> TRACK " SECTOR "S$" (Y/N)?" G0SUB1790
457
1380 IFQ*="Y"G0T01450 1390 CL0SE15 1400 F'RINTCD*"ATTEMPT TO EDIT A SECTOR C RVS> FAILEDCROFF> 1410 PRINT"{DOWN>PRESS xRVS>RETURNvROFF> TO CONTINUE11 1420 GETC*:IFC$=""THEN1420 1430 IFC*< >CHR*(13)GOTO1420 1440 G0T0120 1450 PR I NTCD* " {RVS> REWRITING <.ROFFJ TRACK " T * " - SECTOR " S * " 1460 FORI=0T0127 1470 P R I N T # 1 5 , " M - W " C H R * ( I + B P ) C H R * ( 4 ) C H R * (1)CHR*(PEEK(40704+1)) 1480 NEXTI 1490 REM WRITE 1500 T=VAL<T*) 1510 S=VAL<S*> 1520 J0B=144 1530 GOSUB1620 1540 CL0SE15 1550 IFE< >1GOTO1400 1560 PRINTCD*"ATTEMPT TO EDIT A SECTOR C OMPLETE" 1570 GQT01410 1580 P0KE56,160 1590 CLR 1600 END 1610 REM JOB QUEUE 1620 TRY=0 1630 PRINT#15,"MW"CHR*(8)CHR*(0)CHR* < 2) CHR*<T)CHR*<S) 1640 P R I N T # 1 5 , " M - W " C H R * < 1 ) C H R * ( 0 ) C H R * ( 1 ) CHR*(JOB) 1650 TRY=TRY+1 1660 PRINT#15,"M-R"CHR*<1)CHR*<0> 1670 G E T # 1 5 , E * 1680 I F E * = " " T H E N E * = C H R * ( 0 ) 1690 E=ASC(E*) 1700 IFTRY=500G0T01720 1710 I F E >127G0T01650 1720 RETURN 1730 REM DECIMAL TO HEXADECIMAL 1740 H = I N T ( D / 1 6 ) + 1 1750 L = D - < H - 1 ) * 1 6 + 1 1760 D H * = M I D * ( H D * , H , l ) + M I D * ( H D * , L , 1 ) 1770 RETURN 1780 REM QUERY 1790 GETQ*:IFQ*=""THEN1790 1800 I FQ*< >11Y " ANDQ*< > " N " GOTO 1790 1810 RETURN
458
100 REM EDIT DOB VERSION 110 PRINT"{CLR>EDIT DOS VERSION - 1541" 120 PRINT"{DOWN>REMOVE <RVS>WRITE PROTEC T TABCROFFJ" 130 PRINT"xDOWN3INSERT DISKETTE IN DRIVE I I 140 PRINT"{DOWN3PRESS CRVS>RETURN<ROFF> TO CONTINUE" 150 G E T C * : I F C * = " " T H E N 1 5 0 160 IFC*< >CHR*(13)GOTO150 170 PRINT"OK" 180 0 P E N 1 5 , 8 , 1 5 190 P R I N T # 1 5 , " I 0 " 200 I N P U T # i 5 , E N * , E M * , E T * , E S * 210 I F E N * = " 0 0 " G 0 T 0 2 5 0 220 PRINT"{DOWN>"EN*% "EM*","ET*","ES* 230 CL0SE15 240 END 250 P R I N T # 1 5 , " M - R " C H R * < 1 ) C H R * < 1 ) 260 GET#15,D0S* 270 IFDOS*=""THENDOS*=CHR*(0) 280 ODV=ASC<DOS*> 290 PRINT"xD0WN30LD DOS VERSION:";ODV 300 NDV=-1 310 INPUT"xDOWN>NEW DOS VERSIQN";NDV 320 IFNDV< OORNDV >255G0T0500 330 INPUT" -CDOWN>ARE YOU SURE ( Y / N ) Y<LE FT 3 > " ; Q * 340 IFQ*< >"Y"G0T0500 350 T=18 360 S=0 370 REM SEEK 380 J0B=176 390 G0SUB530 400 REM READ 410 J0B=128 420 G0SUB530 430 P R I N T # 1 5 , " M - W " C H R * ( 2 ) C H R * ( 4 ) C H R * <1)C HR*<NDV> 440 REM WRITE 450 J0B=144 460 G0SUB530 470 CL0SE15 480 PRINT"CDOWN>DONE?" 490 END 500 CL0SE15 510 END 520 REM JOB QUEUE 530 TRY=0 540 P R I N T # 1 5 , " M - W " C H R * ( 8 ) C H R * < 0 ) C H R * ( 2 ) C
459
HR*<T>CHR$<S> 550 PRINT#15,"M-W"CHR$(1)CHR*<0> CHR$(1)C HR$<JOB> 560 TRY=TRY+1 570 PRINT#15,"M-R"CHR$(1>CHR*<0> 580 GET#15,E$ 590 IFE$=""THENE*=CHR$(0) 600 E=ASC<E*> 610 IFTRY=500G0T0630 620 I F E >127G0T0560 630 IFE=1THENRETURN 640 CL0SE15 650 PRINT"tDOWN>CRVS> FAILED{ROFF>" 660 END
460
REM VALIDATE A DISKETTE - 1541 CLR CD$="<D0WN 2 1 } " DIMFS<143>,T*/.<143>,S7.<143> F'RINT" {CLR3- VALIDATE A DISKETTE PRINT"{DOWN>INSERT DISKETTE
154
l l
IN DRIVE
160 PRINT" CDOWN>PRESS iRVS3-RETURN{ROFF> TO CONTINUE" 170 GETC*:IFC$=""THEN170 180 IFC*< >CHR*(13)GOTO170 190 PRINT"OK" 200 O P E N 1 5 , 8 , 1 5 210 P R I N T # 1 5 , " I 0 " 220 I N P U T # 1 5 , E N * , E M * , E T $ , E S * 230 IFEN$="00"G0T0270 240 PRINT"<DOWN3"EN*", "EM$","ET$","ES* 250 CL0SE15 260 END 270 PRINT" <DOWN3- {RVSJFETCHING{ROFF> DIRE CTORY" 280 0 P E N 2 , 8 , 2 , " $ 0 , S , R " 290 I N P U T # 1 5 , E N $ , E M $ , E T * , E S * 300 IFEN$="00"G0T0320 310 G0T0240 320 FORI=0T0253 330 GET#2,B* 340 NEXTI 350 N=0 360 F0RJ=0T07 370 GET#2,B* 380 IFB*=""THENB$=CHR*(0) 390 A=ASC<B$> 400 IFA >127ANDA<133G0T0510 410 F 0 R I = 0 T 0 2 420 GET#2,B* 430 NEXTI 440 IFB*=""THENB$=CHR*(O) 450 A=ASC<B*> 460 IFA=0THENJ=7:NEXTJ:G0T0820 470 F 0 R I = 0 T 0 2 5 480 GET#2,B* 490 NEXTI 500 G0T0750 510 GET#2,B$ 520 IFB*=""THENB$=CHR$(0) 530 T7.(N>=ASC(B$> 540 GET#2,B$ 550 IFB*=""THENB*=CHR*(O >
461
560 570 580 590 600 610 620 630 640 650 660 670 680 690 700 710 720 730 740 750 760 770 780 790 800 810 820 830
S7.<N>=ASC<B*> F*="" NULL=0 F0RI=0T015 GET#2,B* I FB*= " " THENB*=CHR* (0 > A=ASC<B*) IFA=0THENNULL=NULL+1 IFA >127THENA=A-128 IFA< 320RA >95THENA=63 IFA=34THENA=63 F*=F*+CHR*<A> NEXTI IFNULL=16THENJ=7:NEXTJ:G0T0820 F*<N>=F* N=N+1 F0RI=0T010 GET#2,B* NEXTI IFJ=7G0T0790 F0RI=0T01 GET#2,B* NEXTI NEXTJ IFST=64G0T0820 G0T0360 CL0SE2 INPUT#15,EN*,EN*,ET*,ES*
850 PRINT"CDOWN>NO CLOSED F I L E S ARE IN T HE DIRECTORY" 860 CL0SE15 870 END 880 1=0 890 PRINT"{CLR>" 900 N*=R I GHT* ( " 00 " + R I GHT* (STR* (N) , LEN (ST R*(N)>-l>,3) 910 FORJ =OTON-1 920 J * = R I G H T * ( " 0 0 " + R I G H T * ( S T R * ( J + 1 ) ? LEN( STR*<J+1)>-l>,3) 930 PRINT"<HOME>CRVS>VALIDATINGCROFF> #" J*"/"N*": "F*(J) 940 P R I N T " v H O M E > " ; L E F T * ( C D * , I + 2 ) ; F * ( J ) ; " I Im 950 NB=1 960 T=T7.(J) 970 S=S"/.(J) 980 GOSUB1640 990 PR I NT " CHONE> " LEFT* (CD* ? 14-2) F * (J ) NB 1000 J0B=176
n
462
1090 G E T # 1 5 , B * 1100 T = A S C ( B * + C H R * ( 0 ) ) 1110 IFT=OGOTO1170 1120 G E T # 1 5 , B * 1130 S = A S C ( B * + C H R * ( 0 ) ) 1140 IFT >350RS>20+2*(T>17) + (T>24) + (T>30) THENI=I+2:R*="CRVS>":GOTO1230 1150 NB=NB+1 1160 G0T0980 1170 I = I + 2 1180 R * = " { R O F F > " 1190 IFE=1GOTO1240 1200 R $ = " { R V S } " 1210 G03UB1700 1220 G0T01250 1230 E * = " I L L E G A L TRACK OR SECTORCLEFT>": G0T01250 1240 E S = " 0 0 , 0 K , 0 0 , 0 0 " 1250 P R I N T " { H O M E > " R * ; L E F T * < C D * , I > ; F * < J > " "E*"{ROFF>" 1260 F*<J> = "CHOMEJCRVS>"+J*+"{ROFF>"+R*+ L E F T * < C D * , I ) + " { L E F T 3>"+F*<J>+" "+E*+"<R OFF> " 1270 IFI=20ANDJ<>N-1THENFORD=1TO1000:NEX TD:PRINT"CCLR>" : 1=0 1280 NEXTJ 1290 CL0SE15 1300 IFN<11THENS=N:GOTO1500 1310 INPUT"<DOWN>SUMMARY INFORMATION ( Y / N) Y<LEFT 3 > " ; Q * 1320 IFQ*<>"Y"GOTO11O 1330 SI*="CCLR>CRVS> /"+N*+" SUMMAR Y INFORMATION CROFF>" 1340 S=0 1350 P R I N T S I * 1360 F 0 R I = 0 T 0 9 1370 P R I N T F * ( S ) 1380 S=S+1 1390 IFS=NTHENI=9 1400 NEXTI 1410 IFS< >NGOTO1460
463
1420 IFS=NTHENPRINT"CD0WN> CRVS> TYPE ' C ' TO CONTINUE CROFF>" 1430 G E T C * : I F C * = " " T H E N 1 4 3 0 1440 IFC*< >"C"GOTO1430 1450 GOTO110 1460 PRINT"CDGWN3{RVS> TYPE *C' TO CONT INUE OR ' S ' TO STOP <ROFF>" 1470 GETC*:IFC$=""THEN1470 1480 IFC*<>"C"ANBC*<>"S"GOTO1470 1490 I FC*=11C " GOTO 1350 1500 G0T0110 1510 REM JOB QUEUE 1520 TRY=0 1530 P R I N T # 1 5 , " M - W " C H R * ( 8 ) C H R * < 0 ) C H R * ( 2 ) CHR*(T)CHR*(S) 1540 P R I N T # 1 5 , " M W " C H R * ( 1 ) C H R * ( 0 ) C H R * ( I ) CHR*(JOB) 1550 TRY=TRY+1 1560 P R I N T # 1 5 , " M - R " C H R * < 1 ) C H R * ( 0 ) 1570 G E T # 1 5 , E * 1580 IFE*=""THENE*=CHR*(O) 1590 E=ASC(E*) 1600 IFTRY=500G0T01620 1610 I F E >127G0T01550 1620 RETURN 1630 REM S T R * ( T , S ) 1640 T * = R I G H T * ( S T R * ( T ) , L E N ( S T R * ( T ) ) - l ) 1650 IFT<1OTHENT*="0"+T* 1660 S * = R I G H T * ( S T R * ( S ) , L E N < S T R * ( S > ) - 1 ) 1670 IFS<1OTHENS*="O"+S* 1680 RETURN 1690 REM E N * , E M * , E T * , E S * 1700 I F E >1ANDE<12THENEN*=RIGHT*(STR*(E+1 8),2):G0T01720 1710 E N * = " 0 2 " : E M * = " ? T I M E 0UT":G0T01730 1720 EM*="READ ERROR" 1730 E T * = T * 1740 E S * = S * 1750 E * = E N * + " , "+EM*+","+ET*+"?"+ES* 1760 RETURN
464
100 REM DUPLICATE TRACK S t SECTOR - 1541 110 PR I NT " { CLR> DUPLICATE TRACK 8t SECTOR - 1541" 120 PRINT"CDOWN>INSERT DISKETTE IN DRIVE I I 130 INPUT"{DOWN>SOURCE TRACK AND SECTOR (T,S)";T,S 140 G0SUB580 150 T R = T : T = 0 160 SR=S:S=O 170 INPUT"CDOWN>TARGET TRACK AND SECTOR <T,S>";T,S 180 G0SUB580 190 TW=T 200 SW=S 210 INPUT"CDOWN>ARE YOU SURE Y<LEFT 3>" jQ* 220 IFQ*<>"Y"THENEND 230 O P E N 1 5 , 8 , 1 5 240 P R I N T # 1 5 , " I 0 " 250 I N P U T # 1 5 , E N * , E M * , E T * , E S * 260 I F E N * = " 0 0 " G 0 T 0 3 1 0 270 PRINT"{DOWN>"EN*", "EM*","ET*","ES* 280 CL0SE15 290 END 300 REM SEEK 310 T=TR 320 S=SR 330 J0B=176 340 G0SUB630 350 IFE=1G0T0380 360 G0T0750 370 REM READ 380 J0B=128 390 G0SUB630 400 IFE=1G0T0430 410 G0T0750 420 REM SEEK 430 T=TW 440 S=SW 450 JOB=176 460 G0SUB630 470 IFE=1G0T0500 480 G0T0750 490 REM WRITE 500 J0B=144 510 G0SUB630 520 IFE=1G0T0540 530 G0T0750 540 CL0SE15
465
550 PRINT"C DOWN >DONE!" 560 END 570 REM ILLEGAL TRACK OR SECTOR 580 IFT<1ORT>35THENEND 590 N S = 2 0 + 2 * ( T > 1 7 ) + ( T > 2 4 ) + ( T > 3 0 ) 600 IFS< OORS >NSTHENEND 610 RETURN 620 REM JOB QUEUE 630 TRY=0 640 P R I N T # 1 5 , " M - W " C H R * < 8 > C H R * ( 0 ) C H R * ( 2 ) C HR*<T>CHR*<S> 650 P R I N T # 1 5 , " M - W " C H R * ( 1 ) C H R * ( 0 ) C H R * ( 1 ) C HR*(JOB) 660 TRY=TRY+1 670 PRINT#15,"M-R"CHR*<1)CHR*<0> 680 G E T # 1 5 , E * 690 I F E * = " " T H E N E * = C H R * ( 0 ) 700 E=ASC<E*> 710 IFTRY=500G0T0750 720 I F E >127G0T0660 730 RETURN 740 REM ERROR HANDLER 750 E T * = R I G H T * ( S T R * ( T ) , L E N ( S T R * < T ) ) - 1 ) 760 I FT< 1 OTHENET*=110 " +ET* 770 E S * = R I G H T * ( S T R * ( S ) , L E N ( S T R * ( S ) ) - l ) 780 IFS<1OTHENES*="0"+ES* 790 I F E >1ANDE<12THENEN*=RIGHT*(STR*(E+18 ),2):GQT0810 800 E N * = " 0 2 " : E M * = " ? T I M E 0UT":GQT0830 810 IFE=70RE=8THENEM*="WRITE ERROR":GOTO 830 820 EM*="READ ERROR" 830 PRINT"CDOWN>"EN*", "EM*","ET*","ES* 840 PRINT"CDOWN> xRVS>FAILEDxROFF3" 850 CL0SE15 860 END
466
lOO REM COPY TRACK & SECTOR - 1541 110 PRINT"{CLR>COPY TRACK & SECTOR - 154 1" 120 PRINT"<DOWN>INSERT MASTER IN DRIVE" 130 INPUT"CDOWN>TRACK AND SECTOR < T , S ) " ; T,S 140 IFT<1ORT>35THENEND 150 N S = 2 0 + 2 * ( T > 1 7 > + (T>24) + < T>30) 160 IFS< OORS >NSTHENEND 170 INPUT"<DOWN>ARE YOU SURE Y<LEFT 3>" ;Q* 180 IFQ*< >"Y"THENEND 190 O P E N 1 5 , 8 , 1 5 200 P R I N T # 1 5 , " I O " 210 I N P U T # 1 5 , E N * , E M * , E T * , E S * 220 I F E N * = " 0 0 " G 0 T 0 2 7 0 230 PRINT"{DOWN>"EN*", "EM*","ET*","ES* 240 CL0SE15 250 END 260 REM SEEK 270 J0B=176 280 G0SUB570 290 IFE=1G0T0320 300 G0T0690 310 REM READ 320 JOB=128 330 G0SUB570 340 IFE=1G0T0360 350 G0T0690 360 CL0SE15 370 PRINT"CDOWN>INSERT CLONE IN DRIVE" 380 PRINT"PRESS CRVS>RETURNCROFF> TO CON TINUE" 390 G E T C * : I F C * = " " T H E N 3 9 0 400 IFC*< >CHR* <13)G0T0390 410 PRINT"OK" 420 0 P E N 1 5 , 8 , 1 5 430 REM SEEK 440 JOB=176 450 G0SUB570 460 IFE=1G0T0490 470 G0T0690 480 REM WRITE 490 J0B=144 500 G0SUB570 510 IFE=1G0T0530 520 G0T0690 530 CL0SE15 540 PRINT"<DOWN>DONE!" 550 END
467
560 REM JOB QUEUE 570 TRY=0 580 P R I N T # 1 5 , " M - W " C H R * ( 8 ) C H R * <0>CHR*(2)C HR*<T>CHR*<S> 590 PRINT#15,"M-W"CHR*(1> C H R * ( O ) C H R * ( 1 ) C HR*(JOB) 600 TRY=TRY+1 610 PRINT#15,"MR"CHR*(1)CHR*(0) 620 G E T # 1 5 , E * 630 I F E * = " " T H E N E * = C H R * ( 0 ) 640 E=ASC<E*> 650 IFTRY=500G0T0690 660 I F E >127G0T0600 670 RETURN 680 REM ERROR HANDLER 690 E T * = R I G H T * ( S T R * ( T ) , L E N ( S T R * <T))1) 700 IFT<1OTHENET*="0"+ET* 710 E S * = R I G H T * < S T R * < S ) , L E N ( S T R * < S ) ) - 1 ) 720 IFS<1OTHENES*="0"+ES* 730 I F E >1ANDE<12THENEN*=RIGHT*(STR*(E+18 ),2):G0T0750 740 E N * = " 0 2 " : E M * = " ? T I M E 0UT":G0T0770 750 IFE=70RE=8THENEM*="WRITE ERROR":GOTO 770 760 EM*="READ ERROR" 770 PRINT"{DOWN>"EN*", "EM*","ET*","ES* 780 PRINT"<DOWN>{RVS>FAILEDiROFF>" 790 CLOSE15 800 END
468
100 REM RECOVER TRACK & SECTOR - 1541 110 PRINT" {CLR3-RECOVER TRACK 8 t SECTOR 1541"
120 PRINT"CDOWN>INSERT I I DISKETTE IN DRIVE 130 INPUT"CDOWN>RECOVER TRACK AND SECTOR
<T,S>";T,S 140 IFT<1ORT >35THENEND 150 NS=20+2*(T>17)+(T>24)+(T>30) 160 IFS< OORS >NSTHENEND 170 INPUT"{DOWN>ARE YOU SURE Y<LEFT 3>" ;Q$ 180 IFQ$< >"Y"THENEND
190 0PEN15,8,15 210 I N P U T # 1 5 , E N * , E M * , E T * , E S $ 220 IFEN$="OO"G0T0290 230 PRINT"<DOWN>"EN*", "EM*","ET*","ES* 240 PRINT"{DOWN>PRESS {RVS3-RETURNtROFF> TO CONTINUE" 250 GETC$:IFC$=""THEN250
200 PRINT#15,"I0"
300 G0SUB520 310 IFE=1G0T0340 320 G0T0640 340 J0B=128 350 G0SUB520
290 J0B=176
IFE< >1G0T0640
PRINT#15,"M-W"CHR*(8)CHR*(0)CHR$(2)C
469
HR*<T>CHR*<S) 540 PRINT#15,"M-W"CHR*<1)CHR*<0)CHR*<1>C HR*<JOB> 550 TRY=TRY+1 560 PRINT#15,"MR"CHR*(1)CHR* < 0) 570 G E T # 1 5 , E * 580 I F E * = " " T H E N E * = C H R * ( 0 ) 590 E=ASC<E*> 600 IFTRY=500G0T0640 610 I F E >127G0T0550 620 RETURN 630 REM ERROR HANDLER 640 E T * = R I G H T * < S T R * ( T ) , L E N ( S T R * ( T ) ) l ) 650 IFT<1OTHENET*="0"+ET* 660 E S * = R I G H T * ( S T R * ( S ) , L E N ( S T R * ( S ) ) 1 ) 670 IFS<1OTHENES*="0"+ES* 680 I F E >1ANDE<12THENEN*=RIGHT*(STR*(E+18 ),2>:G0T0700 690 E N * = " 0 2 " : E M * = " ? T I M E 0UT":G0T0720 700 IFE=70RE=8THENEM*="WRITE ERROR":GOTO 720 710 EM*="READ ERROR" 720 PRINT"{DOWN>"EN*", "EM*","ET*","ES* 730 PRINT"CDOWN> <RVS>FAILED<ROFF>" 740 CL0SE15 750 END
470
100 REM LAZARUS - 1541 110 PRINT"CCLR>LAZARUS - 1541" 120 PRINT"<DOWN>INSERT DISKETTE I I
IN DRIVE
130 INPUT"tHOME>{DOWN 4>ATTEMPT A RESURR ECTION (Y/N> YCLEFT 3 J " j Q * 140 IFQ*< >"Y"THENEND 150 0 P E N 1 5 , 8 , 1 5 160 REM SEEK 170 FORT=1TG35 180 N S = 2 0 + 2 * ( T > 1 7 ) + ( T > 2 4 ) + ( T > 3 0 ) 190 T * = R I G H T * < S T R * < T ) , L E N < S T R * < T > ) - l ) 200 IFT<1OTHENT*="0"+T* 210 JOB=176 220 G0SUB510 230 IFE=1G0T0250 240 BD=BD+1:R=R+NS:G0T0420 250 REM READ 260 FORS=OTONS 270 S * = R I G H T * ( S T R * ( S ) , L E N ( S T R * ( S ) ) 1 ) 280 IFS<1OTHENS*="O"+S* 290 PRINT"{HOME> CDOWN 6>{RVS>RESURRECTIN GCROFF> TRACK " T * " - SECTOR " S * 300 J0B=128 310 G0SUB510 320 IFE=1G0T0410 330 R=R+1 340 IFE<>4ANDE<>5G0T0410 350 IFE=5G0T0380 360 P R I N T # 1 5 , " M - W " C H R * ( 7 1 ) C H R * < 0 ) C H R * < 1 ) CHR*<7) 370 REM WRITE 380 J0B=144 390 G0SUB510 400 IFE< >1THENW=W+1 410 NEXTS 420 NEXTT 430 CL0SE15 440 PRINT" CHOME> <DOWN 6> I I 450 IFBD=35THENPRINT"CHOME>CDOWN 6>9BAD DISK":END 460 PRINT" <HOME3- CDOWN 6>READ ERRORS : " R 470 PRINT"<DOWN>WRITE ERRORS:"W 480 PRINT"tDOWN>DONE!" 490 END 500 REM JOB QUEUE 510 TRY=0 520 PR I NT#15, " M-W " CHR* (8) CHR* (0) CHR* (2) C HR*<T)CHR*<S>
471
530 PRINT#15,"M-W"CHR*(1)CHR*<0> C H R * ( 1 ) C HR*(JOB> 540 TRY=TRY+1 550 PRINT#15,"MR"CHR* i 1 ) C H R * ( 0 ) 560 G E T # 1 5 , E * 570 IFE*=""THENE*=CHR*(O) 580 E=ASC(E*> 590 IFTRY=500G0T061O 600 I F E >127G0T0540 610 RETURN
472
100 REM INTERROGATE FORMATTING I D ' S - 15 41 l l O DIMT(35) 120 F 0 R I = l T 0 3 5 130 T < I > = 1 140 NEXTI 150 PRINT" <!CLR> INTERROGATE FORMATTING I D ' S - 1541" 160 PRINT"<DOWN>INSERT MASTER IN DRIVE" 170 PRINT" <DOWN>PRESS <.RVS>RETURNCROFF> TO CONTINUE" 180 G E T C * : I F C * = " " T H E N 1 8 0 190 IFC*< >CHR* <13)G0T0180 200 0 P E N 1 5 , 8 , 1 5 210 P R I N T " { C L R > " 220 REM SEEK 230 F 0 R T = l T 0 3 5 240 I F T < T)=0G0T0440 250 G0SUB550 260 IFE< >1G0T0410 270 P R I N T # 1 5 , " M - R " C H R * < 2 2 ) C H R * < 0 ) 280 G E T # 1 5 , I * 290 I F I * = " " T H E N I * = C H R * ( 0 ) 300 I = A S C < I * ) 310 I * = R I G H T * < S T R * < I ) , L E N < S T R * < I ) ) - l ) 320 P R I N T # 1 5 , " M - R " C H R * < 23)CHR* < 0 ) 330 G E T # 1 5 , D * 340 IFD*=""THEND*=CHR*(O) 350 D=ASC<D*) 360 D * = R I G H T * ( S T R * ( D ) , L E N ( S T R * ( D ) ) - l ) 370 I * = " C H R * ( " + I * + " ) " 380 D * = " C H R * ( " + D * + " ) " 390 I D * = I * + " + " + D * 400 G0T0450 410 IFE=3THENID*="?N0 SYNC MARKS":G0T045 O 420 IFE=2THENID*="7HEADER BLOCKS NOT PRE SENT":G0T0450 430 IFE=9THENID*="7CHECKSUM ERROR IN HEA DERS":G0T0450 440 I D * = " 7 T I M E OUT" 450 T * = R I G H T * ( S T R * ( T ) , L E N < S T R * ( T ) ) - 1 ) 460 IFT<lOTHENT*=" " + T * 470 PRINT"TRACK " T * " = " I D * 480 REM PAUSE 490 G E T C * : I F C * = " " G 0 T 0 5 1 0 500 G E T C * : I F C * = " " T H E N 5 0 0 510 NEXTT 520 CL0SE15 530 END
473
540 REM JOB QUEUE 550 TRY=0 560 P R I N T # 1 5 , " M - W " C H R * < 8 ) C H R * ( 0 ) C H R * ( 2 ) C HR*<T>CHR*<0> 570 PRINT#15,"M-W"CHR*<1>CHR*<0>CHR*<1)C HR*<176> 580 TRY=TRY+1 590 P R I N T # 1 5 , " M - R " C H R * <1)CHR* < 0) 600 G E T # 1 5 , E * 610 I F E * = " " T H E N E * = C H R * ( 0 > 620 E=ASC(E*) 630 IFTRY=500G0T0650 640 I F E >127G0T0580 650 RETURN
474
100 REM INTERROGATE A TRACK - 1541 110 PRINT"{CLR>INTERROGATE A TRACK - 154 1" 120 PRINT"{DOWN>INSERT MASTER I N DRIVE" 130 INPUT"CDOWNJINTERROGATE TRACK";T 140 IFT<1ORT >35THENEND 150 INPUT"{DOWN>ARE YOU SURE YCLEFT 3>" ;Q$ 160 IFQ$< >"Y"THENEND 170 O P E N 1 5 , 8 , 1 5 180 NS=20+2* <T>17) + (T>24) + (T>30) 190 REM SEEK 200 J0B=176 210 G0SUB370 220 REM READ 230 PRINT"CCLR>" 240 FORS=OTONS 250 J0B=128 260 G0SUB370 270 S $ = R I G H T $ ( S T R $ ( S ) , L E N ( S T R $ < S ) ) - l ) 280 IFS<lOTHENS*=" " + S * 290 PRINT"TRACK";T;" " ; 300 IFE=1THENPRINT"SECTOR " S * " = OK":GOT 0330 310 I F E >1ANDE<12THENEM$=STR$ < E + 1 8 ) + " REA D ERROR" 320 PRINT"SECTOR "S$" ="EM* 330 NEXTS 340 CL0SE15 350 END 360 REM JOB QUEUE 370 TRY=O 380 PRINT#15,"M-W"CHR$<8>CHR*<0>CHR*(2>C HR$ <T>CHR$ <S) 390 PRINT#15,"M-W"CHR$(1)CHR$(O)CHR$<1)C HR*(JOB) 400 TRY=TRY+1 410 PRINT#15,"M-R"CHR$<1>CHR*<0) 420 GET#15,E$ 430 IFE$=""THENE$=CHR$ <0> 440 E=ASC<E*> 450 IFTRY=500G0T0480 460 I F E >127G0T0400 470 RETURN 480 EM*="7TIME OUT" 490 RETURN
475
100 REM SHAKE, RATTLE, & ROLL - 1541 110 PRINT"{CLRJ SHAKE, RATTLE, & ROLL - 1 541" 120 PRINT"tDOWN>INSERT DISKETTE IN DRIVE 130 INPUT"<DOWN>CLATTER TRACK";T 140 IFT<1ORT>35THENEND 150 INPUT"{DOWN>ARE YOU SURE YCLEFT 3>" ;Q* 160 IFQ*<>"Y"THENEND 170 0 P E N 1 5 , 8 , 1 5 180 0 P E N 2 , 8 , 2 , " # " 190 PRINT"<CLR>" 200 REM SEEK 210 B0SUB360 220 NS=20+2* < T>17) + < T>24) + (T>30) 230 FORS=OTONS 240 REM READ 250 P R I N T # 1 5 , " U l " ; 2 ; 0 ; T ; s 260 I N P U T # 1 5 , E N * , E M * , E T * , E S * 270 P R I N T " T R A C K " ; T ; " - " ; 280 I F E N * = " 0 0 " T H E N P R I N T " S E C T 0 R " ; S ; " = OK" :G0T0300 290 PRINT"SECTOR " E S * " = "EN*" "EM* 300 NEXTS 310 CLOSE2 320 I N P U T # 1 5 , E N * , E M * , E T * , E S * 330 CL0SE15 340 END 350 REM JOB QUEUE 360 TRY=0 370 P R I N T # 1 5 , " M - W " C H R * ( 8 ) C H R * ( 0 ) C H R * <2)C HR*(T)CHR*<S) 380 P R I N T # 1 5 , " M - W " C H R * ( l ) C H R * ( 0 ) C H R * <1)C HR*(176) 390 TRY=TRY+1 400 P R I N T # 1 5 , " M - R " C H R * ( 1 ) C H R * ( 0 ) 410 G E T # 1 5 , E * 420 IFE*=""THENE*=CHR*(0) 430 IFTRY=500G0T0460 440 IFASC(E*> >127G0T0390 450 RETURN 460 CL0SE2 470 I N P U T # 1 5 , E N * , E M * , E T * , E S * 480 CLOSE15 490 PRINT"tDOWN>CRVS>FAILEDCROFF>" 500 END
477
100 REM INTERROGATE A DISKETTE - 1541 110 DIMT<35) 120 F 0 R I = l T 0 3 5 130 T ( I ) = 1 140 NEXTI 150 PRINT"{CLR>INTERROGATE A DISKETTE 1541" 160 PRINT"{DOWN>INSERT MASTER IN DRIVE" 170 PRINT" <!DOWN>PRESS {RVS>RETURN{ROFF> TO CONTINUE" 180 G E T C * : I F C * = " " T H E N 1 8 0 190 IFC*< >CHR*(13 > GOTO180 200 PRINT"OK" 210 PRINT 220 O P E N 1 5 , 8 , 1 5 230 F0RT=lT035 240 I F T ( T ) = 0 G 0 T 0 3 9 0 250 N S = 2 0 + 2 * ( T > 1 7 ) + ( T > 2 4 ) + ( T > 3 0 ) 260 REM SEEK 270 J0B=176 280 G0SUB430 290 REM READ 300 FORS=OTONS 310 J0B=128 320 G0SUB430 330 IFE=1G0T0330 340 S*=RIGHT* < S T R * ( S ) , L E N < S T R * ( S ) ) - 1 ) 350 IFS<1OTHENS*=" " + S * 360 I F E >1ANDE<12THENEM*=STR*(E+18)+" REA D ERROR" 370 PRINT"TRACK";T;" SECTOR " S * " = "EM* 380 NEXTS 390 NEXTT 400 CLQSE15 410 END 420 REM JOB QUEUE 430 TRY=0 440 PRINT#15,"M-W"CHR*<8>CHR*(0>CHR*(2)C HR*<T>CHR*<S> 450 PRINT#15,"M-W"CHR*(1)CHR*<0> CHR* <1)C HR*<JOB> 460 TRY=TRY+1 470 P R I N T # 1 5 , " M - R " C H R * ( 1 ) C H R * ( 0 ) 480 G E T # 1 5 , E * 490 I F E * = " " T H E N E * = C H R * ( 0 ) 500 E=ASC<E*> 510 IFTRY=500G0T0540 520 IFE >127G0T0460 530 RETURN 540 EM$="7TIME OUT" 550 RETURN
479
REM DUMP TRACK & SECTOR - 1541 P0KE56,159 CLR PRINT"CCLR>DUMP TRACK & SECTOR PRINT"CDOWN>INSERT DISKETTE
154
IN DRIVE
150 INPUT" {DOWN>TRACK & SECTOR < T , S > " ; T , S 160 IFT<1ORT>35G0T0630 170 N S = 2 0 + 2 * ( T > 1 7 ) + (T>24) + <T>30> 180 IFS< OORS >NSG0T0630 190 INPUT"CDOWNJARE YOU SURE YCLEFT 3>" ;Q$ 200 IFQ$< >"Y"G0T0630 210 0 P E N 1 5 , 8 , 1 5 220 P R I N T # 1 5 , " I O " 230 I N P U T # 1 5 , E N * , E M $ , E T $ , E S $ 240 IFEN$="OO"G0T0290 250 P R I N T " < D O W N > " E N $ " , " E M V , " E T $ " , " E S $ 260 PRINT"CDOWN>PRESS CRVS>RETURN<ROFF> TO CONTINUE" 270 GETC$:IFC$=""THEN270 280 IFC$< >CHR*(13)G0T0270 290 T $ = R I G H T $ < S T R $ < T > , L E N < S T R * < T > ) - l ) 300 IFT<1OTHENT*="0"+T* 310 S$=RIGHT*<STR*<S>,LEN<STR$ < S ) ) - 1 ) 320 IFS<1OTHENS$="0"+S* 330 REM SEEK 340 J0B=176 350 G0SUB670 360 IFE=< >1G0T0410 370 REM READ 380 J0B=128 390 G0SUB670 400 IFE=10RE=40RE=5G0T0510 410 I F E >1ANDE<12THENEN$=RIGHT$(STR$ < E+18 ),2):G0T0430 420 E N $ = " 0 2 " : E M * = " 7 T I M E 0UT":G0T0440 430 EM$="READ ERROR" 440 ET*=T$ 450 E S * = S * 460 P R I N T " t D O W N > " E N $ " , " E M * " , " E T $ " , " E S $ 470 CL0SE15 480 P O K E 5 6 , l 6 0 490 CLR 500 END 510 F0RJ=0T031 520 F 0 R I = 0 T 0 7 530 P R I N T # 1 5 , " M - R " C H R $ < J * 8 + I ) C H R * < 4 )
481
540 GET#15,D* 550 D = A S C ( D * + C H R * ( 0 ) ) 560 P 0 K E < 4 0 7 0 4 + J * 8 + I ) , D 570 NEXTI 580 NEXTJ 590 CLQSE15 600 PRINT"CDOWN>DONE!" 610 PRINT"{D0WN>P0KE56,160:CLR" 620 END 630 POKE56,160 640 CLR 650 END 660 REM JOB QUEUE 670 TRY=0 680 P R I N T # 1 5 , " M - W " C H R * ( 8 ) C H R * ( 0 ) C H R * ( 2 ) C HR*<T)CHR*<S) 690 P R I N T # 1 5 , " M - W " C H R * ( 1 ) C H R * ( 0 ) C H R * ( 1 > C HR*(JOB) 700 TRY=TRY+1 710 PRINT#15,"M-R"CHR*<1>CHR*<0) 720 G E T # 1 5 , E * 730 IFE*=""THENE*=CHR*< 0) 740 E=ASC(E*) 750 IFTRY=500G0T0770 760 I F E >127G0T0700 770 RETURN
482
100 REM BULK ERASER - 1541 110 PRINT"{CLR>BULK ERASER - 1541" 120 PRINT"(DOWN>INSERT DISKETTE IN DRIVE I I 130 INPUT" tDOWN3- {RVS>ERASECROFF> THIS D I SKETTE Y{LEFT 3 > " ; Q * 140 IFQ$< >"Y"THENEND 150 INPUT"{DOWN>ARE YOU SURE YCLEFT 3 > " ;Q$ 160 IFQ$< >"Y"THENEND 170 0 P E N 1 5 , 8 , 1 5 180 F 0 R I = 0 T 0 2 3 190 READD 200 D*=D*+CHR*<D) 210 NEXTI 220 P R I N T # 1 5 , " M - W " C H R $ ( 0 ) C H R $ ( 4 ) C H R $ ( 2 4 ) D$ 230 FORT=1T035 240 PRINT"<HOME> tDOWN 8><RVS>ERASINGCR0F F> TRACK"T 250 REM SEEK 260 J0B=176 270 G0SUB360 280 REM EXECUTE 290 J0B=224 300 G0SUB360 310 NEXTT 320 PRINT"{HOME3- tDOWN 8>D0NE!
II
330 CL0SE15 340 END 350 REM JOB QUEUE 360 TRY=O 370 P R I N T # 1 5 , " M - W " C H R $ ( 8 ) C H R $ ( 0 ) C H R $ ( 2 ) C HR*(T)CHR$(0) 380 P R I N T # 1 5 , " M - W " C H R $ < 1 ) C H R * < 0 ) C H R * ( 1 ) C HR*(JOB) 390 TRY=TRY+1 400 P R I N T # 1 5 , " M - R " C H R * ( 1 ) C H R $ ( 0 ) 410 G E T # 1 5 , E * 420 IFE$=""THENE$=CHR$(0) 430 E=ASC<E*) 440 IFTRY=500G0T0470 450 I F E >127G0T0390 460 RETURN 470 CLOSE15 480 PRINT"CDOWN> CRVS> FAILED{ROFF>" 490 END 500 REM 21 ERROR
483
510 DATA
32,163,253,169, 48, 1,
85,141,
1,
28 32
32,201,253,
76,105,249,234
484
APPENDIX D
485
100 110 120 130 140 150 160 170 180 190 200
REM DECIMAL T0 HEXADECIMAL H$="0123456789ABCDEF" PRINT"iCLR>DECIMAL TO HEXADECIMAL" D=-1 INPUT"{DOWN>DECIMAL ";D IFD< OORD >255THENEND H=INT(D/16) L=D-<H*16) HD$=MID* < H * , H + 1 , 1 ) + M I D $ < H * , L + 1 , 1 ) PRINT"CDOWN>HEXADECIMAL: "HD$ G0T0130
487
100 110 120 130 140 150 160 170 180 190 = 16 200 210 220 230 240 250 260 270 280 290 300 310
REM HEXADECIMAL T0 DECIMAL H$="01234567S9ABCDEF" PRINT"CCLR>HEXADECIMAL TO DECIMAL" HD*="" INPUT"<.DOWN>HEXADECIMAL"iHD* IFLEN< HD$)=OTHENEND I F L E N ( H D * ) < >2THENEND H=0 F0RI=lT016 IFLEFT*<HD*,l>=MID*<H$,I,l>THENH=I NEXTI IFH=OGOTO130 H=H-1 L=0 F0RI=lT016 IFRIGHT* <HD*,1)=MID*(H,1,1)THENL=
1 = 16
NEXTI IFL=OGOTO130 L=L-1 D=H*16+L PR I NT " < . DOWN3 DEC I MAL G0T0130
:"D
489
100 110 120 130 140 150 160 170 180 190 200 210 220
REM DECIMAL T0 BINARY DEFFNB(B>=2^ < B - I N T ( B / 8 ) * 8 ) A N D D PRINT"CCLR> DECIMAL TO BINARY" D=-1 INPUT"CDOWN>DECIMAL";D IFD< OORD >255THENEND PRINT"<DQWN>BINARY : " ; FORB=7TOO S T E P - 1 IFFNB(B)=OTHENPRINT"0";:G0T0200 PRINT"l"J NEXTB PRINT G0TQ130
491
100 REM BINARY T0 DECIMAL 110 PRINT"CCLR>BINARY TO DECIMAL" 120 B $ = " " 130 INPUT"CDOWN>BINARY ( E . G . , 10101010)" ;B$ 140 IFLEN(B$)=OTHENEND 150 I F L E N ( B * ) < >8THENEND 160 B=0 170 D=0 180 F 0 R I = l T 0 8 190 I F M I D $ ( B $ , I , 1 ) = " 1 " T H E N B = B + 1 : D = D + 2 ^ ( 8 -I):G0T0210 200 I F M I D * ( B * , I , 1 ) = " 0 " T H E N B = B + 1 210 NEXTI 220 IFB< >8G0T0120 230 PRINT"CDOWN>DECIMAL : "D 240 G0T0120
493
100 REM HEXADECIMAL T0 GCR 110 H*="0123456789ABCDEF" 120 D I M B * ( 1 5 ) 130 B * ( 0 ) = " 0 1 0 1 0 " 140 B * ( l ) = " 0 1 0 1 1 " 150 B * ( 2 ) = " 1 0 0 1 0 " 160 B * ( 3 ) = " 1 0 0 1 1 " 170 B * ( 4 ) = " 0 1 1 1 0 " 180 B * ( 5 ) = " 0 1 1 1 1 " 190 B* < 6 ) = " 1 0 1 1 0 " 200 B * ( 7 ) = " 1 0 1 1 1 " 210 B * ( 8 ) = " 0 1 0 0 1 " 220 B * ( 9 ) = " 1 1 0 0 1 " 230 B * ( 1 0 ) = " 1 1 0 1 0 " 240 B * ( 1 1 ) = "11011" 250 B * ( 1 2 ) = " O i 1 0 1 M 260 B * ( 1 3 ) = " 1 1 1 0 1 " 270 B * ( 1 4 ) = " 1 1 1 1 0 M 280 B * ( 1 5 ) = " 1 0 1 0 1 " 290 PRINT"{CLR>HEXADECIMAL TO GCR" 300 HG*="" 310 INPUT"CDOWN>HEXADECIMAL ( E . G . , 084A0 023)";HG* 320 IFLEN(HG*)=OTHENEND 330 IFLEN(HG*)<>8THENEND 340 F 0 R I = l T 0 4 350 H G * ( I ) = M I D * ( H G * , 1 * 2 1 , 2 ) 360 NEXTI 370 F 0 R J = l T 0 4 380 H ( J ) = 0 390 F 0 R I = l T 0 1 6 400 IFLEFT*(HG*(J),l)=MID*(H*,I,l)THENH( J)=l:1=16 410 NEXTI 420 I F H ( J ) = 0 G 0 T 0 3 0 0 430 H ( J ) = H ( J ) - 1 440 L ( J ) = O 450 FORI=1TO16 460 I F R I G H T * ( H G * ( J ) , l ) = M I D * ( H * , I , l ) T H E N L (J)=l:1=16 470 NEXTI 480 I F L ( J ) = 0 G 0 T 0 3 0 0 490 L ( J ) = L ( J ) - 1 500 NEXTJ 510 F Q R I = l T 0 4 520 I M A G E * = I M A G E * + B * ( H ( I ) ) 530 I M A G E * = I M A G E * + B * ( L ( I ) ) 540 NEXTI 550 PRINT"xDOWN>"IMAGE* 560 PRINT"CUP>";
495
570 F 0 R I = l T 0 8 580 PRINT"^ "5 590 NEXTI 600 PRINT"*UP>" 6iO FQRI=lT05 620 B D * < I ) = M I D $ < I M A G E * , I * 8 - 7 . 8 > 630 NEXTI 640 F 0 R J = l T 0 5 650 F 0 R I = l T 0 8 660 I F M I D * < B D * < J > , I , l ) = " l " T H E N D < J > = D < J > + 2^<8-I) 670 NEXTI 680 NEXTJ 690 F 0 R I = l T 0 5 700 H = I N T < D < I > / 1 6 > + 1 710 L = D ( I ) ( H 1 ) * 1 6 + 1 720 D H * < I > = M I D * < H * , H , 1 > + M I D * < H * , L , 1 > 730 NEXTI 740 PRINT"vDOWN>HEXADECIMAL: "; 750 F 0 R I = l T 0 4 760 P R I N T H G * < I > ; " " ; 770 NEXTI 780 PRINT 790 PRINT"vDOWN>GCR : "; 800 F 0 R I = l T 0 5 810 P R I N T D H * ( I > 5 " " ; 820 NEXTI 830 PRINT 840 PRINT"xDOWN>DONE!" 850 END
496
100 REM GCR T0 HEXADECIMAL 110 H*="0123456789ABCDEF" 120 DEFFNB (B) =2'% ( B - 1 NT <B/8 > * 8 ) ANDD 130 DIMB*<15> 140 B * < 0 ) = " 0 1 0 1 0 " 150 B * < l > = " O l O H " 160 B * < 2 > = " 1 0 0 1 0 " 170 B * < 3 > = " 1 0 0 1 1 " 180 B * < 4 > = " 0 1 1 1 0 " 190 B * < 5 > = " 0 1 1 1 1 " 200 B * < 6 > = " 1 0 1 1 0 " 210 B * < 7 > = " l O l H " 220 B * < 8 ) = " 0 1 0 0 1 " 230 B * < 9 > = " 1 1 0 0 1 " 240 B * < 1 0 > = " 1 1 0 1 0 " 250 B $ ( 1 1 ) = " 1 1 0 1 1 " 260 B $ < 1 2 ) = " 0 1 1 0 1 " 270 B * < 1 3 > = " 1 1 1 0 1 " 280 B $ < 1 4 ) = " l l H O " 290 B * < 1 5 > = " 1 0 1 0 1 " 300 PRINT"CCLR>GCR TO HEXADECIMAL" 310 GH*="" 320 INPUT"CDOWN>GCR ( E - G - , 525DA52A53)"; GH* 330 IFLEN(GH*)=OTHENEND 340 IFLEN(GHS)<>1OTHENEND 350 F 0 R I = l T 0 5 360 G H * < I > = M I D * < G H * , I * 2 - l , 2 > 370 NEXTI 380 F 0 R J = l T 0 5 390 H < J ) = 0 400 F O R I = l T Q 1 6 410 I F L E F T * < G H * < J > , l ) = M I D * ( H * , I , l ) T H E N H ( J)=I:1=16 420 NEXTI 430 I F H ( J ) = 0 G 0 T 0 3 1 0 440 H ( J ) = H < J ) - 1 450 L ( J ) = O 460 FORI=1TO16 470 I F R I G H T * < G H * < J > , l > = M I D * < H * , I , l ) T H E N L <J>=I:1=16 480 NEXTI 490 I F L ( J ) = 0 G 0 T 0 3 1 0 500 L < J > = L < J ) - 1 510 NEXTJ 520 F 0 R I = l T 0 5 530 H D < I ) = H < I > * 1 6 + L < I > 540 NEXTI 550 IMAGE$="" 560 F 0 R I = l T 0 5
497
570 580 590 610 600 610 620 630 640 650 660 670 680 690 700 710 720 730 740 750 760 770 780 790 800 810 820 830 840 850 860
D=HD(I> F0RB=7T00 STEP-1 IFFNB(B)=OTHENIMAGE*=IMAGE$+"0":GOTO IMAGE*=IMAGE*+"1" NEXTB NEXTI PRINT"CDOWN>"IMAGE<6 PRINT" {UF'> " ; F0RI=lT05 PRINT"^ "; NEXTI PRINT"<UP>" F0RI=lT08 H $ ( I ) = M I D $ <IMAGE$,1*54,5) NEXTI F0RJ=lT08 F0RI=0T015 IFH$<J)=B$<I)THEND<J)=I+l:I=15 NEXTI NEXTJ F0RI=lT08 IFD(I)=0THENBDE=1 NEXTI IFBDE=1G0T0940 PRINT"CDOWN>GCR : "; F0RI=lT05 PRINTGH*<I)" "5 NEXTI PRINT PRINT"CDOWN>HEXADECIMAL: " !
880 P R I N T M I D $ < H * , D < I ) , 1 ) ; 890 I F I / 2 = I N T ( I / 2 ) T H E N P R I N T " "; 900 NEXTI 910 PRINT 920 PRINT"CDGWN>DONE!" 930 END 940 F 0 R I = l T 0 8 950 IFD(I)=OTHENPRINT"<RVSJ"H$ < I ) " { R O F F > ";:G0T0970 960 P R I N T H $ ( I ) ; 970 NEXTI 980 PRINT"{DOWN>{RVS>BYTE DECODING ERROR tROFF>" 990 END
498
INDEX
499
INDEX
A
27 24-25 22
B-A B-E B-F BP bad sectoring backup of a protected diskette BAM binary to GCR conversion bit copying bit manipulation block allocate map block availability map block, data header block-allocate command block-execute command block-free block-read block-write command buffer buffer areas, organization of buffer-pointer command bugs in DOS 2.6 bump byte separation bytes, determining bytes in the header block bytes per diskette
C
72 72 72 71 122 162 21, 26, 36 40 115-116 122 115 26 21 113 113 89, 113 72, 97 71, 72, 94 71, 98 71, 77, 100 82 82 71, 75 206-208 112 114 116 32 31
C cards, wild carriage return as a delimiter chaining characters, sychronization checksum, header block
23 23, 24 55 42 113-114 32
501
checksums clock rates close coda obscura codes, FDC job command channel command, validate commands, block-allocate block-execute block-free block-read block-write buffer-pointer copy direct-access DOS initialize LOAD memory-execute memory-write rename SAVE scratch validate VERIFY Commodore encoding scheme Commodore file types communications, computer-disk drive controller, floppy disk coping a locked file copy command
118-119 30-31 19 122 109, 112 16 26-27 : 89 97 94 98 77, 100 75 23 71-72 15 16 21 16 96 85 22-23 16 24 26-27 16 113 43 183 103 70 23
data block data communication channel DEL deleted delimiter determining bytes DIR direct access direct-access commands direct-access data channel disk commands, execution disk drives, incompatability disk, full disk ID
file
502
disk interrogation disk management disk protection schemes diskette, recovering a physically damaged diskette, recovering an entire DOS bugs DOS, definition of DOS error messages DOS protection DOS, tasks of DOS 2.6 sync mark duplicating a protection scheme
E
encoding scheme, Commodore end or identify EOI EOR EOR truth table error messages, DOS error, illegal track or sector illegal quantity no channel recovery hard recovery soft time out write errors, hard read read/write soft write execute execution of disk commands
F
113 99 99 118 118 104, 119-121 91 74 83 175-176 175 112 112 175 120 175 175 120 112 183-184
FDC FDC formatting routine FDC job codes FDC main loop FDC major entry points FDC read routine FDC routines, major FDC routines, using FDC write routine fields of a directory
103, 182 193 109, 112 189-190 190 191 188-193 193-199 192 36
503
1541 binary to GCR look up table file, deleted file management file name limits file open error file padding file, random access relative sequential data side sector user storage file storage, program sequential file types, Commodore files, locked fixed record length floppy disk controller formatting a diskette forward pointer full disk full new, recovering from
G
114-115 114
hard error, recovery hard errors header block header block, byte makeup header block checksum header block ID header block layout
I ID character -2 ID HI illegal quantity error illegal track or sector error incompatability, write initialize command initializing a disk
22 32 32 74 91 208-209 21 21-22
504
last block signal layout of a header block length, fixed record LOAD load address locating particular relative locked file, coping locked file, renaming locked
M
files files
49 32 66 16 48 68-69 70 70 70
ME M-mode MR M-W magnetic domains major FDC routines memory-execute command memory-read command memory-write command merging sequential files with copy command
N
file
21 20-21 21 29 83 47
505
82
padding the last block in a parser routine pointer, forward PRG processor, interface program file storage programmer's aid protected diskette, analyzing protected diskette, backup of a protection, DOS protection scheme, duplication
Q
file
25
random access file read errors read mode read/write errors reallocating sectors record size recording process recovering a hard error recovering a physically damaged diskette recovering a relative file recovering a scratched file recovering a soft error recovering an entire diskette recovering an unclosed file recovering from a full new recovering from a short new recovering scratched files recovery, relative file relative file relative file entries relative files relative file, recovery rename command renaming a locked file reset, warm
56 119 200 175 173 56 199 175-176 177 176 173-175 175 177 177-178 179-180 178-179 26 176 56 44 56 176 22-23 70 100
506
SAVE save and replace operation scratch command scratched scratched files, recovering scratching a scratching an unclosed sector sector filling sequence sector layout sector number sectors sequential data sequential file storage short new, recovering from side sector side sector signal, last block soft error, recovery soft errors ST status variable stopgap measure sychronization characters sync mark sync mark, placement sync mode
T
file
file
16 44-45 24 27 26 24 27 31 42 31 32 29 56 53 178-179 56 56 49 175 175 99 99 117 113-114 31-32, 115 114 114
tail gap time out error tracing a track number tracks tracks on a formatted diskette
U
file
U2 U1 unclosed file, recovering 177-178unclosed unscratching a user file storage using IP routines using the FDC routines USR
507
V V validate command validate routine, aborted variable, status VERIFY 26 25, 26-27 27 99 16
warm reset wild cards wild cards in file names write error write mode
508
1541 User's Guide Written by one of the authors of Inside Commodore DOS, this book proves to be the perfect complement to Inside Commodore DOS. This book shows you how to make more effective use of your 1541 disk drive. It expands the documentation that came with your 1541 disk drive. Practical information on setting up and operating the disk drive as well as the DOS which came with your drive is included. Diskette housekeeping is throughly discussed including how to format or initialize a disk, and renaming, copying, and combining files. Complete your knowledge of the 1541 with the 1541 User's Guide. $19 95 The Elementary Commodore
Explains the C o m m o d o r e 64 in simple, everyday language. How to hook it up, use the keyboard, and program in BASIC. Teaches about word processing, utilities and peripherals. $14.95
Intermediate Commodore
Takes you f r o m being a fledging programmer and teaches important principles so you can handle more complicated problems. Helps you take that step f r o m elementary BASIC programming to machine language programming, $l4.95
nk-l
Tired of paying $50, $75 or even $100 for quality products for your Commodore 64? If so, try our KWIK-WARE! line. At just $ 19 95, each KWIK-WARE! product delivers high quality at an unbeatable price, and all KWIK-WARE! products are fully compatable with one another.
KWIK-LOAD!, winner of Softsel's award for the hottest selling new utility of 1984, is the basis for all KWIK-WARE! products, all of which have the extra speed of KWIK-LOAD! built-in. KWIK-LOAD! loads and copies files over 300% faster than normal Commodore DOS. It includes KWIK-COPY], a menu driven group of utilities which lets you perform DOS commands, check drive speed, edit sectors on disk, and alphabetize disk directories. KWIK-PAD!, a co-resident secretary program. This handy utility features a built-in calculator mode, memo pad, appointment book, calendar, address book and more! KWIK-WRITE! offers all the features of more costly word processors for a fraction of the price. Fully menu driven, KWIK- WRITEl features full fledged help screens, cut and paste, search and replace, print preview, screen displays up to 132 characters wide, a full range of embedded printer commands, and much more. KWIK-PHONE!, a complete telecommunications package featuring simple command menus. It is compatible with acoustic or direct-connect modems. The exclusive, built-in KWIK-MAILl feature will answer your phone automatically to send or receive text, programs or hi-res graphics and a built-in phone book stores thousands of phone numbers. Be sure to watch for KWIK-CALC!, a complete spreadsheet package with over 2,500,000 available cells. KWIK-CALC! offers the versatility of the more expensive spreadsheet programs. KWIK-CALC! is priced at $24.95. At DATAMOST, we didn't just ask why software is so expensive, we did something about it. KWIK-WARE! only $19 95 per package.
AnkH Dare to adventure in the MetaReal world, guiding your "Surrogate Other" through a maze of 64 rooms. Test your reasoning, logic, and intellect in this hi-res arcade-action puzzle. Can you find the answer, using only your "other" and your wits? $19.95 AzteC Guide your intrepid adventurer through eight levels of an ancient Aztec ruin, in quest of the elusive golden idol. Use your wit, dexterity, and weapons to fight off spiders, scorpions, cobras, natives, and worse. $19 95 Jet-B00t Jack HelpJack gather up all the notes using only his custom Jet-Boots and your dexterity, in his adventure through the wonderous Music Machine. A 10-screen, multiskill level obstacle course guaranteed to test the expertise of even the most seasoned game player. $19 95 Mabel's Mansion In this hi-res, real time adventure, Barney the Bellhop must find his inheritance in his Aunt Mabel's haunted house. Hidden among the 90 rooms of her house are numerous treasures, guarded by hundreds ofghosts, ghouls, and monsters. $19-95
Available only to readers of this book. Send $24.95 plus $2.00 for shipping and handling (California residents add 6V2% sales tax) to:
mD A T A M O S T
Please RUSH me Inside Commodore DOS diskette. I have enclosed my check or money order for $24.95 plus $2.00 shipping and handling (California residents add $1.62 for sales tax).
State )
Zip