Volume 1 -- Issue 12 | September 1981

In This Issue...

- Field Input Routine for Applesoft
- CHRGET and CHRGOT in Applesoft
- Leaving the S-C Assembler II
- A New, Fancier .AS Directive
- Commented Listing of DOS 3.3 RWTS

Quarterly Disk #4

The fourth Quarterly Disk is now ready, containing all the source code from issues 10 through 12. The cost is only $15, and it will save you a lot of typing and possible searching for typos. All previous Quarterly Disks are still available, at the same price.

Renewing subscriptions

The 4-digit number in the upper right corner of your mailing label is the expiration date of your subscription. The first two digits are the year, and the last two digits are the month of the last issue you have paid for. If it says "8109", this is your last issue. Unless, of course, I receive your renewal check for $12. If your label says 8111 or less, now is the time to renew!

More about the Firmware Card in Slot 4

Michael Sanders' DOS patch for using the Firmware card in slot 4 is really nice. A lot of you have written or called about it, and I use it myself now. In fact, I have changed my HELLO programs to do the patch. All it takes is two POKEs:

         10 POKE 42424,192 : POKE 42432,193

I like doing it this way a lot better than INITting a disk with a modified DOS. If you want to test for the presence of a card before patching, you can do it like this:

         10 FOR I = 768 TO 817: READ A: POKE I,A: NEXT: CALL 768
         20 DATA 173,192,192,162,2,189,0,224,221,44,3,208,16,202,16,245,
            162,192,142,184,165,232,142,192,165,173,193,192,96,162,2,189,
            0,224,221,47,3,208,242,202,16,245,48,228,32,0,240,78,40,241
         30 TEXT: HOME: PRINT CHR$(4)"CATALOG

Field Input Routine for Applesoft | Bob Potts

Inputting strings to an Applesoft program is normally a simple task. What could be easier than "INPUT A$"? But, that method will not allow commas or colons.

Another easy way is to use GET C$ for each character, and append them to a string using A$=A$+C$. But, by the time you add the testing for each input character to find the end of input and other possible control characters, the routine can be terribly slow. Furthermore, it eats up string space like crazy; eventually Applesoft garbage collection starts, and the program dies for a while. Here is the kind of loop I am talking about:

         10 A$=""
         20 GET C$
         30 <perform various tests on C$>
         40 A$=A$+C$:PRINT C$;
         50 GO TO 20

As the string increases in length, the speed decreases dramatically. In fact, some characters may be lost if you are a fast typist.

One way to correct this is to use a machine language routine to input each keystroke, test it, and build a string for the Applesoft program. Such a routine was printed in "Apple Assembly Line" issue #7 (April, 1981), pages 6-8. But that routine used the monitor's RDLINE subroutine to input the string. I needed a routine more adapted to inputting a series of fields, using the screen in a "fill-in-the-blanks" mode.

The following program was designed for use in the various branches of the Bank of Louisville. The Apple is used to calculate loans, print the installment notes, and to enter loan applications. A loan application involves filling in the blanks on several screens full of prompts.

To use the input routine, you first position the cursor to the start of field using VTAB and HTAB; then set a field length using the SCALE= statement, and a field code using the ROT= statement. The actual call to the input routine is done with "&INPUT" and the name of your string. Here is an example for inputting a 5-character field starting in column 10 of line 7:

         10 VTAB 7 : HTAB 10 : SCALE=5 : ROT = 0 : &INPUT A$

The input routine allows skipping from field to field, either forward or backward through a form. Backspace and copy (right arrow) are supported. Filling up a field, or hitting RETURN within a field, finish that field and return the value to Applesoft. An EXIT CODE tells the Applesoft program whether a value was returned in the string or some other exit was chosen. You access the exit code with a PEEK(224). Here are the four exit codes and their meanings:

         EXIT CODE = 0     Field was filled or RETURN typed.
                   = 1     ESCAPE was typed at beginning of field.
                   = 2     CTRL-F was typed at beginning of field.
                   = 3     Left Arrow (backspace) was typed
                           at beginning of field.

If the exit code is zero, then the field data you typed is in your string. Otherwise, the string's value is not changed. Finishing a field by either filling it up or hitting RETURN puts the field data into your string, and I then advance to the next field on the form. I use an exit code of 3 (backspace at beginning of field) to mean that the Applesoft program should go back to the previous field on the current form.

How you use the exit codes of 1 and 2 is up to you. You might use an ESCAPE (exit code = 1) to abort the form-filling and return to a main menu. The ESCAPE is now only recognized if you are at the beginning of the field and the field code is non-zero. Of course, you could change that. You might use the control-F to mean you are finished with the current form.

How Does It Work?

Line 1110 sets the origin to $0300. If you already have something else in page 3, you can change the origin to whatever suits your fancy. Just remember to set the correct values for HIMEM and LOMEM to protect it from Applesoft, and vice versa.

Lines 1380-1440 install the ampersand vector. If you BRUN the program, this code is executed. If you BLOAD it, then CALL 768 will execute it. You only have to execute this once in your program. Once done, any occurrence of an ampersand statement in your program will branch to INPUT.FIELD, at line 1460.

Lines 1460-1500 check for the keyword "INPUT", and a string variable name. The three routines (and others used in this program) starting with "AS." are in the Applesoft ROMs. AS.SYNCHR compares the current character with what is in the A-register; if different you get SYNTAX ERROR, and if the same the character pointer is advanced. AS.PTRGET scans a variable name and finds its descriptor in memory. AS.CHKSTR makes sure that the variable is a string (if not you get TYPE MISMATCH). At this point the address of the string descriptor is in $83,84. The address in $83,84 points to 3 bytes which tell the length and address of the string's contents.

Lines 1520-1690 test the input character and branch accordingly. I use MON.RDKEY to read the character, which means that the data could come from any I/O slot as well as the normal Apple Keyboard. You could add more tests here, or remove some. If it is a printing character, we fall into lines 1730-1810 to store the character in the input buffer and on the screen. If the filed is now full, line 1810 jumps to the routine which passes the data to Applesoft. Note that characters stored in the input buffer have the high-bit equal to zero (Applesoft likes them that way). Characters written on the screen have the high-bit set to one, so that they print in NORMAL video.

