Apple Assembly   Line
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...

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.)