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 |
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 *--------------------------------- |
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 |
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!
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 |
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.
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 |
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:
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 |
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 |