Lines 1920-1990 handle the backspace character. If you are at the beginning of a field, the routine will return with an exit code of 3. Otherwise, the current character will be replace on the screen with an underline character, and the cursor will be backed up.

Lines 2030-2050 handle the right arrow. Normally this just copies over a character on the screen. Characters are picked up from the screen image, and the treated just as though they came from the keyboard. Note that the right arrow will not advance over an underline character.

Lines 2090-2140 handle ESCAPE. As I mentioned earlier, ESCAPE is ignored unless it is typed when the cursor is at the beginning of the field, and the field code is non-zero. This is the only use for the field code in the input routine presented here, but you might think of many more uses and make your own modifications.

Lines 2180-2190 make Applesoft allocate some space for the string in the normal string data space. Then lines 2200-2270 set up the string variable's descriptor to point to this space. Lines 2280-2310 move the string data from the input buffer up to the new place. This code was copied from the "Fast String Input Routine" in AAL #7.

The input routine is presented here in a very simple form; I leave it up to you to modify it to suit your most demanding applications.

     1000  *--------------------------------
     1010  *      FIELD INPUT SUBROUTINE
     1020  *      ----------------------
     1030  *      BY ROBERT W. POTTS
     1040  *      BANK OF LOUISVILLE
     1050  *      P. O. BOX 1101
     1060  *      LOUISVILLE, KY 40201
     1070  *
     1080  *      MODIFIED BY BOB SANDER-CEDERLOF
     1090  *      FOR THE "APPLE ASSEMBLY LINE"
     1100  *---------------------------------
     1110         .OR $300
     1120  *--------------------------------
     1130  MON.CH     .EQ $24      MONITOR HORIZONTAL
     1140  MON.BASL   .EQ $28
     1150  SPC.PNTR   .EQ $71,72
     1160  STR.PNTR   .EQ $83,84
     1170  *---------------------------------
     1180  CT         .EQ $E1      CHARACTER COUNT
     1190  FL         .EQ $E7      FIELD LENGTH  (SET BY "SCALE=FL")
     1200  FLDCOD     .EQ $F9      FIELD CODE    (SET BY "ROT=FC")
     1210  EXITCODE   .EQ $E0      PEEK (224) TO SEE EXIT CODE
     1220  *---------------------------------
     1230  INPUT.BUFFER .EQ $0200
     1240  AMPER.VECTOR .EQ $03F5
     1250  *---------------------------------
     1260  MON.RDKEY  .EQ $FD0C    MONITOR CHAR INPUT
     1270  MON.COUT   .EQ $FDED
     1280  MON.BS     .EQ $FC10    MONITOR BACKSPACE
     1290  *---------------------------------
     1300  AS.CHKSTR  .EQ $DD6C
     1310  AS.SYNCHR  .EQ $DEC0
     1320  AS.PTRGET  .EQ $DFE3
     1330  AS.GETSPA  .EQ $E452
     1340  AS.MOVSTR  .EQ $E5E2
     1350  *---------------------------------
     1360  *  SET UP AMPERSAND VECTOR
     1370  *--------------------------------
     1380  SETUP  LDA #$4C     JMP OPCODE
     1390         STA AMPER.VECTOR
     1400         LDA #INPUT.FIELD
     1410         STA AMPER.VECTOR+1
     1420         LDA /INPUT.FIELD
     1430         STA AMPER.VECTOR+2
     1440         RTS
     1450  *---------------------------------
     1460  INPUT.FIELD
     1470         LDA #$84     "INPUT" TOKEN
     1480         JSR AS.SYNCHR  REQUIRE "INPUT" OR SYNTAX ERROR
     1490         JSR AS.PTRGET  GET STRING VARIABLE
     1500         JSR AS.CHKSTR  REQUIRE STRING OR MISMATCH
     1510  *---------------------------------
     1520         LDA #0       ZERO OUT CHARACTER COUNT
     1530         STA CT
     1540  .1     JSR MON.RDKEY    GET CHARACTER
     1550  .2     AND #$7F     APPLESOFT STYLE
     1560         CMP #$06     CONTROL-F?
     1570         BEQ .3       YES
     1580         CMP #$08     BACKSPACE?
     1590         BEQ .4       YES
     1600         CMP #$0D     RETURN?
     1610         BEQ .7       YES, END OF FIELD
     1620         CMP #$15     RIGHT ARROW?
     1630         BEQ .5       YES
     1640         CMP #$1B     ESCAPE?
     1650         BEQ .6       YES
     1660         CMP #$20     SOME OTHER CONTROL CHARACTER?
     1670         BCC .1       YES, IGNORE IT
     1680         CMP #$5B     ACCEPTABLE PRINTING CHARACTER?
     1690         BCS .1       NO, IGNORE IT
     1700  *--------------------------------
     1710  *  GOT PRINTING CHARACTER - STORE IT
     1720  *--------------------------------
     1730         LDY CT       CHARACTER COUNTER
     1740         STA INPUT.BUFFER,Y  STORE IN STRING
     1750         ORA #$80     TURN ON HIGH BIT
     1760         JSR MON.COUT PRINT CHARACTER
     1770         INC CT       INCREMENT CHARACTER COUNT
     1780         LDA CT
     1790         CMP FL       IS FIELD FILLED UP?
     1800         BNE .1       NO, GET ANOTHER CHARACTER
     1810         BEQ .7       ...ALWAYS
     1820  *---------------------------------
     1830  *  HANDLE CONTROL-F
     1840  *---------------------------------
     1850  .3     LDA CT       ON FIRST CHARACTER?
     1860         BNE .1       NO, GET ANOTHER CHARACTER
     1870         LDA #2       EXIT CODE = 2
     1880         BNE .8       ...ALWAYS
     1890  *---------------------------------
     1900  *  HANDLE BACKSPACE
     1910  *---------------------------------
     1920  .4     LDA #3       EXIT CODE = 3 IF IN 1ST CHAR
     1930         DEC CT       DECREMENT CHARACTER COUNTER
     1940         BMI .8       ON FIRST POSITION
     1950         JSR MON.BS   BACKSPACE
     1960         LDA #$DF     UNDERLINE
     1970         JSR MON.COUT PRINT IT
     1980         JSR MON.BS   BACKSPACE AGAIN
     1990         JMP .1       DO AGAIN
     2000  *---------------------------------
     2010  *  HANDLE RIGHT ARROW
     2020  *---------------------------------
     2030  .5     LDY MON.CH   YES, GET NEXT CHARACTER FROM SCREEN
     2040         LDA (MON.BASL),Y
     2050         JMP .2
     2060  *---------------------------------
     2070  *  HANDLE ESCAPE
     2080  *---------------------------------
     2090  .6     LDA FLDCOD   FIELD CODE = 0?
     2100         BEQ .1       YES, GET ANOTHER CHARACTER
     2110         LDA CT
     2120         BNE .1       NO, GET ANOTHER CHARACTER
     2130         LDA #1       EXIT CODE = 1
     2140         BNE .8       ...ALWAYS
     2150  *--------------------------------
     2160  *  STORE THE INPUT DATA IN THE STRING
     2170  *--------------------------------
     2180  .7     LDA CT       STRING LENGTH
     2190         JSR AS.GETSPA  GET SPACE IN STRING AREA
     2200         LDY #0       MOVE DATA INTO VARIABLE
     2210         STA (STR.PNTR),Y  LENGTH
     2220         LDA SPC.PNTR
     2230         INY
     2240         STA (STR.PNTR),Y  LO-BYTE OF ADDRESS
     2250         LDA SPC.PNTR+1
     2260         INY
     2270         STA (STR.PNTR),Y  HI-BYTE OF ADDRESS
     2280         LDX #INPUT.BUFFER
     2290         LDY /INPUT.BUFFER
     2300         LDA CT       LENGTH
     2310         JSR AS.MOVSTR
     2320         LDA #0       EXIT CODE = 0
     2330  .8     STA EXITCODE
     2340         RTS

