Volume 1 -- Issue 7 | April 1981

As of today the total distribution of the Apple Assembly Line is nearly 350. Let's shoot for 1000 by the end of 1981! I will have a full page ad in the next eight issues of NIBBLE, so I think 1000 is a reasonable goal. Thank you for your support!

In This Issue...

- Text File I/O in Assembly Language Programs
- Applesoft Internal Entry Points
- Patch S-C Assembler II for More Errors
- Fast String Input Routine for Applesoft
- Hiding Things Under DOS
- Commented Listing of DOS 3.2.1 Format
- Commented Listing of DOS 3.3 Format
- Substring Search for Applesoft

Cross Reference (XREF) for S-C ASSEMBLER II

Bob Kovacs has a new product, one which many of you have asked me for. It enables you to produce a complete cross reference listing of all symbols used in an assembly language program. See his ad on page 7 for a description and ordering information. I am honored to have three companies (Rak-Ware, Decision Systems, and Flatland Software) producing software to complement my assembler!

80 Columns on Your Printer

For some reason unknown to me Apple's Parallel Interface Card comes with at least three different ROM's. There seems to me no indication on the package which one you are getting, and no listing in the manual of the exact ROM on the board. This leads to confusion, because some ROM versions will print 80-column assembly listings at the drop of a hat (Just type PR#1 and ASM, and you have it!); but others require special treatment.

If you have the latter type, I have found that this works:

         :PR#1        (assuming slot # 1)
         :$579:50     ($578 + slot#     )
         :ASM

Text File I/O in Assembly Language Programs | Bob Sander-Cederlof

A surprisingly large number of people have written or called to ask the same question:

> "How can I read or write a text file from my program? I know I can issue OPEN, READ, WRITE, and CLOSE commands just like in Applesoft -- by outputting a control-D and the command string. But after that, where is the data?"

It is really very simple, and after I tell you, you may be just as embarrassed as they were!

Remember that in Applesoft, after opening a file and setting it up to read with the OPEN and READ commands, you actually read it with normal INPUT statements. In assembly language you do the same thing. You can either input a line by calling the monitor routine at $FD6F, or you can read character-by-character by calling the character input routine at $FD0C. After a JSR $FD0C, the input character will be in the A-register. After a JSR $FD6F, the input line will be in the monitors buffer starting at $0200, and the X-register will contain the number of characters in the line (not counting the carriage return).

Also remember that after using the OPEN and WRITE commands, all you do in Applesoft to write on a text file is use the normal PRINT statement. In the same way, from assembly language, you just call the monitor print character routine at $FDED. The character to be written should be in the A-register, and then use JSR $FDED.

Here is a little program which opens a text file and reads it into a buffer at $4000. It demonstrates a few more tricks you might need to know, as well.

Lines 1180-1270 patch DOS so that it thinks you are executing an Applesoft program. (If you really are calling this from a RUNning Applesoft program, you can skip lines 1190 and 1200.) We want to be able to issue DOS commands by printing control-D and the command string, so we have to be RUNning. We want to be able to tell when the end-of-file comes without getting an "OUT OF DATA" error, so we turn on the Applesoft ON ERR flag and set it up to branch to our own END.OF.DATA routine.

Lines 1310-1350 print the DOS OPEN and READ commands. The message printer is a very simple loop at lines 1630-1690.

Lines 1380-1500 read the characters from the file and store them in a buffer at $4000. I save the stack pointer before the loop so I can restore it after the end-of-file occurs. Lines 1530-1570 restore the stack pointer, close the file, and return to DOS.

I really should clean up the mess I created with lines 1180-1270, but I will leave that as an exercise for the reader.

     1000  *---------------------------------
     1010  *      DEMONSTRATION OF READING A TEXT FILE
     1020  *---------------------------------
     1030  PROMPT.CHAR         .EQ $33
     1040  CURRENT.LINE.NO     .EQ $75,76
     1050  BUF.PNTR            .EQ $9D,9E
     1060  DOS.LANGUAGE.FLAG   .EQ $AAB6
     1070  ONERR.FLAG          .EQ $D8
     1080  DOS.ONERR.PNTR      .EQ $9D5A,9D5B
     1090  DOS.REENTRY         .EQ $3D0
     1100  MON.RDKEY           .EQ $FD0C
     1110  MON.COUT            .EQ $FDED
     1120  *---------------------------------
     1130  TEXT.READER
     1140  *---------------------------------
     1150  *      PATCH DOS SO END OF FILE WILL
     1160  *      BRANCH TO MY "END.OF.DATA"
     1170  *---------------------------------
     1180         LDA #1       TELL DOS WE ARE IN APPLESOFT
     1190         STA DOS.LANGUAGE.FLAG
     1200         STA CURRENT.LINE.NO+1  NOT IN DIRECT MODE
     1210         STA PROMPT.CHAR        NOT DIRECT MODE
     1220         LDA #$FF     TURN ON "ON ERR"
     1230         STA ONERR.FLAG
     1240         LDA #END.OF.DATA
     1250         STA DOS.ONERR.PNTR
     1260         LDA /END.OF.DATA
     1270         STA DOS.ONERR.PNTR+1
     1280  *---------------------------------
     1290  *      OPEN THE FILE
     1300  *---------------------------------
     1310         LDY #QOPEN-QTS
     1320         JSR QUOTE.PRINT
     1330         LDY #QREAD-QTS
     1340         JSR QUOTE.PRINT
     1350  *---------------------------------
     1360  *      READ THE FILE
     1370  *---------------------------------
     1380         TSX
     1390         STX OLD.STACK.PNTR
     1400         LDA #BUFFER
     1410         STA BUF.PNTR
     1420         LDA /BUFFER
     1430         STA BUF.PNTR+1
     1440  .1     JSR MON.RDKEY   READ CHARACTER
     1450         LDY #0
     1460         STA (BUF.PNTR),Y
     1470         INC BUF.PNTR
     1480         BNE .1
     1490         INC BUF.PNTR+1
     1500         BNE .1       ...ALWAYS
     1510  *---------------------------------
     1520  END.OF.DATA
     1530         LDX OLD.STACK.PNTR
     1540         TXS
     1550         LDY #QCLOSE-QTS
     1560         JSR QUOTE.PRINT
     1570         JMP DOS.REENTRY
     1580  *---------------------------------
     1590  *      PRINT A MESSAGE
     1600  *      MESSAGE STARTS AT QTS,Y
     1610  *      MESSAGE ENDS WITH 00 BYTE
     1620  *---------------------------------
     1630  QUOTE.PRINT
     1640  .1     LDA QTS,Y
     1650         BEQ .2
     1660         JSR MON.COUT
     1670         INY
     1680         BNE .1       ...ALWAYS
     1690  .2     RTS
     1700  *---------------------------------
     1710  QTS    .EQ *
     1720  QOPEN  .HS 84       CONTROL-D
     1730         .AS -/OPEN TESTFILE/
     1740         .HS 8D00
     1750  QREAD  .HS 84       CONTROL-D
     1760         .AS -/READ TESTFILE/
     1770         .HS 8D00
     1780  QCLOSE .HS 84       CONTROL-D
     1790         .AS -/CLOSE/
     1800         .HS 8D00
     1810  *---------------------------------
     1820  OLD.STACK.PNTR .BS 1
     1830  *---------------------------------
     1840  BUFFER     .EQ $4000
     1850  *---------------------------------

Applesoft Internal Entry Points | Bob Sander-Cederlof

An excellent article appeared just over a year ago (by the same title) in The Apple Orchard, Volume 1, Number 1, March/April 1980. John Crossley of Apple Computer, Inc. wrote it. He revealed most of the usable entry points within the Applesoft ROM, and many details on how they work and how to use them. If you don't have that magazine, go get one right away. They are available at some stores, through some local Apple clubs, and directly from the publisher (the Internatioal Apple Corps). There are a few typographical errors, but you should be able to figure them out by comparing with a disassembly.

To get you started, I have made up a list of my own which includes the starting addresses for all the keyword routines. I got these from the ROM itself. The keyword list starts at $D0D0, and a parallel list of addresses starts at $D000. The addresses in the list are all low-byte-first, and are all pointing to one byte before the actual start. That is because Applesoft branches to the appropriate routine by placing the address from this list on the stack and then using RTS (see AAL issue #1, page 11, for an explanation of this technique).

This chart shows all the token values for Applesoft, and the address where the token is processed.
     token   keyword  addr

    80  128  END      D870
    81  129  FOR      D766
    82  130  NEXT     DCF9
    83  131  DATA     D995
    84  132  INPUT    DBB2
    85  133  DEL      F331
    86  134  DIM      DFD9
    87  135  READ     DBE2
    88  136  GR       F390
    89  137  TEXT     F399
    8A  138  PR#      F1E5
    8B  139  IN#      F1DE
    8C  140  CALL     F1D5
    8D  141  PLOT     F225
    8E  142  HLIN     F232
    8F  143  VLIN     F241
    90  144  HGR2     F3D8
    91  145  HGR      F3E2
    92  146  HCOLOR=  F6E9
    93  147  HPLOT    F6FD
    94  148  DRAW     F769
    95  149  XDRAW    F76F
    96  150  HTAB     F7E7
    97  151  HOME     FC58
    98  152  ROT=     F721
    99  153  SCALE=   F727
    9A  154  SHLOAD   F775
    9B  155  TRACE    F26D
    9C  156  NOTRACE  F26F
    9D  157  NORMAL   F273
    9E  158  INVERSE  F277
    9F  159  FLASH    F280
    A0  160  COLOR=   F24F
    A1  161  POP      D96B
    A2  162  VTAB     F256
    A3  163  HIMEM:   F286
    A4  164  LOMEM:   F2A6
    A5  165  ONERR    F2CB
    A6  166  RESUME   F318
    A7  167  RECALL   F3BC
    A8  168  STORE    F39F
    A9  169  SPEED=   F262
    AA  170  LET      DA46
    AB  171  GOTO     D93E
    AC  172  RUN      D912
    AD  173  IF       D9C9
    AE  174  RESTORE  D849
    AF  175  &        03F5
    B0  176  GOSUB    D921
    B1  177  RETURN   D96B
    B2  178  REM      D9DC
    B3  179  STOP     D86E
    B4  180  ON       D9EC
    B5  181  WAIT     E784
     token   keyword  addr

    B6  182  LOAD     D8C9
    B7  183  SAVE     D8B0
    B8  184  DEF      E313
    B9  185  POKE     E77B
    BA  186  PRINT    DAD5
    BB  187  CONT     D896
    BC  188  LIST     D6A5
    BD  189  CLEAR    D66A
    BE  190  GET      DBA0
    BF  191  NEW      D649
    C0  192  TAB(
    C1  193  TO
    C2  194  FN
    C3  195  SPC(
    C4  196  THEN
    C5  197  AT
    C6  198  NOT
    C7  199  STEP
    C8  200  +
    C9  201  -
    CA  202  *
    CB  203  /
    CC  204  ^
    CD  205  AND
    CE  206  OR
    CF  207  >
    D0  208  =
    D1  209  <
    D2  210  SGN      EB91
    D3  211  INT      EC24
    D4  212  ABS      EBB0
    D5  213  USR      000A
    D6  214  FRE      E2DF
    D7  215  SCRN(    D413
    D8  216  PDL      DFCE
    D9  217  POS      E300
    DA  218  SQR      EE8E
    DB  219  RND      EFAF
    DC  220  LOG      E942
    DD  221  EXP      EF0A
    DE  222  COS      EFEB
    DF  223  SIN      EFF2
    E0  224  TAN      F03B
    E1  225  ATN      F09F
    E2  226  PEEK     E765
    E3  227  LEN      E6D7
    E4  228  STR$     E3C6
    E5  229  VAL      E708
    E6  230  ASC      E6E6
    E7  231  CHR$     E647
    E8  232  LEFT$    E65B
    E9  233  RIGHT$   E687
    EA  234  MID$     E691

Patch S-C Assembler II for More Errors | Bob Sander-Cederlof

Some of you have asked for a way to see all your errors at once. If you patch Version 4.0 in this simple way, you will see all error messages during one ASM, instead of aborting the assembly after the first error.

Look at $1752 to $1754; you should see 20 81 1A. If you do, then make this patch:

         :$1752:4C 8E 18

Now try an assembly of some source code with several errors in it. You will see all the errors on your screen. Or if your printer is on, they will all print.

Personally, I liked it better the other way. But if you never make more than one error per program, you won't be able to tell the difference!

Fast String Input Routine for Applesoft | Bob Sander-Cederlof

Yet another use for the imperious ampersand! This program will read a line from the keyboard or a text file into a string variable. It will accept commas and colons without complaint, too. No more "EXTRA IGNORED" messages, and much less chance of garbage collection tying things up.

The program is shown here with the origin set to $0300, the most popular place in your Apple. If that taxi is already full, you can change the origin to whatever you like. In fact, the subroutine itself is completely relocatable. You can put it anywhere in memory you like, just so you set $3F6 and 3F7 to point to it.

Lines 1160-1220 are executed if you BRUN a file with this program on it. They put a JMP GET into $3F5, so that the "&" will call my subroutine. Once this code is executed, you can execute statements like "&GET A$" to read a line into a string.

Lines 1240-1500 are the input subroutine. At line 1240 the token following the ampersand is tested; it should be $BE, which is the token for "GET". If not, JMP $DEC9 makes your screen say "SYNTAX ERROR"!

Lines 1270 and 1280 set up the address of the string variable in locations $83 and $84. We will use this later to tell Applesoft where the input line is.

Lines 1290-1360 change the prompt symbol to a bell (in case you backspace too much) and call on the monitor input routine to read a line. After the line is read, the prompt is restored to whatever it was before. The length of the input line is in the X-register, and the line itself is in the buffer starting at $0200.

Lines 1370 and 1380 call on Applesoft to set aside space for the input line in the string area. This may force garbage collection if you are about out of memory at the time. GETSPA leaves the address of the start of the slot set aside for our input line in locations $71 and $72.

Lines 1390-1460 store the length and address of the input line into the string variable. The address is of the slot GETSPA just reserved.

Lines 1470-1500 call on MOVSTR to copy the input line from the monitor's input buffer (at $0200) into the slot reserved by GETSPA.

Now if you want to read some data off the disk which might have commas and colons in it, you can do it like this:

         100 PRINT CHR$(4) "OPEN MY.FILE"
         110 PRINT CHR$(4) "READ MY.FILE"
         120 FOR I = 1 TO 10
         130 & GET A$(I)
         140 NEXT I

     1000  *---------------------------------
     1010  *      FAST INPUT STRING ROUTINE
     1020  *      &GET <STRING VARIABLE>
     1030  *      ACCEPTS ANY CHARACTER, UNLIKE NORMAL INPUT
     1040  *---------------------------------
     1050  AMPERSAND.VECTOR    .EQ $3F5
     1060  LENGTH .EQ $9D
     1070  SYNTAX.ERROR .EQ $DEC9
     1080  PTRGET .EQ $DFE3
     1090  GETSPA .EQ $E452
     1100  MOVSTR .EQ $E5E2
     1110  *---------------------------------
     1120  MON.PROMPT .EQ $33
     1130  MON.RDLINE .EQ $FD6F
     1140  *---------------------------------
     1150         .OR $300
     1160         LDA #$4C     JUMP INSTRUCTION
     1170         STA AMPERSAND.VECTOR
     1180         LDA #GET
     1190         STA AMPERSAND.VECTOR+1
     1200         LDA /GET
     1210         STA AMPERSAND.VECTOR+2
     1220         RTS
     1230  *---------------------------------
     1240  GET    CMP #$BE     GET TOKEN
     1250         BEQ .1       YES
     1260         JMP SYNTAX.ERROR
     1270  .1     JSR $B1
     1280         JSR PTRGET   GET STRING DESCRIPTOR
     1290         LDA MON.PROMPT
     1300         PHA
     1310         LDA #$87     BELL FOR PROMPT
     1320         STA MON.PROMPT
     1330         JSR MON.RDLINE   INPUT A LINE
     1340         PLA
     1350         STA MON.PROMPT
     1360         STX LENGTH   SAVE LENGTH
     1370         TXA
     1380         JSR GETSPA   GET SPACE IN STRING AREA
     1390         LDY #0       MOVE DATA INTO VARIABLE
     1400         STA ($83),Y  LENGTH
     1410         LDA $71
     1420         INY
     1430         STA ($83),Y  LO-BYTE OF ADDRESS
     1440         LDA $72
     1450         INY
     1460         STA ($83),Y  HI-BYTE OF ADDRESS
     1470         LDY /$200    SET UP TO COPY STRING DATA
     1480         LDX #$200    INTO STRING AREA
     1490         LDA LENGTH
     1500         JMP MOVSTR   COPY IT NOW

Hiding Things Under DOS | Rick Hatcher

In issue number 5/1980 of NIBBLE, a small article by William Reynolds III tells how to do something I have wondered about for a long time. That is how to move the HIMEM pointer down so that machine language code or something else can be put out of the way and protected. For example: I have a lower-case routine I like to use on key input; I also like to use the character display routine from Lawrence Hall of Science which is hooked into the control-Y pointer. This is one way to dump memory in both hex and ASCII. I have looked for protected areas but until now the only place seemed to be from $300 to $3CF. This is a little over 200 bytes, and I needed about 400.

Neil Konzen's Program Line Editor (from Call A.P.P.L.E.) moves the file buffers down and leaves space between the buffers and DOS...but the manual which I sneaked a look at does not tell how to do it. The article in NIBBLE on page 40 finally revealed the secret. The file buffers are located by a pointer at locations $9D00 and $9D01 (least significant byte first, as usual). A DOS routine at $A7D4 builds the buffers using this pointer and the value of MAXFILES (at $AA57). [note: all addresses assume a 48K system]

All you have to do is change the address at $9D00.9D01 and call the routine at $A7D4. I wanted to create a space of $200 bytes (512 decimal). The normal value at $9D00.9D01 is $9CD3. I changed it to $9AD3, and then typed A7D4G in the monitor. The value of HIMEM was automatically changed to $9400 from the usual $9600. The protected area is from $9B00 to $9CFF. The buffers are located from $9400 to $9AFF and DOS is located from $9D00 to BFFF. If a MAXFILES command is used it changes HIMEM but the buffer top at $9AFF stays unchanged.

To make space like this from an Applesoft program, here is all you need:

         100 POKE 40193,154
         110 POKE 40192,211
         120 CALL 42964

         OR

         100 POKE 40192,PEEK(40192)-2
         CALL 42964

It isn't so easy in Integer BASIC, because the routine moves HIMEM without moving the program down in memory. (Remember Integer BASIC programs are at the top of memory up against HIMEM; Applesoft programs are at the low end of memory.) The NIBBLE article gives a method for Integer BASIC, but I haven't tried it.

I use an Applesoft HELLO program which first does the three lines above, and then BRUNs or BLOADs the code I want to hide. The BRUN portion sets up the I/O hooks at $36.39 and sets up the control-Y vector at $3F8. I use the BLOAD if I want the code resident but not hooked in.

Once the space is made, it stays there. If you INIT a slave disk, the slave has the same change.

The NIBBLE article reveals a few more details about the buffers in which you may be interested.

Commented Listing of DOS 3.2.1 Format | Bob Sander-Cederlof

Here is the second installment of DOS disassembly, covering the area from $BEA0 through $BFFF. If you read the listing in last month's AAL carefully, you probably noted that it ended with the label definition "FORMAT", but no code followed. Well, here it is!

FORMAT turns a blank diskette into one with address headers recorded on every track. Otherwise, the disk is empty. No directory is written into track $11 yet, nor is any DOS recorded yet in tracks 0, 1, and 2. When you use the INIT command, the first step exectured is to format the disk; after formatting, a DOS image and empty directory are written; then your HELLO program is SAVEd.

The Apple Disk Interface depends on critical software timing to operate correctly. You will find many strange sequences of code (such as PHA, PLA, NOP, PHA, PLA between $BF47 and $BF4B) which are for timing purposes. If you are interested in counting cycles, the timing for each opcode-address mode combination are listed in the Quick Reference Card that came with your S-C ASSEMBLER II Version 4.0.

     1000  *      .LIST OFF
     1010  *---------------------------------
     1020  *      DOS 3.2.1 DISASSEMBLY $BEA0-BFFF
     1030  *      BOB SANDER-CEDERLOF      3-26-81
     1040  *---------------------------------
     1050  CURRENT.TRACK       .EQ $478
     1060  *---------------------------------
     1070  PHASE.OFF           .EQ $C080
     1080  PHASE.ON            .EQ $C081
     1090  MOTOR.OFF           .EQ $C088
     1100  MOTOR.ON            .EQ $C089
     1110  ENABLE.DRIVE.1      .EQ $C08A
     1120  ENABLE.DRIVE.2      .EQ $C08B
     1130  Q6L                 .EQ $C08C
     1140  Q6H                 .EQ $C08D
     1150  Q7L                 .EQ $C08E
     1160  Q7H                 .EQ $C08F
     1170  *---------------------------------
     1180  SECTOR     .EQ $2D
     1190  VOLUME     .EQ $2F
     1200  TRACK.CNTR .EQ $41
     1210  DATA.CNTR  .EQ $46
     1220  SYNC.CNT   .EQ $47
     1230  CONST.AA   .EQ $4A
     1240  FILL.CNTR  .EQ $4B
     1250  FMT.SECTOR .EQ $4B
     1260  *---------------------------------
     1270  READ.ADDRESS        .EQ $B965
     1280  SEEK.TRACK.ABSOLUTE .EQ $BA1E
     1290  RWTS.EXIT           .EQ $BE37
     1300  ERROR.HANDLER       .EQ $BE39
     1310  *---------------------------------
     1320  ERR.BAD.DRIVE       .EQ $40
     1330  *---------------------------------
     1340         .OR $BEA0
     1350         .TA $800
     1360  *---------------------------------
     1370  FORMAT LDA #128     SET CURRENT TRACK REAL HIGH
     1380         STA CURRENT.TRACK   SO DRIVE WILL HOME
     1390         LDA #0              TO TRACK 0
     1400         STA TRACK.CNTR  INIT COUNTER FOR INIT ROUTINE
     1410         JSR SEEK.TRACK.ABSOLUTE
     1420  *---------------------------------
     1430         LDA #$AA     SAVE $AA IN PAGE ZERO FOR TIMING
     1440         STA CONST.AA
     1450  *---------------------------------
     1460  *      FILL ENTIRE TRACK WITH SYNC BYTES
     1470  *---------------------------------
     1480         LDY #80      START WITH 80 SYNC-BYTES
     1490  FILL.TRACK.WITH.SYNC
     1500         STY SYNC.CNT  # OF SYNC BYTES BETWEEN SECTORS
     1510         LDA #39      WRITE SYNC'S OVER ENTIRE TRACK
     1520         STA FILL.CNTR
     1530         LDA Q6H,X    GET READY TO WRITE
     1540         LDA Q7L,X
     1550         LDA #$FF     WRITE $FF EVERYWHERE
     1560         STA Q7H,X    ALL SET TO WRITE....
     1570         CMP Q6L,X
     1580         BIT $00      DELAY 3 CYCLES
     1590  .1     DEY
     1600         BEQ .3
     1610         PHA
     1620         PLA          THESE ARE JUST FOR TIMING
     1630         NOP          NEED 27 CYCLES BTWN WRITES
     1640  .2     PHA
     1650         PLA
     1660         NOP
     1670         NOP
     1680         STA Q6H,X    WRITE SYNC BYTE
     1690         CMP Q6L,X
     1700         BCS .1       ...ALWAYS
     1710  .3     DEC FILL.CNTR  TRACK FULL YET?
     1720         BNE .2       NO
     1730  *---------------------------------
     1740  *      WRITE 13-SECTOR HEADERS ON TRACK
     1750  *
     1760  *          EACH SECTOR CONSISTS OF AN ADDRESS BLOCK
     1770  *          AND A DATA BLOCK.
     1780  *          ADDRESS:  D5 AA B5 V1 V2 T1 T2
     1790  *                    S1 S2 C1 C2 DE AA EB
     1800  *          DATA:     FORMATTED TO ALL SYNC BYTES
     1810  *---------------------------------
     1820  FORMAT.TRACK
     1830         LDY SYNC.CNT  # SYNC BYTES BTWN SECTORS
     1840         NOP
     1850         NOP
     1860  .1     BNE .4       ...ALWAYS
     1870  *---------------------------------
     1880  .2     PHA          WRITE SYNC BYTES BEFORE SECTOR
     1890         PLA
     1900         PHA
     1910         PLA
     1920         CMP ($00,X)  DELAY 6 CYCLES
     1930  .4     NOP
     1940  .5     STA Q6H,X    WRITE NEXT SYNC BYTE
     1950         CMP Q6L,X
     1960         DEY
     1970         BNE .2
     1980  *---------------------------------
     1990         LDA #$D5     WRITE D5 AA B5
     2000         JSR WRITE.BYTE.2
     2010         LDA #$AA
     2020         JSR WRITE.BYTE.3
     2030         LDA #$B5
     2040         JSR WRITE.BYTE.3
     2050         LDA VOLUME   WRITE VOLUME, TRACK, AND SECTOR
     2060         JSR WRITE.BYTE.1
     2070         LDA TRACK.CNTR
     2080         JSR WRITE.BYTE.1
     2090         LDA FMT.SECTOR
     2100         JSR WRITE.BYTE.1
     2110         LDA VOLUME   COMPUTE CHECKSUM
     2120         EOR TRACK.CNTR
     2130         EOR FMT.SECTOR
     2140         PHA          WRITE CHECKSUM
     2150         LSR
     2160         ORA CONST.AA   #$AA, FOR TIMING
     2170         STA Q6H,X
     2180         CMP Q6L,X
     2190         PLA
     2200         ORA #$AA
     2210         JSR WRITE.BYTE.2
     2220         LDA #$DE     WRITE DE AA EB
     2230         JSR WRITE.BYTE.3
     2240         LDA #$AA
     2250         JSR WRITE.BYTE.3
     2260         LDA #$EB
     2270         JSR WRITE.BYTE.3
     2280         LDA #$FF     WRITE MORE SYNC BYTES
     2290         JSR WRITE.BYTE.3
     2300         LDY #2       FILL WHOLE DATA BLOCK WITH $FF
     2310         STY DATA.CNTR
     2320         LDY #173
     2330         BNE .7       ...ALWAYS
     2340  .6     DEY          FINISHED?
     2350         BEQ .8       YES, AT LEAST THIS GROUP
     2360         PHA          23 CYCLES PER BYTE
     2370         PLA
     2380         NOP
     2390  .7     PHA
     2400         PLA
     2410         STA Q6H,X
     2420         CMP Q6L,X
     2430         BCS .6       ...ALWAYS
     2440  .8     DEC DATA.CNTR  FINISHED?
     2450         BNE .7       NOT YET, DO SECOND GROUP
     2460  *---------------------------------
     2470         LDY SYNC.CNT
     2480         CLC
     2490         BIT $00      DELAY
     2500         STA Q6H,X
     2510         LDA Q6L,X
     2520         LDA FMT.SECTOR   COMPUTE NEXT SECTOR #
     2530         ADC #10      SKEW FACTOR = 10
     2540         STA FMT.SECTOR
     2550         SBC #12
     2560         BEQ CHECK.TRACK
     2570         BCS .9       STORE VALUE MODULO 13
     2580         .HS 2C       'BIT' OPCODE TO SKIP NEXT TWO BYTES
     2590  .9     STA FMT.SECTOR
     2600         LDA #$FF
     2610         JMP .5       DO NEXT SECTOR
     2620  *---------------------------------
     2630  *      CHECK WHETHER TRACK OVERLAPPED
     2640  *---------------------------------
     2650  CHECK.TRACK
     2660         PHA          TIME DELAY
     2670         PLA
     2680         LDY SYNC.CNT
     2690         LDA Q6H,X    SET UP TO READ
     2700         LDA Q7L,X    SENSE WRITE PROTECT
     2710         BMI .4       DRIVE ERROR
     2720         DEY
     2730  .1     PHA          DELAY LOOP
     2740         PLA
     2750         PHA
     2760         PLA
     2770         PHA
     2780         PLA
     2790         DEY          FINISHED WITH DELAY YET?
     2800         BNE .1       NO
     2810         JSR READ.ADDRESS
     2820         BCS .2       BAD READ
     2830         LDA SECTOR   SHOULD BE SECTOR 0
     2840         BEQ .3       YES!
     2850  .2     LDY SYNC.CNT  DIMINISH SYNC COUNT
     2860         DEY          AND TRY AGAIN
     2870         CPY #16      UNLESS NOT ENOUGH LEFT
     2880         BCC .4       DRIVE ERROR
     2890         JMP FILL.TRACK.WITH.SYNC
     2900  *---------------------------------
     2910  .3     INC TRACK.CNTR  NEXT TRACK
     2920         LDA TRACK.CNTR
     2930         CMP #35      FINISHED?
     2940         BCS .5       YES
     2950         ASL          DOUBLE FOR TRACK SEEK ROUTINE
     2960         JSR SEEK.TRACK.ABSOLUTE
     2970         LDY SYNC.CNT  BUMP SYNC.CNT BEFORE TRYING
     2980         INY           NEXT TRACK
     2990         INY
     3000         STY SYNC.CNT
     3010         JMP FILL.TRACK.WITH.SYNC
     3020  *---------------------------------
     3030  .4     LDA #ERR.BAD.DRIVE
     3040         JMP ERROR.HANDLER
     3050  *---------------------------------
     3060  .5     JMP RWTS.EXIT
     3070  *---------------------------------
     3080  *      SUBROUTINES TO WRITE BYTE ON DISK
     3090  *---------------------------------
     3100  WRITE.BYTE.1
     3110         PHA          ADDRESS BLOCK FORMAT
     3120         LSR
     3130         ORA CONST.AA
     3140         STA Q6H,X
     3150         CMP Q6L,X
     3160         PLA
     3170         CMP ($00,X)  DELAY 6 CYCLES
     3180         ORA #$AA
     3190  WRITE.BYTE.2
     3200         NOP
     3210  WRITE.BYTE.3
     3220         PHA
     3230         PLA
     3240         NOP
     3250         STA Q6H,X
     3260         CMP Q6L,X
     3270         RTS
     3280  *---------------------------------
     3290  *      VARIOUS ODDS AND ENDS
     3300  *---------------------------------
     3310         .HS 0160     LEFT OVER
     3320  PATCH1 JMP $A5DD
     3330  PATCH2 STA $AA63
     3340         STA $AA70
     3350         STA $AA71
     3360         RTS
     3370  PATCH3 JSR $A75B
     3380         STY $AAB7
     3390         RTS
     3400  PATCH4 JSR $AE7E    FROM $B377
     3410         LDX $B39B
     3420         TXS
     3430         JSR $A316
     3440         TSX
     3450         STX $B39B
     3460         LDA #9       "DISK FULL" ERROR
     3470         JMP $B385

Commented Listing of DOS 3.3 Format | Bob Sander-Cederlof

As promised three or four pages ago, here is my rendition of the DOS 3.3 Format routine.

By the way, there are a lot of differences between DOS 3.2.1 and DOS 3.3 FORMAT routines. Later in this issue of AAL you will find a commented listing of the DOS 3.3 version. If you compare the two, you will find at least these major differences:

1. DOS 3.2.1 formats 13 sectors per track, DOS 3.3 formats 16 sectors per track.
2. DOS 3.2.1 writes an address header followed by a long series of $FF bytes where the data should be; DOS 3.3 writes an address header followed by a standard data block (the data is all $00 bytes).
3. DOS 3.2.1 writes an address header starting with $D5AAB5; DOS 3.3 writes an address header starting with $D5AA96.
4. DOS 3.2.1 verifies correct format by trying to read sector 0 immediately after formatting the last sector; no other verification is made. DOS 3.3 tries to read EVERY sector just formatted; it does a complete check of the track.
5. DOS 3.2.1 writes the sectors in the order 0, 10, 7, 4, 1, 11, 8, 5, 2, 12, 9, 6, 3; DOS 3.3 writes them in sequential order 0, 1, 2, ... , 15.

     1000  *      .LIST OFF
     1010  *---------------------------------
     1020  *      DOS 3.3 DISASSEMBLY     $BEAF-BFFF
     1030  *      BOB SANDER-CEDERLOF        3-26-81
     1040  *---------------------------------
     1050  RETRY.COUNT         .EQ $578
     1060  *---------------------------------
     1070  PHASE.OFF           .EQ $C080
     1080  PHASE.ON            .EQ $C081
     1090  MOTOR.OFF           .EQ $C088
     1100  MOTOR.ON            .EQ $C089
     1110  ENABLE.DRIVE.1      .EQ $C08A
     1120  ENABLE.DRIVE.2      .EQ $C08B
     1130  Q6L                 .EQ $C08C
     1140  Q6H                 .EQ $C08D
     1150  Q7L                 .EQ $C08E
     1160  Q7H                 .EQ $C08F
     1170  *---------------------------------
     1180  SECTOR     .EQ $2D
     1190  CONST.AA   .EQ $3E
     1200  FMT.SECTOR .EQ $3F
     1210  VOLUME     .EQ $41
     1220  TRACK.CNTR .EQ $44
     1230  SYNC.CNT   .EQ $45
     1240  IOB.PNTR   .EQ $48,49
     1250  *---------------------------------
     1260  WRITE.SECTOR        .EQ $B82A
     1270  READ.SECTOR         .EQ $B8DC
     1280  READ.ADDRESS        .EQ $B944
     1290  RWTS.BUFFER         .EQ $BB00
     1300  WRITE.ADDRESS       .EQ $BC56
     1310  SEEK.TRACK          .EQ $BE5A
     1320  SETUP.TRACK         .EQ $BE95
     1330  *---------------------------------
     1340  ERR.CANT.FORMAT     .EQ $08
     1350  *---------------------------------
     1360         .OR $BEAF
     1370         .TA $800
     1380  *---------------------------------
     1390  FORMAT LDY #3       POINT AT VOLUME NUMBER
     1400         LDA (IOB.PNTR),Y
     1410         STA VOLUME
     1420         LDA #$AA     SET UP CONSTANT IN PAGE ZERO
     1430         STA CONST.AA FOR TIMING
     1440         LDY #86      CLEAR BUFFER TO ALL 00'S
     1450         LDA #0
     1460         STA TRACK.CNTR
     1470  .1     STA RWTS.BUFFER+255,Y
     1480         DEY          UPPER PORTION
     1490         BNE .1
     1500  .2     STA RWTS.BUFFER,Y
     1510         DEY          LOWER PORTION
     1520         BNE .2
     1530         LDA #80      SET UP AS THOUGH IN TRACK 80
     1540         JSR SETUP.TRACK
     1550         LDA #40      START WITH 40 SYNC'S BTWN SECTORS
     1560         STA SYNC.CNT
     1570  *---------------------------------
     1580  .3     LDA TRACK.CNTR
     1590         JSR SEEK.TRACK
     1600         JSR FORMAT.TRACK
     1610         LDA #ERR.CANT.FORMAT
     1620         BCS .5       ERROR
     1630         LDA #48      TRY UP TO 48 TIMES
     1640         STA RETRY.COUNT
     1650  .4     SEC
     1660         DEC RETRY.COUNT
     1670         BEQ .5       OUT OF RETRIES, ERRCODE=$30
     1680         JSR READ.ADDRESS
     1690         BCS .4       ERROR, TRY AGAIN
     1700         LDA SECTOR
     1710         BNE .4       MUST BE SECOTR 0
     1720         JSR READ.SECTOR
     1730         BCS .4       ERROR, TRY AGAIN
     1740         INC TRACK.CNTR   NEXT TRACK
     1750         LDA TRACK.CNTR
     1760         CMP #35      FINISHED?
     1770         BCC .3       NOT YET
     1780         CLC          INDICATE NO ERROR
     1790         BCC .6       ...ALWAYS
     1800  *---------------------------------
     1810  .5     LDY #13      POINT AT ERROR SLOT IN IOB
     1820         STA (IOB.PNTR),Y
     1830         SEC          FLAG ERROR
     1840  .6     LDA MOTOR.OFF,X   STOP DRIVE
     1850         RTS
     1860  *---------------------------------
     1870  *      FORMAT A TRACK
     1880  *---------------------------------
     1890  FORMAT.TRACK
     1900         LDA #0       START WITH SECTOR 0
     1910         STA FMT.SECTOR
     1920         LDY #128     EXTRA SYNC'S BEFORE FIRST SECTOR
     1930         BNE .2       ...ALWAYS
     1940  .1     LDY SYNC.CNT
     1950  .2     JSR WRITE.ADDRESS
     1960         BCS .10      ERROR, EXIT NOW
     1970         JSR WRITE.SECTOR
     1980         BCS .10      ERROR, EXIT NOW
     1990         INC FMT.SECTOR  NEXT SECTOR
     2000         LDA FMT.SECTOR
     2010         CMP #16      FINISHED WITH THIS TRACK?
     2020         BCC .1       NOT YET
     2030  *---------------------------------
     2040  *      VERIFY THE TRACK
     2050  *---------------------------------
     2060         LDY #15      START WITH SECOTR 15
     2070         STY FMT.SECTOR
     2080         LDA #48      RETRY UP TO 48 TIMES
     2090         STA RETRY.COUNT
     2100  .3     STA SECTOR.FLAGS,Y  CLEAR ALL THE SECTOR FLAGS
     2110         DEY
     2120         BPL .3
     2130         LDY SYNC.CNT DELAY A WHILE
     2140  .4     JSR .10      12 CYCLES
     2150         JSR .10      12 CYCLES
     2160         JSR .10      12 CYCLES
     2170         PHA          PHA+PLA=7 CYCLES
     2180         PLA
     2190         NOP          NOP+DEY+BNE=7 CYCLES
     2200         DEY
     2210         BNE .4       WHOLE LOOP = 50 CYCLES
     2220         JSR READ.ADDRESS
     2230         BCS .8       ERROR, TRY AGAIN
     2240         LDA SECTOR   BETTER BE SECTOR 0
     2250         BEQ .6       IT IS, HURRAY!
     2260         LDA #16      REDUCE # SYNC'S BY TWO
     2270         CMP SYNC.CNT UNLESS ALREADY < 16
     2280         LDA SYNC.CNT
     2290         SBC #1
     2300         STA SYNC.CNT
     2310         CMP #5       IF SYNC.CNT < 5, THERE IS NO HOPE
     2320         BCS .8       >=5, TRY AGAIN
     2330         SEC          FLAG COULDN'T DO IT
     2340         RTS
     2350  .5     JSR READ.ADDRESS
     2360         BCS .7       ERROR, TRY AGAIN
     2370  .6     JSR READ.SECTOR
     2380         BCC .11      GOOD!
     2390  .7     DEC RETRY.COUNT
     2400         BNE .5       TRY AGAIN
     2410  .8     JSR READ.ADDRESS
     2420         BCS .9
     2430         LDA SECTOR
     2440         CMP #15      SECTOR = 15?
     2450         BNE .9       NO
     2460         JSR READ.SECTOR
     2470         BCC FORMAT.TRACK
     2480  .9     DEC RETRY.COUNT
     2490         BNE .8       TRY AGAIN
     2500         SEC          FLAG WE COULDN'T DO IT
     2510  .10    RTS          RETURN
     2520  *---------------------------------
     2530  .11    LDY SECTOR
     2540         LDA SECTOR.FLAGS,Y
     2550         BMI .7       ALREADY READ THIS ONE!
     2560         LDA #$FF
     2570         STA SECTOR.FLAGS,Y
     2580         DEC FMT.SECTOR
     2590         BPL .5
     2600         LDA TRACK.CNTR
     2610         BNE .12
     2620         LDA SYNC.CNT
     2630         CMP #16
     2640         BCC .10
     2650         DEC SYNC.CNT
     2660         DEC SYNC.CNT
     2670  .12    CLC
     2680         RTS
     2690  *---------------------------------
     2700  SECTOR.FLAGS
     2710         .HS FFFFFFFFFFFFFFFF
     2720         .HS FFFFFFFFFFFFFFFF
     2730  *---------------------------------
     2740  PHYSICAL.SECTOR.VECTOR
     2750         .HS 000D0B0907050301
     2760         .HS 0E0C0A080604020F
     2770  *---------------------------------
     2780  *      CLOBBER WHATEVER IS IN RAM CARD
     2790  *---------------------------------
     2800  PATCH1 JSR $FE93    WHAT PATCH REPLACED
     2810         LDA $C081    WRITE-ENABLE RAM CARD
     2820         LDA $C081
     2830         LDA #0       PUT ZERO IN BYTE WE LATER
     2840         STA $E000    TEST TO SEE WHICH LANGUAGE
     2850         JMP $B744    RETURN
     2860  *---------------------------------
     2870  *---------------------------------
     2880  *      VARIOUS ODDS AND ENDS
     2890  *---------------------------------
     2900         .HS 000000
     2910  PATCH2 STA $AA63
     2920         STA $AA70
     2930         STA $AA71
     2940         RTS
     2950  PATCH3 JSR $A75B
     2960         STY $AAB7
     2970         RTS
     2980  PATCH4 JSR $AE7E    FROM $B377
     2990         LDX $B39B
     3000         TXS
     3010         JSR $A316
     3020         TSX
     3030         STX $B39B
     3040         LDA #9       "DISK FULL" ERROR
     3050         JMP $B385

Substring Search Function for Applesoft | Bob Sander-Cederlof

Lee Reynolds' article in the January 1981 Call A.P.P.L.E. touched off this project. When you are searching through text arrays for keywords, or through a mailing list for someone who lives on "XYZ Street", Applesoft can be vveeerrrrryyy slow. This subroutine, linked in through the famous ampersand feature, will give you the speed your Apple is famous for.

Lee's program was quite similar to this one, but it did not allow the keyword or the string-to-be-searched to be expressions. He left that extension as "an exercise for the reader". Being one reader badly in need of exercise, I took up the challenge.

Although it is not really necessary, I used one of the newly discovered "secret" opcodes (which I wrote about last month) at line 2060. If you like, you can replace that line with:

         2060 GS1    LDA (FACMO),Y
         2065        TAX

     1010  *---------------------------------
     1020  *
     1030  *      SUBSTRING SEARCH FUNCTION FOR APPLESOFT
     1040  *      ---------------------------------------
     1050  *
     1060  *      & SUB$( A$, B$, I )
     1070  *
     1080  *      SEARCHES FOR FIRST OCCURRENCE OF
     1090  *      B$ IN A$; PUTS RESULT IN I
     1100  *
     1110  *      RETURNS I=0 IF B$ IS NOT IN A$
     1120  *
     1130  *      (REFERENCE:  CALL A.P.P.L.E. ARTICLE
     1140  *      IN JANUARY 1981 ISSUE BY LEE REYNOLDS,
     1150  *      PAGES 26-30.)
     1160  *
     1170  *---------------------------------
     1180  FACMO       .EQ $A0
     1190  TEMPPT      .EQ $52
     1200  MAIN.LENGTH .EQ $18
     1210  MAIN        .EQ $19,1A
     1220  KEY.LENGTH  .EQ $1B
     1230  KEY         .EQ $1C,1D
     1240  *---------------------------------
     1250  ASSIGN .EQ $DA5C    STORE VALUE IN VARIABLE
     1260  SYNCHR .EQ $DEC0    REQUIRE (A) AS NEXT CHAR
     1270  FRMEVL .EQ $DD7B    EVALUATE FORMULA
     1280  SYNCOM .EQ $DEBE    REQUIRE COMMA
     1290  SYNRPN .EQ $DEB8    REQUIRE ")"
     1300  CHKSTR .EQ $DD6C    REQUIRE STRING
     1310  PTRGET .EQ $DFE3    GET POINTER
     1320  FRETMP .EQ $E604    FREE TEMPORARY STRING
     1330  SNGFLT .EQ $E301    FLOAT (Y)
     1340  *---------------------------------
     1350         .OR $300
     1360         .TF B.SUBSTRING SEARCH
     1370  *---------------------------------
     1380  SETUP.AMPERSAND
     1390         LDA #$4C     JMP OPCODE
     1400         STA $3F5
     1410         LDA #SUB
     1420         STA $3F6
     1430         LDA /SUB
     1440         STA $3F7
     1450         RTS
     1460  *---------------------------------
     1470  SUBQT  .AS "($BUS"  SUB$( BACKWARDS
     1480  *---------------------------------
     1490  SUB
     1500         LDX #4       COMPARE FOR "SUB$("
     1510  .1     LDA SUBQT,X
     1520         JSR SYNCHR   COMPARE WITH INPUT
     1530         DEX
     1540         BPL .1
     1550  *---------------------------------
     1560         LDY #MAIN.LENGTH
     1570         JSR GET.STRING
     1580         LDY #KEY.LENGTH
     1590         JSR GET.STRING
     1600         JSR PTRGET   GET VARIABLE FOR RESULT
     1610         STA $85
     1620         STY $86
     1630         JSR SYNRPN   REQUIRE RIGHT PAREN
     1640  *---------------------------------
     1650         JSR FREE.STRINGS
     1660  *---------------------------------
     1670         LDX #0       ANSWER OFFSET
     1680  .2     LDA MAIN.LENGTH SEE IF IT CAN STILL FIT
     1690         CMP KEY.LENGTH
     1700         BCC .8       WILL NOT FIT
     1710         LDY #0
     1720  .3     LDA (KEY),Y
     1730         CMP (MAIN),Y
     1740         BNE .6
     1750         INY
     1760         CPY KEY.LENGTH
     1770         BCC .3
     1780         INX          X IS RESULT
     1790         TXA
     1800         TAY
     1810  .4     JSR SNGFLT   FLOAT THE BYTE IN Y
     1820         LDA $12
     1830         PHA
     1840         LDA $11
     1850         JMP ASSIGN   STORE VALUE IN VARIABLE
     1860  .6     INC MAIN
     1870         BNE .7
     1880         INC MAIN+1
     1890  .7     INX
     1900         DEC MAIN.LENGTH
     1910         BNE .2
     1920  .8     LDY #0       RESULT IS 0
     1930         BEQ .4       ...ALWAYS
     1940  *---------------------------------
     1950  *      GET STRING EXPRESSION
     1960  *---------------------------------
     1970  GET.STRING
     1980         STY GS2      PLUG OUTPUT VECTOR
     1990         JSR FRMEVL   EVALUATE FORMULA
     2000         JSR SYNCOM   REQUIRE TRAILING COMMA
     2010         JSR CHKSTR   REQUIRE STRING
     2020         LDY #2       GET STRING DATA
     2030  * THE NEXT LINE IS A "SECRET" 6502 OPCODE,
     2040  * WHICH DOES BOTH LDA (FACMO),Y AND LDX (FACMO),Y
     2050  * AT THE SAME TIME.
     2060  GS1    .DA #$B3,#FACMO
     2070         STX *-*,Y    PLUGGED IN FROM ABOVE
     2080  GS2    .EQ *-1
     2090         DEY
     2100         BPL GS1
     2110         RTS
     2120  *---------------------------------
     2130  *      FREE UP ANY TEMPORARY STRINGS
     2140  *---------------------------------
     2150  FREE.ONE.STRING
     2160         LDA TEMPPT+1
     2170         LDY #0
     2180         JSR FRETMP
     2190  FREE.STRINGS
     2200         LDA TEMPPT
     2210         CMP #$56     EMPTY?
     2220         BCS FREE.ONE.STRING
     2230         RTS

Here is a sample Applesoft program which uses the Substring Search Subroutine. Line 10 loads the subroutine and calls 768 to link in the ampersand vector. Line 120 reads in your search key. If you just hit the RETURN key, the program quits.

Line 130 gets the next string to be searched from the DATA list. If the value is ".", we are at the end of the list, so it loops back to line 110.

Line 140 calls our substring search subroutine to see if the key string can be found in the search string. If not, it jumps back to line 130 to get another search string. Lines 150-180 print the search string, emphasizing the portion that matched the key string by printing it in inverse.

    10 PRINT CHR$(4);"BLOAD B.SUBSTRING.SEARCH": CALL 768
    100 DATA ASM,DELETE,FAST,FIND,HIDE,INCREMENT,LIST,LOAD,MEMORY,
        MERGE,MGO,NEW,PRT,RENUMBER,RESTORE,SAVE,SLOW,USER,VAL,.
    110 RESTORE
    120 INPUT "KEY STRING: ";K$: IF K$ = "" THEN END
    130 READ A$: IF A$="." THEN PRINT: GOTO 110
    140 & SUB$(A$,K$,I): IF I=0 THEN 130
    150 IF I>1 THEN PRINT LEFT$(A$,I-1);
    160 INVERSE: PRINT K$;: NORMAL
    170 L=LEN(A$)-I+1-LEN(K$): IF L>0 THEN PRINT RIGHT$(A$,L)
    180 PRINT: GOTO 130

Apple Assembly Line is published monthly by S-C SOFTWARE, P. O. Box 5537, Richardson, TX 75080. Subscription rate is $12/year, in the U.S.A., Canada, and Mexico. Other countries add $6/year for extra 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.)