Here is a brief sample showing how you might use the input routine to fill in five fields:

    10 PRINT CHR$(4)"BRUN B.INPUT ROUTINE"
    20 DIM V(5),H(5),L(5),T$(5),A$(5)
    30 FOR I = 1 TO 5: READ V(I),H(I),L(I),T$(I): NEXT
    40 DATA 5,7,30,NAME
    50 DATA 7,7,3,AGE
    60 DATA 7,27,3,WEIGHT
    70 DATA 9,7,12,STATE
    80 DATA 9,27,5,ZIP
    90 TEXT: HOME: VTAB 23: INVERSE: PRINT " TYPE CTRL-F WHEN
       FORM FINISHED ": NORMAL
    100 FOR I = 1 TO 5: VTAB V(I): HTAB H(I)-LEN(T$(I))-1: PRINT
        T$(I)" ";: FOR J=1 TO L(I): PRINT CHR$(95);: NEXT: NEXT
    110 I = 1
    120 VTAB V(I): HTAB H(I): SCAL=L(I): ROT=0
    130 & INPUT A$(I): XC=PEEK(224)
    140 ON XC+1 GOTO 200,300,400,500
    200 I=I+1: IF I>5 THEN 110
    210 GOTO 120
    300 END: REM ESCAPE
    400 REM CONTROL-F
    410 HOME: FOR I=1 TO 5: PRINT A$(I): NEXT: END
    500 REM BACKSPACE
    510 I=I-1: IF I=0 THEN I=5
    520 GOTO 120

CHRGET and CHRGOT in Applesoft | Bob Sander-Cederlof

On pages 13 and 14 of the September 1981 Kilobaud Microcomputing (Robert Baker's Pet-Pourri column) there is a good description of the CHRGET/CHRGOT duo. These two subroutines (really two entry points into one routine) seem to be common to the Microsoft Basics, at least the 6502 versions.

What are they? When Applesoft initializes itself one of the tasks is to copy a short subroutine into page zero, from $00B1 through $00C8. There is no difference between the PET and the Apple versions, except that the PET version is copied into $0070-0087. Here is the code:

     1000  *---------------------------------
     1010  *      APPLESOFT CHRGET/CHRGOT SUBROUTINES
     1020  *---------------------------------
     1030         .OR $00B1
     1040  *---------------------------------
     1050  TXTPTR .EQ $B8      INSIDE 'LDA' INSTRUCTION
     1060  *---------------------------------
     1070  CHRGET INC TXTPTR   INCREMENT ADDRESS OF NEXT CHARACTER
     1080         BNE CHRGOT
     1090         INC TXTPTR+1
     1095  *---------------------------------
     1100  CHRGOT LDA $8888    PICK UP THE NEXT CHARACTER
     1110         CMP #$3A     TEST IF COLON
     1120         BCS .1       YES, Z AND C SET, RETURN
     1130         CMP #$20     TEST IF BLANK
     1140         BEQ CHRGET   YES, IGNORE IT
     1150         SEC          DO DIGIT TEST
     1160         SBC #$30
     1170         SEC          SET Z IF VALUE WAS $00 (EOL TOKEN)
     1180         SBC #$D0     AND CLEAR CARRY IF DIGIT ($30-39)
     1190  .1     RTS

Almost every time Applesoft wants to look at a character from your program or even from the input buffer, it does so by calling this subroutine. The CHRGET entry increments the address used to pick up the next character, and then falls into CHRGOT. In either case, the character is picked up and several tests are performed. Blanks are passed over, ignored. Colon (end of statement) and $00 (end of line) set the Z status bit. Digits clear CARRY, non-digits set CARRY. The calling program can use these status bits. For example:

         JSR CHRGET
         BEQ END        BRANCH IF COLON OR END-OF-LINE
         BCC DIGIT      BRANCH IF CHAR IS DIGIT (0-9)

The article in Kilobaud suggests patching this routine at $00BA to jump to your own code. Your program can trap certain characters for special functions, in much the same way as the "&" is now handled by Applesoft. You just have to be sure that you execute the instructions your JMP overlayed before returning to the remainder of CHRGET. It appears that many of the enhancement packages available for PET Basic use this scheme.

Why use this patching scheme instead of the "&" for special functions? Because your special functions can be made to appear an integral part of the language, without the telltale ampersand. Because even special codes inside expressions or other statements can be trapped. Because you want to encode or otherwise obfuscate your program for security. Because you just want to be different. Of course, the disadvantage is that the entire operation of Applesoft is slowed down by the amount of time your extra testing takes, since every character retrieved by the interpreter will go through your routine as well as the standard CHRGET.

     1000  *---------------------------------
     1010  *      SAMPLE APPLESOFT FILTER PROGRAM
     1020  *---------------------------------
     1030         .OR $BA
     1040         JMP FILTER
     1050  *---------------------------------
     1060         .OR $300
     1070  FILTER CMP #'#      CHECK FOR "#" CHARACTER
     1080         BNE .1       NO, PASS UNMOLESTED
     1090         JSR WHATEVER.YOU.WANT
     1100         JMP $B1
     1110  .1     CMP #$3A     CHECK FOR COLON
     1120         BCS .2       YES, RETURN JUST CHRGET WOULD
     1130         JMP $BE      NO, RECONNECT WITH CHRGET
     1140  .2     RTS
     1150  *---------------------------------
     1160  WHATEVER.YOU.WANT
     1170         JSR $FBE2    RING BELL
     1180         RTS

Here is a sample patch program, just show how it is done. Any time the patch discovers a "#" character, it will ring the Apple's bell. The sample Applesoft lines show what I mean. If you want to try out the patch, assemble it and then call Applesoft. Then get to the monitor and patch CHRGET like this:

         ]CALL -151
         *BA:4C 00 03
         *3D0G

Then enter some Applesoft lines with embedded "#" characters, and RUN.

         ]LIST
         10 PRINT #1;#2;#3;#4;#5;#6;#7
         20 A#=3: B#=4: PRINT A+B

If you think of some really practical ways to use patches like this, let me know about them.

Leaving the S-C Assembler II | Bob Sander-Cederlof

How do you get out of the assembler? I suppose I could have made a QUIT or EXIT command, but I didn't. If you want to go to Applesoft or Integer BASIC, type FP or INT. You will then be instantly in the version of Basic you wanted. However, you will still be hooked into the Assembler's output subroutine. If you load a small program and LIST it, you will find that tapping the space bar will stop the listing and restart it, just as inside the assembler. Notice I said a "small" program; a large program might over-write part of the assembler, causing the computer to hang up.

What you must do is type FP or INT, and then PR#0. The PR#0 unhooks the assembler output routine, and you are free.

Now, if you are sure that you have not over-written the assembler with your Applesoft or Integer BASIC program, and you want to return to the assembler, you can do so by typing CALL 4096. I use this for going back and forth rapidly when I am testing &-routines and the like.

What if you want to leave the assembler to go to the monitor? First of all, remember that you can use all of the monitor commands without ever leaving the assembler, by typing a dollar sign and then the monitor command. But if you really want out, how do you get there? If you have an old monitor ROM (not AUTOSTART), hitting RESET will get you to the monitor. With the Autostart ROM, you can type $FF59G or $FF69G. The first will unhook DOS, while the second will leave DOS hooked in. (The second is the same as the Basic command CALL-151.) Still another way is to patch the Autostart ROM RESET vector at $3F2 (type "$3F2:69 FF 5A"), so that RESET enters the monitor.

And how do you get back to the assembler from the monitor, without disturbing or losing your source code? Simply type "1003G" and you will be there. If you type "1000G" you will also get to the assembler, but all your source code will be gone, just as though you had typed the "NEW" command.

A New, Fancier .AS Directive | Bob Sander-Cederlof

Many times I write text printing loops that depend on the sign bit of each byte to indicate the end of text. I might set up the text this way:

         .AS /THIS IS THE TEXT I WANT TO PRIN
         .AS -/T/

This assembles with the sign bits off (0) on all the characters of the text except the last one. I can terminate my printing loop by testing that bit. A little later, I will show you an example of just such a loop.

But when there are many messages, I get tired of using separate lines for the last character of each message! Why not have an assembler directive which automatically sets the sign bit of the last character the opposite of the sign bits of the rest of the message? Since Version 4.0 of the S-C Assembler II has a .US directive for me, the user, to program....

The only problem is that how to program for the .US directive has never been revealed. Until now.

The following little program will implement just the directive I want, and install it as the .US directive. It uses five programs inside the assembler (see lines 1100-1140). The code is patterned directly after the code for the .AS directive, which starts at $203C in most copies of Version 4.0.

NOTE: You should check your assembler to make sure that the four bytes starting at $203C are "A0 00 84 04"; if they are, you can use the same addresses for the five routines as I have shown here. (If not, send me your original Version 4.0 disk for a free update. Be sure to adequately protect the disk for shipping, because your new copy will come back on the same disk.)

Line 1000 sets the origin of the code to $0F00. You could use some other origin, like $0300, if you wish. Just be sure it is an area of memory that you will not be using for some other purpose wile you are assembling. Line 1010 directs the object code to a BRUNnable file named B.US.DIRECTIVE.

The code from 1160 to 1210 is executed when you BRUN B.US.DIRECTIVE. It stores the address of DIR.US in the .US vector at the beginning of the assembler. You can read a little about this on page 15 of the Version 4.0 update manual.

Lines 1030-1050 define a few variables. WBUF is the line buffer the assembler uses, starting at $0200. The assembler unpacks a line from the source code into this buffer, and then proceeds to analyze it. DLIM and HIBIT are temporary locations in page zero where I will save the delimiter character and the high-bit setting.

The meat of the directive is in lines 1230-1510. If you disassemble the code at $203C in the S-C Assembler II, you will see a marked similarity here. You might also try disassembling the code for the GNNB and GNC subroutines.

GNC retrieves the next character from WBUF and increments the pointer. The character is tested. Carry status is set if the end-of-line token was picked up. Equal status is set if a blank or end-of-line token was picked up. GNNB calls on GNC until a non-blank character is found. GNC returns with the character in the A-register, and the pointer to the next character in the Y-register.

Lines 1240-1310 scan from the end of the opcode field to try to find the delimiter. If no non-blank character is found after the opcode field, you will get the "BAD ADDRESS ERROR". If a minus sign is found, $80 is stored in HIBIT instead of $00. This value will be merged with every character between the delimiters, to set or clear the high-bit of each byte. When the delimiter is found, it is stored in DLIM.

Lines 1320-1350 check to make sure that there are some characters after the delimiter before the next occurrence of the delimiter. For example, if you write ".US //", I want to assemble no bytes and go on. If I find the end-of-line token, you will get the error message.

Lines 1360-1430 are a loop to output the bytes one by one. I have to look ahead to see if the next character is the delimiter again. If not, then I will output the current character (by now accessed with "LDA WBUF-2,Y", because Y has been advanced). If the next one is the delimiter, then the current one is the last character of the string; I will have to go to ".3", to handle the last character.

Lines 1450-1490 handle the last character of the string between the delimiters. The high-bit is first set just like all the rest of the bytes at line 1460, and then reversed with the EOR #$80 at line 1470.

There is no end to the detail we could get into by describing how EMIT, CMNT, and ERBA work. I will leave them for you to puzzle over at your leisure. (Can't give away the whole plot in chapter 1!)

     1000         .OR $F00
     1010         .TF B.US.DIRECTIVE
     1020  *---------------------------------
     1030  WBUF   .EQ $0200
     1040  DLIM   .EQ $DA
     1050  HIBIT  .EQ $04
     1060  *---------------------------------
     1070  *  THE FOLLOWING VALUES ARE FOR VERSION 4.0
     1080  *  OF S-C ASSEMBLER II (DISK)
     1090  *---------------------------------
     1100  GNNB   .EQ $1283    GET NEXT NON-BLANK CHAR
     1110  GNC    .EQ $128B    GET NEXT CHAR
     1120  CMNT   .EQ $188E    FINISH THE LINE
     1130  ERBA   .EQ $1932    ERROR: BAD ADDRESS
     1140  EMIT   .EQ $19FA    EMIT A BYTE OF OBJECT CODE
     1150  *---------------------------------
     1160  ACTIVATE.US
     1170         LDA #DIR.US  STORE ADDRESS IN .US VECTOR
     1180         STA $100D    INSIDE S-C ASSEMBLER II VER
     1190         LDA /DIR.US  DISK VERSION 4.0
     1200         STA $100E
     1210         RTS
     1220  *---------------------------------
     1230  DIR.US
     1240         LDY #0       START WITH HI-BIT EQUAL TO ZERO
     1250  .1     STY HIBIT    SET HI-BIT ZERO OR ONE
     1260         JSR GNNB     GET NEXT NON-BLANK AFTER OPCODE
     1270         BCS ERBA2    END OF LINE IS BAD NEWS
     1280         LDY #$80     IN CASE WE NEED HI-BIT OF ONE
     1290         CMP #$2D     CHECK FOR MINUS SIGN
     1300         BEQ .1       YES, WE NEED HI-BIT OF ONE
     1310         STA DLIM     NOT MINUS, MUST BE DELIMITER
     1320         JSR GNC      GET NEXT CHARACTER
     1330         BCS ERBA2    END OF LINE IS BAD NEWS
     1340         CMP DLIM     SEE IF DELIMITER ALREADY
     1350         BEQ .4       YES, NO STRING IN BETWEEN
     1360  .2     JSR GNC      GET NEXT CHARACTER
     1370         BCS ERBA2    END OF LINE IS BAD NEWS
     1380         CMP DLIM     SEE IF DELIMITER YET
     1390         BEQ .3       YES, FINISH UP AND RETURN
     1400         LDA WBUF-2,Y NO, GET PREVIOUS CHAR
     1410         ORA HIBIT    MERGE WITH SELECTED HI-BIT
     1420         JSR EMIT     EMIT THE OBJECT CODE BYTE
     1430         JMP .2       GO FOR ANOTHER ONE
     1440  *---------------------------------
     1450  .3     LDA WBUF-2,Y GET PREVIOUS CHAR
     1460         ORA HIBIT    MERGE WITH SELECTED HI-BIT
     1470         EOR #$80     TOGGLE HI-BIT SINCE LAST CHAR
     1480         JSR EMIT     EMIT THE OBJECT CODE BYTE
     1490  .4     JMP CMNT     FINISH PROCESSING THE LINE
     1500  *---------------------------------
     1510  ERBA2  JMP ERBA     BAD ADDRESS ERROR

The following program shows how I might use the new .US directive I have just built. It prints the line of text from line 1230 ten times on the screen. The .US directive assures that I can tell when I am at the end of the text string by looking at the sign bit. That is just what the BMI opcode at line 1110 is doing. Lines 1070, 1080, 1190, and 1200 are the looping code to make ten copies of the line. Lines 1090-1150 print the message except for the last character; lines 1170-1180 print that last character and a carriage return.

     1000  *---------------------------------
     1010  *      DEMONSTRATE USE OF .US DIRECTIVE
     1020  *---------------------------------
     1030  MON.COUT   .EQ $FDED
     1040  MON.CROUT  .EQ $FD8E
     1050  *---------------------------------
     1060  DEMO.US
     1070         LDA #10      DO 10 LINES
     1080         STA LINE.COUNT
     1090  .3     LDY #0
     1100  .1     LDA TEXT,Y   GET CHAR FROM TEXT STRING
     1110         BMI .2
     1120         ORA #$80     MAKE NORMAL VIDEO
     1130         JSR MON.COUT
     1140         INY          NEXT CHARACTER
     1150         BNE .1       ...ALWAYS
     1160  *---------------------------------
     1170  .2     JSR MON.COUT
     1180         JSR MON.CROUT
     1190         DEC LINE.COUNT
     1200         BNE .3
     1210         RTS
     1220  *---------------------------------
     1230  TEXT   .US /THIS IS MY MESSAGE/
     1240  LINE.COUNT .BS 1
     1250  *---------------------------------

Commented Listing of DOS 3.3 RWTS | Bob Sander-Cederlof

Last March I started out this series of DOS listings with the RWTS portion of DOS 3.2.1. Since then I have printed all of DOS 3.2.1 and DOS 3.3 from $B800 thru $BFFF, except for DOS 3.3 RWTS. Somehow it almost was overlooked, but here it is now.

There are minor differences between the two versions of RWTS, which you can find by comparing the listing from the March 1981 issue of AAL and this one. The differences start at line 1810. I suppose the changes are meant to be improvements, but most of them seem to make very little difference.

One critical major difference: DOS 3.2.1 and previous versions use sector numbers which are actually written in the headers. DOS 3.3 uses two different sets of sector numbers: physical and logical. The physical sector numbers are recorded in the sector header blocks; logical sector numbers are used in RWTS calls and File Manager calls. The translation is performed using the table at line 4280, which I have called the PHYSICAL.SECTOR.VECTOR. This table is accessed at line 3310: the logical sector number is in the Y-register, and indexes into the physical sector vector to pick up a physical sector number.

     1000  *---------------------------------
     1010  *      DOS 3.3 DISASSEMBLY   $BD00-BEAE
     1020  *      BOB SANDER-CEDERLOF       3-3-81
     1030  *---------------------------------
     1040  CURRENT.TRACK       .EQ $478
     1050  DRIVE.1.TRACK       .EQ $478 THRU 47F (INDEX BY SLOT)
     1060  DRIVE.2.TRACK       .EQ $4F8 THRU 4FF (INDEX BY SLOT)
     1070  SEARCH.COUNT        .EQ $4F8
     1080  RETRY.COUNT         .EQ $578
     1090  SLOT                .EQ $5F8
     1100  SEEK.COUNT          .EQ $6F8
     1110  *---------------------------------
     1120  PHASE.OFF           .EQ $C080
     1130  PHASE.ON            .EQ $C081
     1140  MOTOR.OFF           .EQ $C088
     1150  MOTOR.ON            .EQ $C089
     1160  ENABLE.DRIVE.1      .EQ $C08A
     1170  ENABLE.DRIVE.2      .EQ $C08B
     1180  Q6L                 .EQ $C08C
     1190  Q6H                 .EQ $C08D
     1200  Q7L                 .EQ $C08E
     1210  Q7H                 .EQ $C08F
     1220  *---------------------------------
     1230  SECTOR     .EQ $2D
     1240  TRACK      .EQ $2A
     1250  VOLUME     .EQ $2F
     1260  DRIVE.NO   .EQ $35
     1270  DCT.PNTR   .EQ $3C,3D
     1280  BUF.PNTR   .EQ $3E,3F
     1290  MOTOR.TIME .EQ $46,47
     1300  IOB.PNTR   .EQ $48,49
     1310  *---------------------------------
     1320  PRE.NYBBLE          .EQ $B800
     1330  WRITE.SECTOR        .EQ $B82A
     1340  READ.SECTOR         .EQ $B8DC
     1350  READ.ADDRESS        .EQ $B944
     1360  POST.NYBBLE         .EQ $B8C2
     1370  SEEK.TRACK.ABSOLUTE .EQ $B9A0
     1380  DELAY.LOOP          .EQ $BA00
     1390  *---------------------------------
     1400  ERR.WRITE.PROTECT   .EQ $10
     1410  ERR.WRONG.VOLUME    .EQ $20
     1420  ERR.BAD.DRIVE       .EQ $40
     1430  *---------------------------------
     1440         .OR $BD00
     1450         .TA $800
     1460  *---------------------------------
     1470  RWTS   STY IOB.PNTR SAVE ADDRESS OF IOB
     1480         STA IOB.PNTR+1
     1490         LDY #2
     1500         STY SEEK.COUNT  UP TO 2 RE-CALIBRATIONS
     1510         LDY #4
     1520         STY SEARCH.COUNT
     1530         LDY #1       POINT AT SLOT# IN IOB
     1540         LDA (IOB.PNTR),Y  SLOT# FOR THIS OPERATION
     1550         TAX
     1560         LDY #15      POINT AT PREVIOUS SLOT#
     1570         CMP (IOB.PNTR),Y  SAME SLOT?
     1580         BEQ .3       YES
     1590         TXA          SAVE NEW SLOT ON STACK
     1600         PHA
     1610         LDA (IOB.PNTR),Y  GET OLD SLOT#
     1620         TAX
     1630         PLA          STORE NEW SLOT #
     1640         PHA          INTO OLD SLOT# SPOT
     1650         STA (IOB.PNTR),Y
     1660  *---------------------------------
     1670  *      SEE IF OLD MOTOR STILL SPINNING
     1680  *---------------------------------
     1690         LDA Q7L,X    GO INTO READ MODE
     1700  .1     LDY #8       IF DATA DOES NOT CHANGE
     1710         LDA Q6L,X       FOR 96 MICROSECONDS,
     1720  .2     CMP Q6L,X       THEN THE DRIVE IS STOPPED
     1730         BNE .1       WOOPS! IT CHANGED!
     1740         DEY          TIME UP YET?
     1750         BNE .2       NO, KEEP CHECKING
     1760         PLA          GET NEW SLOT # AGAIN
     1770         TAX
     1780  *---------------------------------
     1790  .3     LDA Q7L,X    SET UP TO READ
     1800         LDA Q6L,X
     1810         LDY #8
     1820  .31    LDA Q6L,X    GET CURRENT DATA
     1830         PHA          7 CYCLE DELAY
     1840         PLA
     1850         PHA          7 CYCLE DELAY
     1860         PLA
     1870         STX SLOT
     1880         CMP Q6L,X    SEE IF DATA CHANGED
     1890         BNE .32      YES, IT CHANGED
     1900         DEY
     1910         BNE .31      KEEP WAITING
     1920  .32    PHP          SAVE ANSWER ON STACK
     1930         LDA MOTOR.ON,X   TURN ON MOTOR
     1940         LDY #6       COPY POINTERS INTO PAGE ZERO
     1950  .4     LDA (IOB.PNTR),Y
     1960         STA DCT.PNTR-6,Y
     1970         INY          DCT.PNTR .EQ $3C,3D
     1980         CPY #10      BUF.PNTR .EQ $3E,3F
     1990         BNE .4
     2000         LDY #3       GET MOTOR ON TIME FROM DCT
     2010         LDA (DCT.PNTR),Y
     2020         STA MOTOR.TIME+1  HIGH BYTE ONLY
     2030         LDY #2       GET DRIVE #
     2040         LDA (IOB.PNTR),Y
     2050         LDY #16      SEE IF SAME AS OLD DRIVE#
     2060         CMP (IOB.PNTR),Y
     2070         BEQ .5       YES
     2080         STA (IOB.PNTR),Y  UPDATE OLD DRIVE #
     2090         PLP          SET Z STATUS
     2100         LDY #0       TO FLAG MOTOR OFF
     2110         PHP
     2120  .5     ROR          CHECK LSB OF DRIVE #
     2130         BCC .6       DRIVE 2
     2140         LDA ENABLE.DRIVE.1,X
     2150         BCS .7       ...ALWAYS
     2160  .6     LDA ENABLE.DRIVE.2,X
     2170  .7     ROR DRIVE.NO SET SIGN BIT IF DRIVE 1
     2180         PLP          WAS MOTOR PROBABLY OFF?
     2190         PHP
     2200         BNE .9       NO, DEFINITELY ON
     2210  *---------------------------------
     2220  *      DELAY FROM 150 TO 180 MILLISECONDS,
     2230  *      DEPENDING ON WHAT GARBAGE IS IN A-REG
     2240  *---------------------------------
     2250         LDY #7       YES, WAIT A WHILE
     2260  .8     JSR DELAY.LOOP
     2270         DEY          BUT IT WORKS ANYWAY....
     2280         BNE .8
     2290         LDX SLOT     RESTORE SLOT#
     2300  *---------------------------------
     2310  .9     LDY #4       GET TRACK #
     2320         LDA (IOB.PNTR),Y
     2330         JSR SEEK.TRACK
     2340         PLP          WAS MOTOR DEFINITELY ON?
     2350         BNE PROCESS.COMMAND  YES, MOTOR ON
     2360         LDY MOTOR.TIME+1   SEE IF NEED TO WAIT
     2370         BPL PROCESS.COMMAND   NO
     2380  *---------------------------------
     2390  *      MOTOR WAS OFF, SO WAIT REST OF MOTOR ON TIME
     2400  *      FOR APPLE DISK II, MOTOR ON TIME IS 1 SECOND.
     2410  *      PART OF THIS TIME IS COUNTED DOWN WHILE SEEKING
     2420  *      FOR THE TRACK.
     2430  *---------------------------------
     2440  .10    LDY #18      ABOUT 100 MICROSECONDS PER TRIP
     2450  .11    DEY
     2460         BNE .11
     2470         INC MOTOR.TIME
     2480         BNE .10
     2490         INC MOTOR.TIME+1
     2500         BNE .10
     2510  *---------------------------------
     2520  *      MOTOR ON AND UP TO SPEED, SO LET'S
     2530  *      FIND OUT WHAT THE COMMAND IS AND DO IT!
     2540  *---------------------------------
     2550  PROCESS.COMMAND
     2560         LDY #12      GET COMMAND
     2570         LDA (IOB.PNTR),Y
     2580         BEQ .8       NULL COMMAND, LET'S LEAVE
     2590         CMP #4       FORMAT?
     2600         BEQ .9       YES
     2610         ROR          SET CARRY=1 IF READ, =0 IF WRITE
     2620         PHP          SAVE ON STACK
     2630         BCS .1       READ
     2640         JSR PRE.NYBBLE  WRITE
     2650  .1     LDY #48      UP TO 48 RETRIES
     2660         STY RETRY.COUNT
     2670  .2     LDX SLOT     GET SLOT NUMBER AGAIN
     2680         JSR READ.ADDRESS
     2690         BCC .5       GOOD ADDRESS READ
     2700  .21    DEC RETRY.COUNT
     2710         BPL .2       KEEP TRYING
     2720  .3     LDA CURRENT.TRACK  GET TRACK WE WANTED
     2730         PHA          SAVE IT
     2740         LDA #96      PRETEND TO BE ON TRACK 96
     2750         JSR SETUP.TRACK
     2760         DEC SEEK.COUNT
     2770         BEQ .6       NO MORE RE-CALIBRATES
     2780         LDA #4
     2790         STA SEARCH.COUNT
     2800         LDA #0       LOOK FOR TRACK 0
     2810         JSR SEEK.TRACK
     2820         PLA          GET TRACK WE REALLY WANT
     2830  .4     JSR SEEK.TRACK
     2840         JMP .1
     2850  *---------------------------------
     2860  .5     LDY $2E      TRACK# IN ADDRESS HEADER
     2870         CPY CURRENT.TRACK
     2880         BEQ .10      FOUND RIGHT TRACK
     2890         LDA CURRENT.TRACK
     2900         PHA          SAVE TRACK WE REALLY WANT
     2910         TYA          SET UP TRACK WE ACTUALLY FOUNG
     2920         JSR SETUP.TRACK
     2930         PLA          TRACK WE WANT
     2940         DEC SEARCH.COUNT
     2950         BNE .4       TRY AGAIN
     2960         BEQ .3       TRY TO RE-CALIBRATE AGAIN
     2970  *---------------------------------
     2980  *      DRIVE ERROR, CANNOT FIND TRACK
     2990  *---------------------------------
     3000  .6     PLA          REMOVE CURRENT.TRACK
     3010         LDA #ERR.BAD.DRIVE
     3020  .7     PLP
     3030         JMP ERROR.HANDLER
     3040  *---------------------------------
     3050  *      NULL COMMAND, ON THE WAY OUT....
     3060  *---------------------------------
     3070  .8     BEQ RWTS.EXIT
     3080  *---------------------------------
     3090  *      FORMAT COMMAND
     3100  *---------------------------------
     3110  .9     JMP FORMAT
     3120  *---------------------------------
     3130  *      READ OR WRITE COMMAND
     3140  *---------------------------------
     3150  .10    LDY #3       GET VOLUME# WANTED
     3160         LDA (IOB.PNTR),Y
     3170         PHA          SAVE DESIRED VOLUME# ON STACK
     3180         LDA VOLUME
     3190         LDY #14      STORE ACTUAL VOLUME NUMBER FOUND
     3200         STA (IOB.PNTR),Y
     3210         PLA          GET DESIRED VOLUME# AGAIN
     3220         BEQ .11      IF =0, DON'T CARE
     3230         CMP VOLUME   SEE IF RIGHT VOLUME
     3240         BEQ .11      YES
     3250         LDA #ERR.WRONG.VOLUME
     3260         BNE .7       UH OH!
     3270  *---------------------------------
     3280  .11    LDY #5       GET SECTOR# WANTED
     3290         LDA (IOB.PNTR),Y   (LOGICAL SECTOR NUMBER)
     3300         TAY          INDEX INTO PHYSICAL SECTOR VECTOR
     3310         LDA PHYSICAL.SECTOR.VECTOR,Y
     3320         CMP SECTOR
     3330         BNE .21      NOT THE RIGHT SECTOR
     3340         PLP          GET COMMAND FLAG AGAIN
     3350         BCC WRITE
     3360         JSR READ.SECTOR
     3370         PHP          SAVE RESULT; IF BAD, WILL BE COMMAND
     3380         BCS .21      BAD READ
     3390         PLP          THROW AWAY
     3400         LDX #0
     3410         STX $26
     3420         JSR POST.NYBBLE
     3430         LDX SLOT
     3440  RWTS.EXIT
     3450         CLC
     3460         .HS 24       "BIT" TO SKIP NEXT INSTRUCTION
     3470  *---------------------------------
     3480  ERROR.HANDLER
     3490         SEC          INDICATE AN ERROR
     3500         LDY #13      STORE ERROR CODE
     3510         STA (IOB.PNTR),Y
     3520         LDA MOTOR.OFF,X
     3530         RTS
     3540  *---------------------------------
     3550  WRITE  JSR WRITE.SECTOR
     3560         BCC RWTS.EXIT
     3570         LDA #ERR.WRITE.PROTECT
     3580         BCS ERROR.HANDLER   ...ALWAYS
     3590  *---------------------------------
     3600  *      SEEK TRACK SUBROUTINE
     3610  *      (A) = TRACK# TO SEEK
     3620  *      (DRIVE.NO) IS NEGATIVE IF DRIVE 1
     3630  *                AND POSITIVE IF DRIVE 2
     3640  *---------------------------------
     3650  SEEK.TRACK
     3660         PHA          SAVE TRACK#
     3670         LDY #1       CHECK DEVICE CHARACTERISTICS TABLE
     3680         LDA (DCT.PNTR),Y   FOR TYPE OF DISK
     3690         ROR          SET CARRY IF TWO PHASES PER TRACK
     3700         PLA          GET TRACK# AGAIN
     3710         BCC .1       ONE PHASE PER TRACK
     3720         ASL          TWO PHASES PER TRACK, SO DOUBLE IT
     3730         JSR .1       FIND THE TRACK
     3740         LSR CURRENT.TRACK  DIVIDE IT BACK DOWN
     3750         RTS
     3760  *---------------------------------
     3770  .1     STA TRACK
     3780         JSR GET.SLOT.IN.Y
     3790         LDA DRIVE.1.TRACK,Y
     3800         BIT DRIVE.NO   WHICH DRIVE?
     3810         BMI .2       DRIVE 1
     3820         LDA DRIVE.2.TRACK,Y
     3830  .2     STA CURRENT.TRACK   WHERE WE ARE RIGHT NOW
     3840         LDA TRACK    WHERE WE WANT TO BE
     3850         BIT DRIVE.NO WHICH DRIVE?
     3860         BMI .3       DRIVE 1
     3870         STA DRIVE.2.TRACK,Y   DRIVE 2
     3880         BPL .4       ...ALWAYS
     3890  .3     STA DRIVE.1.TRACK,Y
     3900  .4     JMP SEEK.TRACK.ABSOLUTE
     3910  *---------------------------------
     3920  *      CONVERT SLOT*16 TO SLOT IN Y-REG
     3930  *---------------------------------
     3940  GET.SLOT.IN.Y
     3950         TXA          SLOT*16 FROM X-REG
     3960         LSR
     3970         LSR
     3980         LSR
     3990         LSR
     4000         TAY          SLOT INTO Y
     4010         RTS
     4020  *---------------------------------
     4030  *      SET UP CURRENT TRACK LOCATION
     4040  *      IN DRIVE.1.TRACK OR DRIVE.2.TRACK VECTORS,
     4050  *      INDEXED BY SLOT NUMBER.
     4060  *
     4070  *      (A) = TRACK# TO BE SET UP
     4080  *---------------------------------
     4090  SETUP.TRACK
     4100         PHA          SAVE TRACK # WE WANT TO SET UP
     4110         LDY #2       GET DRIVE NUMBER FROM IOB
     4120         LDA (IOB.PNTR),Y
     4130         ROR          SET CARRY IF DRIVE 1, CLEAR IF 2
     4140         ROR DRIVE.NO MAKE NEGATIVE IF 1, POSITIVE IF 2
     4150         JSR GET.SLOT.IN.Y
     4160         PLA          GET TRACK #
     4170         ASL          DOUBLE IT
     4180         BIT DRIVE.NO  WHICH DRIVE?
     4190         BMI .1       DRIVE 1
     4200         STA DRIVE.2.TRACK,Y
     4210         BPL .2       ...ALWAYS
     4220  .1     STA DRIVE.1.TRACK,Y
     4230  .2     RTS
     4240  *---------------------------------
     4250  FORMAT
     4260  *---------------------------------
     4270         .BS $BFB8-*
     4280  PHYSICAL.SECTOR.VECTOR
     4290         .HS 000D0B09070503010E0C0A080604020F

Apple Assembly Line is published monthly by S-C SOFTWARE, P. O. Box 280300, Dallas, TX 75228. Phone (214) 324-2050 Subscription rate is $12/year, in the U.S.A., Canada, and Mexico. Other countries add $6/year for extra postage. Back issues are available for $1.20 each (other countries add $1 per back issue for postage). All material herein is copyrighted by S-C SOFTWARE, all rights reserved. Unless otherwise indicated, all material herein is authored by Bob Sander-Cederlof. (Apple is a registered trademark of Apple Computer, Inc.)