In This Issue...
The Chips are Falling
We have just learned that Western Design Center is dramatically lowering the price of the 65802 microprocessors. We've been selling the '802 for $50.00: Effective about the time you read this our price will be $25.00. All the chips they produce are now rated to operate to at least 4 MHz, and they're now getting a significant yield of 6 and even 8 MHz parts. Evidently the volume production made possible by Apple's use of the 65816 in the IIgs is paying off.
Missing ProDOS Books
We had a scare a couple of weeks ago: Quality Software and Simon & Schuster had both run out of copies of Beneath Apple ProDOS, that excellent reference on the inner workings of ProDOS, so it looked for a while like we might lose a valuable resource. All is well, though, the folks at Quality are planning a new printing, so we expect to have more copies of the book in a month or two. We'll just hold any orders until that time.
Curiously, both Addison-Wesley's ProDOS Technical Reference Manual and Simon & Schuster's Apple ProDOS: Advanced Features for Programmers have been out of stock at the publishers for a couple of months now. A-W tells us that a revised edition of Apple's manual will be published in late June. S & S has Advanced Features on backorder, but won't quote even a tentative delivery date.
As I mentioned in my description of the new Apple IIgs monitor commands (AAL Jan 87, pp.23-27), there is a new "P" command which searches through a range of memory for all occurrences of a pattern. Well, I tried to use that command the other day.
First, I couldn't remember with confidence what the syntax was. Then I thought that it must be \pattern\adr1.adr2P. I tried that and it did not work. I looked it up in Fischer's "IIgs Technical Reference", and verified that I did have the correct syntax. It still did not work, and I did need a search capability, so I wrote my own. More on it later. That was last week. I don't know why I didn't look upo the command in another reference, but today I did. Both my memory and Fischer's book were wrong!
For some esoteric reason, Apple requires that the "<" character appear between the pattern and the address range. My January article and Apple's own reference material clearly show the "<" there: \pattern\<adr1.adr2P is the correct syntax. Type it correctly, and it works as advertised. For example:
\8E C0\<0/2000.5FFFP will search $2000 through $5FFF in bank 0 for the two bytes $8E and $C0 in successive locations. \"Apple"\<FF/0.FFFFP will search all of bank $FF for the string "Apple". (In my IIgs it found that string five times.)
If you leave out the "<" character, nothing happens.
The "P" command is a powerful tool, and a welcome addition the Apple monitor. However, I still need some capabilities it does not have. For example, what if I want to find all references to a particular address made using any of the LDA instructions. I don't want to see any references using LDX, LDY, CMP, STA, etc. If I could tell it to only compare certain bits in some bytes, I could accomplish this and many other interesting feats. We might also like the feature of allowing a variable length wild card, such as in FID and FILER filenames and in the S-C Macro Assembler REPLACE command. Maybe I want to include the ability to find PC-relative references to an address. There is probably an endless list of possible features.
I did write a new search program, and did include one of the above mentioned new features. I linked it in through the control-Y vector, so that I can use it from within the monitor. It in effect adds a new command to the monitor, with this syntax "adr1.adr2Ypattern". (Where I show "Y", you have to type control-Y. If you are using the command from within the S-C Macro Assembler, you have to type control-O followed by control-Y.) The pattern is a sequence of hex bytes and/or byte-pairs. A hex byte will have to be completely matched. A byte-pair specifies which bits to compare by giving both a mask byte and a key byte. For example:
0/2000.5FFFYAD F0:80 C0 will find all occurrences of LDA $C080 through $C08F in bank 0, 2000-5FFF. FF/0.FFFFYC1 DF:D0 DF:D0 DF:CC DF:C5 will find all occurrences of "Apple" without regard to upper or lower case.
In the January issue of AAL I also discussed the way to hook into the control-Y vector in the IIgs. It is different from the older Apples. Lines 1160-1230 in the following program install the control-Y vector in a IIgs. In an older Apple we would store only a 2-byte address at $3F9 and $3FA. This will still work in a IIgs, but then you would be limited to running in bank 0 in emulation mode. The bank $E1 vector allows you to be called in native mode, and your code can be in any bank of memory.
When you enter a control-Y command (assuming the vector has been installed) control will come to the SEARCH program. Lines 1260-1290 are a nice general way to save the current mode and status and get into emulation mode. Lines 1600-1630 restore the mode and status before returning.
Lines 1310-1320 call the GET.PATTERN subroutine and quit if that subroutine reports an error. GET.PATTERN scans the input buffer from just after the control-Y to a carriage return. It seemed reasonable to me to require that the entire command line be in the first 127 characters of the input buffer; that left the other half ($280-2FF) free for me to use for storing the converted key and mask bytes. GET.PATTERN stores the key bytes starting at $280, and the mask bytes starting at $2C0. It returns the number of bytes in the key in the X-register. Line 1330 stores this count at WBUF, which happens to also be the beginning of the input buffer.
Lines 1340-1350 copy the bank byte of the address range from the place the monitor keeps it ($E1013E) to the end of a 3-byte pointer in page zero. This seems like a good time to list all the places the monitor puts the addresses it parses. The general form of a monitor command is "a/b<c/d.eX". "a/b" is a target address, with "a" specifying the bank and "b" specifying the address within the bank. "c/d" is the beginning address of a range, with "c" specifying the bank and "d" the address within the bank. "e" is the ending address of the range, in the same bank with "d". "X" represents any monitor command letter. Some commands do not use all of the parsable parts, but you can usually get away with entering them anyway. The monitor subroutine called GETNUM is responsible for parsing the addresses. In older machines GETNUM was at $FFA7. In the IIgs there is still a copy of GETNUM at $FFA7, but it is not used. Instead a new version which starts at $F888 is used. This new version allows the "a/" and "c/" parts. From my analysis, here is where the new GETNUM stores a, b, c, d, and e:
a -- $E1/013F b -- $00/0042,43 c -- $E1/013E d -- $00/003C,3D and $00/0040,41 e -- $00/003E,3F
GETNUM will also leave the pointer into the input buffer which points to the next character after the command character in YSAVE, which is $00/0034.
The IIgs also has a new subroutine at $FCCA, which gets the next character from the input buffer and converts it from lower-case to upper-case if it is a lower-case letter.
Lines 1370-1580 perform the actual search operation. Lines 1370-1440 compare the masked portions of the key bytes with the masked portion of the memory bytes starting at PNTR. If they all match, lines 1460-1490 call on PRADR to print the 3-byte address. Lines 1500-1580 increment the PNTR and test to see if we have come to the end of the range. [Note that a multiple- byte key will be tested beyond the end of the range. That is, the search will continue until the first key byte has been tested throughout the range. This means the key bytes after the first one will extend beyond the end of the range. I am pointing this out so that you will not accidentally start tripping softswitches in the I/O space when you have a range ending at $BFFF.]
There are ample possibilities here for you to expand the features of this search. You could add to the GET.PATTERN a more flexible wild-card scheme. You could allow ASCII strings to be entered. You could automatically protect the I/O softswitch range, especially $C000-C0FF. You could save the current shadowing status and then turn off all shadowing during the search. And so on.
I slipped in another little routine at lines 2150-2420. I am not calling it here, but when I did it printed out the currently parsed addresses. It helped me to get a better feeling for what happens when you enter an incomplete address specification. If you leave off the bank, it uses the same one you used before. If you leave out the range beginning, it begins where the previous command ended. There are a lot of choices here, so I find I usually just type the complete command to be sure.
1000 *SAVE S.SEARCH.IIGS 1010 *-------------------------------- 1020 YSAVE .EQ $34 1030 END .EQ $3E,3F ADDRESS OF END OF RANGE 1040 PNTR .EQ $40,41,42 START OF RANGE, CURRENT PNTR 1050 *-------------------------------- 1060 WBUF .EQ $200 1070 KBUF .EQ $280 1080 MBUF .EQ $2C0 1090 *-------------------------------- 1100 NXTCHR .EQ $FCCA 1110 CROUT .EQ $FD8E 1120 PRBYTE .EQ $FDDA 1130 COUT .EQ $FDED 1140 *-------------------------------- 1150 .OP 8 1160 T 1170 LDA #SEARCH Setup Control-Y vector 1180 STA $E100A1 1190 LDA /SEARCH 1200 STA $E100A2 1210 LDA #0 1220 STA $E100A3 1230 RTS 1240 *-------------------------------- 1250 SEARCH 1260 PHP Save mode, get into emulation mode 1270 SEC 1280 XCE 1290 PHP 1300 *-------------------------------- 1310 JSR GET.PATTERN Crack the search pattern 1320 BCS .99 ...nothing there or too long 1330 STX WBUF save # bytes in pattern 1340 LDA $E1013E Bank to search 1350 STA PNTR+2 make 3-byte address 1360 *---Compare pattern-------------- 1370 .1 LDY #-1 1380 .2 INY 1390 LDA >(PNTR),Y Byte from source 1400 EOR KBUF,Y Byte from pattern 1410 AND MBUF,Y Byte from mask 1420 BNE .3 ...does not pass inspection 1430 CPY WBUF At end of pattern yet? 1440 BCC .2 ...no 1450 *---Found a match---------------- 1460 LDA PNTR+2 Print 3-byte address 1470 LDX #PNTR 1480 LDY #" " ...followed by a blank 1490 JSR PRADR 1500 *---Slip to next postion--------- 1510 .3 LDA PNTR Set carry if at end of range 1520 CMP END 1530 LDA PNTR+1 1540 SBC END+1 1550 INC PNTR Bump comparison pointer 1560 BNE .4 1570 INC PNTR+1 1580 .4 BCC .1 1590 *-------------------------------- 1600 .99 PLP Restore machine mode 1610 XCE 1620 PLP 1630 RTS 1640 *-------------------------------- 1650 GET.PATTERN 1660 LDY YSAVE 1670 LDX #-1 1680 .1 INX 1690 LDA #$FF Assume mask =$FF 1700 .2 STA MBUF,X Store a mask value 1710 LDA #$00 Start Keybyte = $00 1720 .3 STA KBUF,X 1730 CPY #$7F 1740 BCS .7 ...LINE TOO LONG 1750 JSR NXTCHR GET UPPER-CASE CHAR AT WBUF,Y 1760 CMP #" " Is it a space? 1770 BEQ .1 ...yes, next byte 1780 CMP #":" 1790 BCC .5 < $BA, must be 0...9 1800 BNE .4 > $BA, must be A...F 1810 LDA KBUF,X = $BA, ":", means to put 1820 BCS .2 ...ALWAYS value into MASK 1830 *---Try letter A...F------------- 1840 .4 CMP #"G" 1850 BCS .7 ...ERROR 1860 CMP #"A" 1870 BCC .7 ...ERROR 1880 SBC #7 "A"..."F" 1890 *---Try hex digit range---------- 1900 .5 CMP #"0" Is it 0...9 or A...F? 1910 BCC .6 ...no 1920 AND #$0F ...yes, isolate digit 1930 ASL KBUF,X Shift previous value 1940 ASL KBUF,X 1950 ASL KBUF,X 1960 ASL KBUF,X 1970 ORA KBUF,X Merge with digit 1980 JMP .3 1990 *---End of line or error--------- 2000 .6 CMP #$8D 2010 BNE .7 ...ERROR 2020 DEY 2030 STY YSAVE 2040 CLC 2050 RTS 2060 *---Bad char or line too long---- 2070 .7 STY YSAVE 2080 LDA #$8D STORE <RETURN> IN MONITOR 2090 STA WBUF+1,Y INPUT BUFFER 2100 LDA #$87 RING THE BELL 2110 JSR COUT 2120 SEC INFORM OF ERROR 2130 RTS 2140 *-------------------------------- 2150 DISPLAY.ADDRESSES 2160 PHP 2170 SEC 2180 XCE 2190 PHP 2200 *-------------------------------- 2210 LDA YSAVE WBUF,Y POINTS TO NEXT CHAR 2220 JSR PRBYTE AFTER THE CTRL-Y 2230 JSR CROUT 2240 *-------------------------------- 2250 LDA $E1013F 2260 LDX #PNTR+2 2270 LDY #"<" 2280 JSR PRADR 2290 *-------------------------------- 2300 LDA $E1013E 2310 LDX #PNTR 2320 LDY #"." 2330 JSR PRADR 2340 *-------------------------------- 2350 LDX #END 2360 LDY #$8D 2370 JSR PRADR0 2380 *-------------------------------- 2390 PLP 2400 XCE 2410 PLP 2420 RTS 2430 *-------------------------------- 2440 PRADR JSR PRBYTE PRINT BANK 2450 LDA #"/" 2460 JSR COUT 2470 PRADR0 2480 LDA 1,X PRINT HI-BYTE OF ADDRESS 2490 JSR PRBYTE 2500 LDA 0,X PRINT LO-BYTE OF ADDRESS 2510 JSR PRBYTE 2520 TYA PRINT TRAILING CHAR 2530 JMP COUT 2540 *-------------------------------- |
Another project I thought of was modifying this search program so that it could run in an older Apple. Naturally we would lose the 3-byte addresses, so the program actually becomes simpler. A listing of this simpler version is shown below.
1000 *SAVE S.SEARCH.II 1010 *-------------------------------- 1020 YSAVE .EQ $34 1030 END .EQ $3E,3F ADDRESS OF END OF RANGE 1040 PNTR .EQ $40,41,42 START OF RANGE, CURRENT PNTR 1050 *-------------------------------- 1060 WBUF .EQ $200 1070 KBUF .EQ $280 1080 MBUF .EQ $2C0 1090 *-------------------------------- 1100 CROUT .EQ $FD8E 1110 PRBYTE .EQ $FDDA 1120 COUT .EQ $FDED 1130 *-------------------------------- 1140 T 1150 LDA #SEARCH Setup Control-Y vector 1160 STA $3F9 1170 LDA /SEARCH 1180 STA $3FA 1190 RTS 1200 *-------------------------------- 1210 SEARCH 1220 JSR GET.PATTERN Crack the search pattern 1230 BCS .99 ...nothing there or too long 1240 STX WBUF save # bytes in pattern 1250 *---Compare pattern-------------- 1260 .1 LDY #-1 1270 .2 INY 1280 LDA (PNTR),Y Byte from source 1290 EOR KBUF,Y Byte from pattern 1300 AND MBUF,Y Byte from mask 1310 BNE .3 ...does not pass inspection 1320 CPY WBUF At end of pattern yet? 1330 BCC .2 ...no 1340 *---Found a match---------------- 1350 LDA PNTR+1 1360 JSR PRBYTE 1370 LDA PNTR 1380 JSR PRBYTE 1390 LDA #" " 1400 JSR COUT 1410 *---Slip to next postion--------- 1420 .3 LDA PNTR Set carry if at end of range 1430 CMP END 1440 LDA PNTR+1 1450 SBC END+1 1460 INC PNTR Bump comparison pointer 1470 BNE .4 1480 INC PNTR+1 1490 .4 BCC .1 1500 *-------------------------------- 1510 .99 RTS 1520 *-------------------------------- 1530 GET.PATTERN 1540 LDY YSAVE 1550 LDX #-1 1560 .1 INX 1570 LDA #$FF Assume mask =$FF 1580 .2 STA MBUF,X Store a mask value 1590 LDA #$00 Start Keybyte = $00 1600 .3 STA KBUF,X 1610 CPY #$7F 1620 BCS .7 ...LINE TOO LONG 1630 JSR NXTCHR GET UPPER-CASE CHAR AT WBUF,Y 1640 CMP #" " Is it a space? 1650 BEQ .1 ...yes, next byte 1660 CMP #":" 1670 BCC .5 < $BA, must be 0...9 1680 BNE .4 > $BA, must be A...F 1690 LDA KBUF,X = $BA, ":", means to put 1700 BCS .2 ...ALWAYS value into MASK 1710 *---Try letter A...F------------- 1720 .4 CMP #"G" 1730 BCS .7 ...ERROR 1740 CMP #"A" 1750 BCC .7 ...ERROR 1760 SBC #7 "A"..."F" 1770 *---Try hex digit range---------- 1780 .5 CMP #"0" Is it 0...9 or A...F? 1790 BCC .6 ...no 1800 AND #$0F ...yes, isolate digit 1810 ASL KBUF,X Shift previous value 1820 ASL KBUF,X 1830 ASL KBUF,X 1840 ASL KBUF,X 1850 ORA KBUF,X Merge with digit 1860 JMP .3 1870 *---End of line or error--------- 1880 .6 CMP #$8D 1890 BNE .7 ...ERROR 1900 DEY 1910 STY YSAVE 1920 CLC 1930 RTS 1940 *---Bad char or line too long---- 1950 .7 STY YSAVE 1960 LDA #$8D STORE <RETURN> IN MONITOR 1970 STA WBUF+1,Y INPUT BUFFER 1980 LDA #$87 RING THE BELL 1990 JSR COUT 2000 SEC INFORM OF ERROR 2010 RTS 2020 *-------------------------------- 2030 NXTCHR LDA WBUF,Y 2040 INY 2050 CMP #$E1 2060 BCC .1 2070 CMP #$FB 2080 BCS .1 2090 AND #$DF 2100 .1 RTS 2110 *-------------------------------- |
A few days ago a friend and I were burning some EPROMs using the SCRG PromGramer in my IIgs. The friend said, "Why don't we use this card to read the IIgs ROM?" "We could put the PromGramer in another machine, and then put the IIgs ROMs in it and read them."
Well, I don't really know why he wanted to do this. He has his own IIgs already anyway. Nevertheless, I can think of reasons to want to capture all of the ROM info on disk files. Disassembly, for one, using the S-C DisAssembler. Another reason would be to be able to compare different machines to see if they have the same ROM contents. But do we have to take out the chips to read them?
No. The following simple commands, issued from inside the S-C Macro Assembler running in your IIgs, will write the entire ROM contents to a series of eight files. It works under either ProDOS or DOS 3.3:
$00/1000<FE/0000.3FFFM BSAVE ROM.FE.0,A$1000,L$4000 $00/1000<FE/4000.7FFFM BSAVE ROM.FE.4,A$1000,L$4000 $00/1000<FE/8000.BFFFM BSAVE ROM.FE.8,A$1000,L$4000 $00/1000<FE/C000.FFFFM BSAVE ROM.FE.C,A$1000,L$4000 $00/1000<FF/0000.3FFFM BSAVE ROM.FF.0,A$1000,L$4000 $00/1000<FF/4000.7FFFM BSAVE ROM.FF.4,A$1000,L$4000 $00/1000<FF/8000.BFFFM BSAVE ROM.FF.8,A$1000,L$4000 $00/1000<FF/C000.FFFFM BSAVE ROM.FF.C,A$1000,L$4000
I found it convenient to put those commands in a text file, which I named GET.ROMS. Then, whenever I want to use it, I can just type EXEC GET.ROMS. I took my disk down to the local Heathkit store around 10:45 this morning and came back with a copy of their ROM.
It strikes me just now that I am being inconsistent. Sometimes I am saying ROM, and sometimes ROMs. There is a reason....
Real IIgs machines come with one system ROM chip, which is a 1 megabit ROM. It holds all 128K bytes, in only one 28-pin package. I presume it is uses pinouts like a 27512 EPROM, but selects one half or the other of the 1024K bits using what would normally be the programming pin on the 27512. My IIgs, on the other hand, is a prototype machine. It has an adapter plugged into the single ROM socket, which holds four 27256 EPROM chips and one 74F139. They call it the "airplane ROMs". I like it, because if I want to I can easily burn a new version of the firmware and plug it in. Some enterprising soul might find a market for a board like this.
You remember in our March issue we talked about patches to fix Version 1.3 of ProDOS. Apple has pulled this version off the market, but there are still a lot of copies floating around. The patches we gave in the March article should make ProDOS 1.3 as good as any other version, but who knows?
Anyway, we heard through the grapevine that some unofficial copies of version 1.4 are out, and that a brand new bug has surfaced in this one. It seems someone put a "LDA $C09C,X" where "LDA $C08E,X" should be.
I ran across a listing in the Washington Apple Pi newsletter (May 1987 issue, page 16) of an Applesoft program which can install all known necessary patches in versions 1.1.1, 1.2, 1.3, and 1.4 of ProDOS. The program was originally written by Stephen Thomas to fix version 1.1.1, when the problem of the four STA's to the stepper motor soft-switches was discovered. (See Nov 86 AAL) Later Glen Bredon modified it to make the corresponding patches to later versions, as well as to fix the additional new bugs. I have further modified it, in an attempt to make it easier to understand.
100 TEXT : HOME :E = 0: PRINT "PRODOS PATCH PROGRAM" 110 IF PEEK (116) < 128 THEN E = 1: GOTO 900: REM ENUF MEM? 120 ONERR GOTO 900 130 REM ---READ PRODOS FILE--- 140 PRINT CHR$ (4)"UNLOCK PRODOS" 150 PRINT CHR$ (4)"BLOAD PRODOS,TSYS,A$2000" 200 REM ---SEARCH $4000-$60FF FOR PATTERN--- 210 V = 1: FOR B = 64 TO 96:A = B * 256 220 IF PEEK (A + 4) < > 189 THEN 250 230 IF PEEK (A + 5) < > 156 THEN 290 240 IF PEEK (A + 6) = 192 THEN V = 3:B = 96: GOTO 290: REM VERSION 1.4 250 IF PEEK (A + 4) < > 234 THEN 290 260 IF PEEK (A + 5) < > 234 OR PEEK (A + 6) < > 234 THEN 290 270 IF PEEK (A + 7) < > 234 OR PEEK (A + 8) < > 234 THEN 290 280 V = 2:B = 96: REM VERSION BEFORE 1.4 290 NEXT B:E = 2: ON V GOTO 900,300,700 300 REM ---FOUND VERSION BEFORE 1.4--- 310 POKE A + 4,189: POKE A + 5,142: POKE A + 6,192: REM "LDA $C08E,X" 400 REM ---LOOK FOR OTHER PATCH AREA--- 410 A = PEEK (A + 2) + 256 * PEEK (A + 3) - 13 * 4096 + A + 5 420 E = 3: IF A < 4 * 4096 OR A > 6 * 4096 THEN 900 430 IF PEEK (A) < > 157 OR PEEK (A + 3) < > 157 THEN 500 440 IF PEEK (A + 6) < > 157 OR PEEK (A + 9) < > 157 THEN 500 450 REM ---FOUND VERSION 1.1.1 OR 1.2, SO CHANGE "STA" TO "LDA"--- 460 FOR I = 0 TO 9 STEP 3: POKE A + I,189: NEXT I 470 V$ = "1.1.1": GOTO 800 500 REM ---VERSION 1.3--- 510 FOR I = 0 TO 12: READ B: IF PEEK (A + I) < > B THEN GOTO 900 520 NEXT I: DATA 160,8,189,128,192,232,232,136,208,248,234,234,96 530 FOR I = 0 TO 11: READ B: POKE A + I,B: NEXT I 540 DATA 189,128,192, 189,130,192, 189,132,192, 189,134,192 550 A = 4 * 4096 + 12 * 256 + 12 * 16 + 13: REM ADDRESS = $4CCD 560 FOR I = 0 TO 3: READ B: IF PEEK (A + I) < > B THEN 900 570 NEXT I: POKE A,240: REM CHANGE "BRA" TO "BEQ" 580 V$ = "1.3": GOTO 800 590 DATA 128,6,190,0 700 REM ---VERSION 1.4--- 710 POKE A + 5,142: REM "LDA $C09C,X" TO "LDA $C08E,X" 720 V$ = "1.4" 800 REM ---WRITE PATCHED VERSION ON DISK--- 810 PRINT CHR$ (4)"BSAVE PRODOS,TSYS,A$2000" 820 PRINT CHR$ (4)"LOCK PRODOS" 830 PRINT "PATCHES COMPLETED TO VERSION "V$: END 900 REM ---ERROR HANDLER--- 910 PRINT CHR$ (7)"ERROR! NO PATCHES WERE MADE." 915 ON E GOTO 930,940,950 920 PRINT "PRODOS FILE NOT FOUND.": END 930 PRINT "NOT ENOUUGH ROOM TO LOAD PRODOS.": END 940 PRINT "PATCH LOCATION NOT FOUND.": END 950 PRINT "PRODOS FILE MAY HAVE BEEN PATCHED," 960 PRINT "ALREADY, OR IS NOT A COMPATIBLE VERSION." 970 END
Lines 100-150 read the ProDOS system file into memory. Then Lines 200-290 search every page from $4000 through $60FF for either five NOPs starting at $xx04 or a "LDA $C09C,X" instruction at $xx04. If neither is found, nothing is patched. If the five NOPs are found, we have version 1.1.1, 1.2, or 1.3. If the LDA is found, we have version 1.4. If it is version 1.4, the only patch needed is to change it to "LDA $C08E,X", which is done at lines 700-720.
Older versions all need "LDA $C08E,X" poked where the five NOPs were, so line 310 takes care of this. Then we look at the address in the operand field of the instruction just prior to the five NOPs. This is a JSR to a little subroutine which we need to modify. Line 410 computes the location within the system file image for the twelve bytes we need to change.
There are several possible versions of this subroutine. If it is a series of "STA $C08x,X" instructions, we have version 1.1.1 or 1.2 and the STA opcodes should be changed to LDA opcodes. Lines 430 and 440 test for STA opcodes, and lines 450-470 make the changes. On the other hand, if the subroutine is like Apple put in version 1.3 we will replace it with a series of four LDAs just like we made in the older versions. Lines 500-590 handle this, and also change an errant "BRA" opcode to a "BEQ" opcode.
Finally, lines 800-830 write out the modified code and re-LOCK the file. I would be careful to check the changes made before doing this to every copy I own, if I were you. And bear in mind that Apple as a company has never authorized any of these changes. (They have only made them necessary, by their own incorrect changes.)
While this article was waiting for the press, Apple finally sent out correct copies of version 1.4. I received my master copy June 1st, and checked it against our patched version 1.3. They were identical except for the copyright dates and version numbers. The official date on this GOOD version 1.4 is April 17, 1987.
A+ Magazine has an interesting puzzle each month, with a challenge to solve it with your Apple and possibly win a prize. The June 1987 puzzle (see page 110, "Computer Calisthenics" by Michael Wiesenberg) involves writing a program to find all possible solutions to the equation ABC*DE=FG*HI, where each letter represents a different digit between 1 and 9. All nine digits are different.
I wrote a quick-and-dirty program in Applesoft, and then refined it a little for speed. Maybe speed isn't the correct work, because even the refined result takes nearly six minutes to find the eleven solutions. The first solution my program found was the same as the one example solution given in the A+ article: 158*23 = 46*79.
Puzzles of this type can be solved, if you have enough time, by trying all possible combinations. A series of nested FOR loops, one for each digit, will produce all possible nine-digit arrangements. Appropriate tests can weed out all values which have more than one letter sharing the same digit. Inside all the loops you test the letter assignments against the puzzle equation, and print the solution if it passes.
This will take way too long, even on a computer, so you start looking for ways to eliminate some combinations. The first thing I noticed is that I can eliminate symmetric solutions by forcing HI>FG. This means H>F, so I can run my FOR loop for H from F+1 to 9, instead of from 1 to 9.
The smallest possible value for abc*de is 245*13=3185. This means F cannot be 1 or 2, because even 29*99 is too small. A little more examination shows that FG must be at least 34, so I put this into my program.
Neither C nor E can be 5, because this would force either G or I to also be 5. I also notice that you cannot have a 1 on the right-hand side of the puzzle equation. If there were a 1 there, the largest possible FG*HI would be 91*87 = 7917. This is still smaller than the smallest possible left-hand side without a 1 (356*24 = 8544), so it cannot work.
Similar analysis shows that D cannot be 8 or 9. The largest possible FG*HI is 87*96 = 8352. If D is 8, the smallest ABC*DE is 134*82 = 10988; D=9 is even worse. This also cannot work, so I limit my D-loop to values from 1 to 7.
I also refined the program in the area of testing whether a digit has already been used by a previous letter. I maintain an array of flags. At the top of each loop I test the flag array entry to see if the digit has already been used. If not, I mark it used and continue. If it is already in use, I skip around all the inner loops to the corresponding NEXT. Here is the Applesoft version of my program:
10 DIM N(9): FOR I = 1 TO 9:N(I) = I: NEXT :EPS = .0001 100 FOR F = 3 TO 8:N(F) = 0 110 FOR GX = 2 TO 9:G = N(GX): IF G = 5 OR G = 0 THEN 320 115 FG = F * 10 + G: IF FG < 34 THEN 320 120 N(GX) = 0: FOR HX = F + 1 TO 9:H = N(HX): IF H = 0 THEN 310 130 N(HX) = 0: FOR IX = 2 TO 9:I = N(IX): IF I = 0 OR I = 5 THEN 300 140 HI = H * 10 + I:P = FG * HI: IF P < 3240 THEN 300 150 N(IX) = 0: FOR DX = 1 TO 8:D = N(DX): IF D = 0 THEN 290 160 N(DX) = 0: FOR EX = 1 TO 9:E = N(EX): IF E = 0 OR E = 5 THEN 280 170 DE = D * 10 + E:Q = P / DE: IF Q < 123 OR Q < > INT (Q + EPS) THEN 280 180 N(EX) = 0:A = INT (Q / 100 + EPS): IF N(A) = 0 THEN 270 190 N(A) = 0:B = INT (Q / 10 - A * 10 + EPS): IF N(B) = 0 THEN 260 200 N(B) = 0:C = INT (Q - A * 100 - B * 10 + EPS): IF N(C) = 0 THEN 250 210 N = N + 1: PRINT SPC( N < 10);N": "FG" X "HI" = "DE" X "Q" = "P 250 N(B) = B 260 N(A) = A 270 N(EX) = EX 280 NEXT EX:N(DX) = DX 290 NEXT DX:N(IX) = IX 300 NEXT IX:N(HX) = HX 310 NEXT HX:N(GX) = GX 320 NEXT GX:N(F) = F: NEXT F
I wanted to see how hard it would be to re-write the above program in assembly language, and if so how much speedier it would be. I am not proud that it took me over four hours to perfect the assembly language version. But the result is nice. It executes in less than seven seconds! This does not necessarily argue well for assembly language programming, if all I care about is the answers to the puzzle. But if I view it as an example, and consider that the same speedup may be possible in much larger and much more frequently used programs, it does make assembly language look good. That is why programs like the S-C Macro Assembler and even the Applesoft firmware itself are written in assembly language.
I used the same overall approach in assembly language version. Lines 1040-1090 initialize an array of flags I use to quickly check whether a digit is already in use. The flag values are either 0 or 1: 0 means a digit is in use, and 1 means it is available. The array is accessed by using the digit value for an index. I can both test a flag and change it to zero with one instruction: LSR FLAGS,X will shift bit 0 into carry. If the flag was 0, it still is and carry is clear. If the flag was 1 it changes to 0 and carry is set.
Lines 1110-1520 are a equivalent of FOR statements, starting up a series of six nested loops to generate values for D, E, F, G, H, and I. Lines 1580-1880 are the equivalent to the NEXT statements. All the flag array handling is also included in these two groups of lines. I put the prior knowledge about the ranges of possible values for these six letters into the FOR-loop values, and eliminate the value 5 for letters E, G, and I.
Lines 1540-1560 call subroutines to test the choices for letters D through I and print the resulting equation if it is a valid solution to the puzzle. Both of these subroutines use some really interesting techniques.
The PRINT subroutine (lines 2820-3060) is controlled by a format string in lines 3080-3160. This string has two kinds of bytes: index values between 0 and 8, and ASCII characters between 80 and FF. The index values point to the table of digits A through I. The print loop, lines 2840-2910, only needs four extra lines to pick up digits out of the digit table and convert them to ASCII. Line 2850 branches directly to the JSR COUT in line 2890 if the format byte is an ASCII character already. Otherwise, lines 2860-2880 use the format byte as an index to pick up the digit and merge $B0 with it to convert it to ASCII. This is not only neat, it is also short and fast. Lines 2940-3050 allow you to pause and abort the program by typing any key to pause, <RETURN> to abort.
The COMPUTE.ABC subroutine (lines 1920-2710) checks the chosen values for D through I to see if they are a valid solution to the puzzle. If they are, values for A, B, and C will be chosen in the process of the check. Then the subroutine returns with carry clear to indicate a valid solution. If the solution is not valid, the subroutine will return with carry set.
Lines 1930-2010 call on CALC.XX (lines 2730-2800) to calculate binary values for DE, FG, and HI. I could have added a test here to be sure that DE>33, but it did not seem to be worth the effort. I didn't try it, though, so I might be wrong. CALC.XX mutiplies the first of each pair of digits by ten, and then adds the second.
Lines 2020-2160 compute WXYZ = FG*HI. The highest possible value for FG will be 79 and for HI will be 98. Since we are limiting F to the range 1 through 7, the largest possible combination will be 78*96 = 7488. This is $1D40. The calculation is a simple 8-bit by 8-bit multiplication, with a 16-bit result.
Lines 2170-2330 compute ABC = (FG*HI)/DE. I could have used two or three more nested loops to pick values for A, B, and C, but "it seemed like a good idea at the time" to do it this way. Now I think two more loops to pick A and B using the same techniques as for the other letters, plus a simple search through the flags to pick C, would be nicer. You might try it that way and compare the speeds of the two approaches. My calculation is a simple division with a 16-bit dividend, 8-bit divisor, 16-bit quotient, and 8-bit remainder. If the remainder is non-zero, then the numbers picked do not form a valid solution.
Lines 2340-2460 determine the value of A by essentially dividing ABC by 100. ABC is a 16-bit value, so the subtraction loop has to do a 16-bit subtraction. The loop subtracts one extra time, so line 2440 corrects the remainder (which will be the value BC). Lines 2450-2460 check to see if the value for A was already used for D through I.
Lines 2480-2580 do a similar operation to separate out the value of B, and by default leave the value of C in the A-register. Lines 2540-2570 check to see if the value for B was already used for D through I or for A. Finally, lines 2590-2660 check the value for C against all the other choices.
As a final product, a program like this has little value (unless you win the contest!). However, it can be a great tool for perfecting your skill as a programmer. It is also a pleasant and harmless form of recreation. If you enjoy articles like these, let us know: we may do some more. Or, how about sending us one yourself?
1000 *SAVE S.PUZZLE 1010 *-------------------------------- 1020 COUT .EQ $FDED 1030 *-------------------------------- 1040 T 1050 LDX #9 1060 LDA #1 STORE 1 IN EACH FLAG 1070 .1 STA FLAGS,X 1080 DEX 1090 BNE .1 1100 *-------------------------------- 1110 LDX #2 FOR F=3 TO 8 1120 LOOP.F INX 1130 STX F 1140 LSR FLAGS,X mark digit in use 1150 *-------------------------------- 1160 CPX #3 IF F=3, START G-LOOP AT 4 1170 BEQ LOOP.G 1180 LDX #1 FOR G=2 TO 9 1190 LOOP.G INX 1200 LSR FLAGS,X 1210 BCC NEXT.G ...DIGIT ALREADY USED 1220 STX G 1230 CPX #5 1240 BEQ FIX.G G can't be 5 1250 *-------------------------------- 1260 LDX F FOR H=F+1 TO 9 1270 LOOP.H INX 1280 LSR FLAGS,X 1290 BCC NEXT.H ...DIGIT ALREADY USED 1300 STX H 1310 *-------------------------------- 1320 LDX #1 FOR I=2 TO 9 1330 LOOP.I INX 1340 LSR FLAGS,X 1350 BCC NEXT.I ...DIGIT ALREADY USED 1360 STX I 1370 CPX #5 1380 BEQ FIX.I I can't be 5 1390 *-------------------------------- 1400 LDX #0 FOR D=1 TO 7 1410 LOOP.D INX 1420 LSR FLAGS,X 1430 BCC NEXT.D ...DIGIT ALREADY USED 1440 STX D 1450 *-------------------------------- 1460 LDX #0 FOR E=1 TO 9 1470 LOOP.E INX 1480 LSR FLAGS,X 1490 BCC NEXT.E ...DIGIT ALREADY USED 1500 STX E 1510 CPX #5 1520 BEQ FIX.E E can't be 5 1530 *-------------------------------- 1540 JSR COMPUTE.ABC 1550 BCS FIX.E ...NOT AN ANSWER 1560 JSR PRINT 1570 *-------------------------------- 1580 FIX.E LDX E NEXT E (1...9) 1590 INC FLAGS,X 1600 NEXT.E CPX #9 1610 BCC LOOP.E 1620 *-------------------------------- 1630 FIX.D LDX D NEXT D (1...7) 1640 INC FLAGS,X 1650 NEXT.D CPX #7 1660 BCC LOOP.D 1670 *-------------------------------- 1680 FIX.I LDX I NEXT I (2...9) 1690 INC FLAGS,X 1700 NEXT.I CPX #9 1710 BCC LOOP.I 1720 *-------------------------------- 1730 FIX.H LDX H NEXT H (F+1...9) 1740 INC FLAGS,X 1750 NEXT.H CPX #9 1760 BCC LOOP.H 1770 *-------------------------------- 1780 FIX.G LDX G NEXT G (2...9) 1790 INC FLAGS,X 1800 NEXT.G CPX #9 1810 BCS FIX.F 1820 JMP LOOP.G 1830 *-------------------------------- 1840 FIX.F LDX F NEXT F (3...8) 1850 INC FLAGS,X 1860 NEXT.F CPX #8 1870 BCS END 1880 JMP LOOP.F 1890 *-------------------------------- 1900 END RTS END OF PROGRAM 1910 *-------------------------------- 1920 COMPUTE.ABC 1930 LDX #D-DIGITS DE = D*10+E 1940 JSR CALC.XX 1950 STA DE 1960 LDX #F-DIGITS FG = F*10+G 1970 JSR CALC.XX 1980 STA FG 1990 LDX #H-DIGITS HI = H*10+I 2000 JSR CALC.XX 2010 STA HI 2020 *---WXYZ = FG * HI--------------- 2030 LDY #8 multiply 8-bits by 8-bits 2040 LDA #0 2050 .1 LSR FG get next bit of multiplier 2060 BCC .2 bit = 0 2070 CLC bit = 1 2080 ADC HI Add multiplicand 2090 .2 ROR Shift product hi-byte 2100 ROR WXYZ product lo-byte 2110 DEY Next bit 2120 BNE .1 ...more to go 2130 STA WXYZ+1 store hi-byte of product 2140 STA ABC+1 2150 LDA WXYZ 2160 STA ABC 2170 *---ABC = WXYZ / DE-------------- 2180 LDY #16 2190 LDA #0 2200 CLC 2210 .3 ROL ABC 2220 ROL ABC+1 2230 ROL 2240 CMP DE 2250 BCC .4 2260 SBC DE 2270 .4 DEY 2280 BNE .3 2290 ROL ABC ...final bit into quotient 2300 ROL ABC+1 2310 STA REM 2320 CMP #1 If any remainder, not a valid answer 2330 BCS .9 2340 *---Check digits of ABC---------- 2350 LDX #-1 2360 LDA ABC 2370 LDY ABC+1 2380 .45 SEC 2390 .5 INX COUNT 100'S 2400 SBC #100 2410 BCS .5 2420 DEY 2430 BPL .45 2440 ADC #100 CORRECT FOR OVER-SUBTRACTION 2450 LDY FLAGS,X SEE IF DIGIT ALREADY USED 2460 BEQ .9 ...YES, NO NEED TO LOOK FURTHER 2470 STX A 2480 LDX #-1 2490 SEC 2500 .6 INX COUNT 10'S 2510 SBC #10 2520 BCS .6 2530 ADC #10 CORRECT FOR OVER-SUBTRACTION 2540 LDY FLAGS,X SEE IF DIGIT ALREADY USED 2550 BEQ .9 ...YES, NO NEED TO LOOK FURTHER 2560 CPX A 2570 BEQ .9 2580 STX B 2590 TAX 2600 LDY FLAGS,X SEE IF DIGIT ALREADY USED 2610 BEQ .9 ...YES, NOT A SOLUTION 2620 CPX A 2630 BEQ .9 2640 CPX B 2650 BEQ .9 2660 STX C ...NO, WE HAVE A SOLUTION 2670 *-------------------------------- 2680 CLC 2690 RTS 2700 .9 SEC 2710 RTS 2720 *-------------------------------- 2730 CALC.XX 2740 LDA DIGITS,X 2750 ASL 2760 ASL 2770 ADC DIGITS,X 2780 ASL 2790 ADC DIGITS+1,X 2800 RTS 2810 *-------------------------------- 2820 PRINT 2830 LDY #0 2840 .1 LDA FORMAT,Y 2850 BMI .2 2860 TAX 2870 LDA DIGITS,X 2880 ORA #"0" 2890 .2 JSR COUT 2900 INY 2910 CPY #FMT.SZ 2920 BCC .1 2930 *-------------------------------- 2940 LDA $C000 2950 BPL .5 2960 STA $C010 2970 CMP #$8D 2980 BEQ .4 2990 .3 LDA $C000 3000 BPL .3 3010 STA $C010 3020 CMP #$8D 3030 BNE .5 3040 .4 PLA POP RETURN, SO RTS QUITS 3050 PLA 3060 .5 RTS 3070 *-------------------------------- 3080 FORMAT .HS 00.01.02 ABC 3090 .AS -" X " times 3100 .HS 03.04 DE 3110 .AS -" = " 3120 .HS 05.06 FG 3130 .AS -" X " times 3140 .HS 07.08 HI 3150 .HS 8D 3160 FMT.SZ .EQ *-FORMAT 3170 *-------------------------------- 3180 DIGITS 3190 A .BS 1 3200 B .BS 1 3210 C .BS 1 3220 D .BS 1 3230 E .BS 1 3240 F .BS 1 3250 G .BS 1 3260 H .BS 1 3270 I .BS 1 3280 *-------------------------------- 3290 DE .BS 1 3300 FG .BS 1 3310 HI .BS 1 3320 WXYZ .BS 2 3330 ABC .BS 2 3340 REM .BS 1 3350 *-------------------------------- 3360 FLAGS .BS 10 3370 *-------------------------------- |
The firmware in the IIgs for the 3.5"-drive includes the so-called "Protocol Converter" interface. We discussed this interface a little in previous issues, especially in the May 1986 article showing how to make DOS 3.3 work with the little drives.
Last month we reported a fix you need to make to our DOS 3.3 patches if you want it to work on a IIgs. Then last week I found out about another IIgs-related problem.
The 3.5"-drive firmware in the IIgs, when you call it through the Protocol Converter interface, stomps on four pagezero locations which are used by other programs. Locations $57, 58, 59, and 5A all are used without any concern for how they might already be in use. The firmware makes a great effort to save and restore all sorts of other locations, but these it just walks over, kicking sand like the big bully at the beach.
We first noticed the problem when using the S-C Macro Assembler with our 3.5" version of DOS 3.3. After any commands using the 3.5" drive, such as LOAD, SAVE, CATALOG, or whatever, the line number INCREMENT would be cleared to zero. That value is kept by S-C Macro in $5A, which the 3.5" firmware zeroes. This means after any disk operation you need to type INC 10, or INC with whatever increment you want to use. Luckily, the other three bytes are not actively in use during a disk operation.
In Applesoft these locations hold temporary string descriptors. I think they are only used while executing one statement, so it is possibly all right to share them with the firmware.
Apple probably is not going to do anything about this problem, because supposedly only NEW software would be coming in through the Protocol Converter interface. The ProDOS interface does not have any problem, because it either does not use those locations or it saves-restores them properly.
I think we probably are going to have to be the ones to change. Either we have to add code to our Unidisk DOS 3.3 to save and restore those four bytes, or we have to change the patch to use the ProDOS interface for reading and writing blocks rather than the Protocol Converter. The trouble with the latter approach is that the ProDOS interface requires the use of locations $42-$47, and four of these are already in use by higher levels of DOS 3.3! It is hard to win at this game. Either way we end up needing to save and restore four bytes.
Until we decide one way or the other, we need to at least come up with a fix for our Unidisk DOS 3.3 that will allow you to use it with the S-C Macro Assembler without losing yur INCrement every time you turn around. Of the four clobbered bytes, only $5A is critical to the assembler. I found a way to save enough bytes in Bill's code to slip this in. It had to be "slipped" in, so that the references to the DOS patches made in Bill's boot program do not have to be changed.
Lines 1970-2020 in Bill's Unidisk driver were:
1970 sta block 1980 ldy #5 1990 lda (iob.ptr),y 2000 lsr 2010 ora block 2020 sta block
Replace those lines with the following, which take the same space:
1970 asl 1980 ldy #5 1990 ora (iob.ptr),y 2000 ror 2010 sta block 2012 lda $5a 2014 pha 2016 nop 2020 nop
This performs the same function as the original code, but adds the feature of saving location $5A on the stack. At the exit we need to restore $5A from the stack, so look at lines 3100-3110 of Bill's program. They were:
3100 sta (iob.ptr),y 3110 rts
Change them to:
3100 jmp bobs.patch
Then insert new code as follows:
3202 bobs.patch 3203 sta (iob.ptr),y 3204 pla 3205 sta $5a 3206 rts
I entered the above patches by using the monitor peeking and poking commands, and they do seem to work correctly. Here are the commands I used:
00/BF8E:91 48 68 85 5A 60 00/BF82:4C 8E BF 00/BED6:0A A0 05 11 48 6A 8D 89 BF A5 5A 48 EA EA
You could add these commands to the EXEC file which loads the IIgs version of the S-C Macro Assembler.
In going over Bill's code while preparing this article, I also noticed that his line 1860, "STA LAST.BLOCK", should be "STA LAST.BLOCK+1". You probably should make this change regardless of what kind of computer you are using.
Here is the complete program with all the changes in place:
1000 *SAVE S.UNIDISK RWTS 1010 *-------------------------------- 1020 UNIDISK.SLOT .EQ 5 1030 1040 MY.COMMAND .EQ $26 1050 MY.BUFFER.POINTER .EQ $3C 1060 IOB.BUFFER.POINTER .EQ $3E 1070 IOB.PTR .EQ $48 1080 1090 MY.BUFFER .EQ $BB00 1100 1110 PATCH.POINT .EQ $BD12 1120 PATCH.RETURN .EQ $BD15 1130 1140 PC.DISPATCH .EQ UNIDISK.SLOT*$100+$C000 1150 1160 PRBYTE .EQ $FDDA 1170 COUT .EQ $FDED 1180 *-------------------------------- 1190 .OR $803 1200 .TF RWTS 3.5 1210 1220 INSTALL 1230 LDX #6 make sure we have a 1240 .1 LDA ID.TABLE,X protocol converter 1250 CMP UNIDISK.SLOT*$100+$C001,X 1260 BNE NO.PC 1270 DEX 1280 DEX 1290 BPL .1 1300 1310 LDA #$4C patch in the JMP 1320 STA PATCH.POINT to our code 1330 LDA #MY.RWTS 1340 STA PATCH.POINT+1 1350 LDA /MY.RWTS 1360 STA PATCH.POINT+2 1370 LDA #$60 1380 STA $A54F disable INIT 1390 1400 MOVE LDY #IMAGE.SIZE+1 install our code 1410 .1 LDA IMAGE-1,Y 1420 STA MY.RWTS-1,Y 1430 DEY 1440 BNE .1 1450 1460 CLC 1470 LDA UNIDISK.SLOT*$100+$C0FF 1480 ADC #3 find protocol 1490 STA READ.CALL converter entry 1500 STA WRITE.CALL 1510 BNE DONE ...always 1520 1530 NO.PC LDX #0 1540 .1 LDA MESSAGES,X print an error message 1550 BEQ DONE 1560 JSR COUT 1570 INX 1580 BNE .1 1590 DONE JMP $3D0 1600 *-------------------------------- 1610 MESSAGES 1620 .HS 8D 1630 .AS -/No PC in slot / 1640 .DA #$B0+UNIDISK.SLOT 1650 .HS 878D00 1660 *-------------------------------- 1670 ID.TABLE .HS 20.FF.00.FF.03.FF.00 1680 * ^ ^ ^ ^ 1690 * Protocol Converter ID Bytes 1700 *-------------------------------- 1710 IMAGE .EQ * 1720 .PH $BEAF 1730 MY.RWTS 1740 CMP #UNIDISK.SLOT*$10 1750 BEQ MINE my call! 1760 TAX not mine, so do 1770 LDY #$F patched-over code 1780 JMP PATCH.RETURN and go back 1790 *-------------------------------- 1800 MINE 1810 LDY #$F 1820 CMP (IOB.PTR),Y check previous slot 1830 BEQ SET.BLOCK same, so go on 1840 STA (IOB.PTR),Y set previous slot 1850 LDA #$FF 1860 STA LAST.BLOCK+1 trash LAST.BLOCK 1870 1880 SET.BLOCK 1890 LDA #0 1900 STA BLOCK+1 1910 LDY #4 1920 LDA (IOB.PTR),Y get track 1930 .1 ASL 1940 ROL BLOCK+1 *16 1950 DEY 1960 BNE .1 1970 ASL 1980 LDY #5 1990 ORA (IOB.PTR),Y get sector 2000 ROR /2, odd/even into carry 2010 STA BLOCK 2012 LDA $5A SAVE $5A FOR IIGS 2014 PHA 2016 NOP 2020 NOP 2030 2040 SET.POINTERS 2050 LDA #MY.BUFFER 2060 STA MY.BUFFER.POINTER 2070 LDA /MY.BUFFER 2080 ADC #0 carry sets hi/lo half of buffer 2090 STA MY.BUFFER.POINTER+1 2100 LDY #8 2110 LDA (IOB.PTR),Y get IOB buffer 2120 STA IOB.BUFFER.POINTER 2130 INY 2140 LDA (IOB.PTR),Y 2150 STA IOB.BUFFER.POINTER+1 2160 2170 SET.DRIVE 2180 LDY #2 2190 LDA (IOB.PTR),Y get drive 2200 LDY #$10 2210 STA (IOB.PTR),Y set previous drive 2220 DEY 2230 DEY 2240 STA (IOB.PTR),Y set previous volume 2250 LSR 2260 BCS SET.COMMAND .CS. if D1 2270 LDA BLOCK add 800 to BLOCK if D2 2280 ADC #800 2290 STA BLOCK 2300 LDA BLOCK+1 2310 ADC /800 2320 STA BLOCK+1 2330 2340 SET.COMMAND 2350 LDY #$C 2360 LDA (IOB.PTR),Y get command 2370 BEQ GOOD.EXIT 2380 CMP #3 exit if not READ or WRITE 2390 BCS GOOD.EXIT 2400 STA MY.COMMAND save command 2410 2420 CHECK.FOR.RE.READ 2430 LDX #0 zero the flag 2440 LDY #1 check two bytes 2450 .1 LDA BLOCK,Y 2460 CMP LAST.BLOCK,Y compare 2470 BEQ .2 same, so go on 2480 INX different, so flag it 2490 STA LAST.BLOCK,Y and store new value 2500 .2 DEY 2510 BPL .1 now do low bytes 2520 TXA check the flag 2530 BNE READ if different, go read 2540 2550 CHECK.FOR.VTOC 2560 LDY #5 2570 LDA (IOB.PTR),Y get sector 2580 BNE SKIP.READ non-zero isn't VTOC 2590 DEY 2600 LDA (IOB.PTR),Y get track 2610 CMP #$11 2620 BNE SKIP.READ not $11 isn't VTOC 2630 2640 READ JSR PC.DISPATCH 2650 READ.CALL .EQ *-2 2660 .DA #1 READ 2670 .DA PARMLIST 2680 BCS ERROR.EXIT 2690 2700 SKIP.READ 2710 LDA MY.COMMAND check command 2720 CMP #2 2730 BEQ WRITE.MOVE.BUFFER 2740 2750 READ.MOVE.BUFFER 2760 LDY #0 2770 .1 LDA (MY.BUFFER.POINTER),Y 2780 STA (IOB.BUFFER.POINTER),Y 2790 INY 2800 BNE .1 2810 BEQ GOOD.EXIT ...always 2820 2830 WRITE.MOVE.BUFFER 2840 LDY #0 2850 .1 LDA (IOB.BUFFER.POINTER),Y 2860 STA (MY.BUFFER.POINTER),Y 2870 INY 2880 BNE .1 2890 2900 WRITE JSR PC.DISPATCH 2910 WRITE.CALL .EQ *-2 2920 .DA #2 WRITE 2930 .DA PARMLIST 2940 BCS ERROR.EXIT 2950 2960 GOOD.EXIT 2970 CLC 2980 LDA #0 2990 BEQ EXIT ...always 3000 3010 ERROR.EXIT 3020 CMP #$2B write protect? 3030 BEQ .1 3040 LDA #$40 make everything else DRIVE ERROR 3050 .HS 2C 3060 .1 LDA #$10 3070 SEC 3080 3090 EXIT LDY #$D 3100 JMP BOBS.PATCH 3110 3120 *-------------------------------- 3130 PARMLIST 3140 .DA #3 3 parameters 3150 .DA #1 unit number 3160 .DA MY.BUFFER buffer address 3170 BLOCK .BS 3 block number 3180 3190 LAST.BLOCK .HS FFFF 3200 *-------------------------------- 3202 BOBS.PATCH 3203 STA (IOB.PTR),Y 3204 PLA RESTORE $5A FOR IIGS 3205 STA $5A 3206 RTS 3207 *-------------------------------- 3210 .BS $BF97-* 3220 .EP 3230 IMAGE.END .EQ *-1 3240 IMAGE.SIZE .EQ IMAGE.END-IMAGE 3250 .LIF |
According to Apple's published specifications, all IIgs tool sets are supposed to contain a function which returns a version number. Furthermore I think we can expect that the version numbers will be updated whenever any changes are made to the tools. Hopefully, programs which use the tools can first check to see if the version in ROM or RAM is up-to-date enough to be used.
It would be nice to be able to run a little program which would list the current version number for all installed tools. I wrote such a program, and it is shown below.
Since I am running under control of the S-C Macro Assembler Version 2.0, my program will receive control from the emulation mode. Therefore lines 1110-1130 get me into full native mode so that I can call tools. When I am finished, lines 1280-1290 put me back in emulation mode and return to the S-C Macro control.
The body of the program is a loop to call each of the tool sets from 1 to $20. Function number 4 of each tool is supposed to return the version number. This function requires one two-byte parameter space on the stack, in which to return the version number. Lines 1170-1190 set up the call and call a tool, and lines 1200-1220 store any error code returned as well as the version number. If the tool is not installed we will get an error code of 0002.
To display the results I wrote a little subroutine which runs in emulation mode. This is convenient, since all of our friendly monitor ROM I/O routines left over from the old days are only callable from emulation mode. There are probably some nice tools in the IIgs to take the place of COUT, PRBYTE, CROUT, and others, but these old friends are certainly easier to use and to remember.
My display routine prints an encoded string. Any bytes in the string which have the high bit on are printed as ASCII characters by calling COUT at $FDED. Any byte in value between $01 and $7F is treated as an index into page zero. The contents of the indexed location are printed out in hexadecimal by calling PRBYTE at $FDDA. A $00 byte in the string marks the end.
PRINT.FORMAT is called with the Y-register pointing just before the first character to be printed. This arrangement lets me break after printing the version number, and then just call PRINT.FORMAT again to print the error number when there is one.
Note at line 1370 I get the status byte I pushed at line 1320 by using a stack-relative LDA. Then I used LSR to shift bit 0, which was the carry bit, into C. Why didn't I use PLP followed by a PHP here? It would save one byte and have the same net effect, right?
Well I tried that, and it didn't work. The reason is that the PLP-PHP is inside my emulation mode subroutine. In emulation mode it is impossible to keep the status bits in the m- and x-bit positions in a zero state. Regardless of what they were when I pushed them at line 1320, after a PLP-PHP operation in emulation mode they will both be 1's. This undid the calling program, which believed it was still in full 16-bit mode. Zap! Pow! Wham! Beep! So I used the safer approach.
If you want to adapt the display subroutine for other purposes and use it in an older Apple that doesn't have a 65816 in it, you can go back to the PLP-PHP method. You will also need to change the BRA opcodes at lines 1540 and 1560 to JMP's.
1000 *SAVE S.TOOL.VERSIONS 1010 *-------------------------------- 1020 .OP 65816 1030 *-------------------------------- 1040 TOOL .EQ $01,02 1050 VERSION .EQ $03,04 1060 ERROR .EQ $05,06 1070 *-------------------------------- 1080 * Program to display Version Numbers of All Tools 1090 *-------------------------------- 1100 DVN 1110 CLC 1120 XCE 1130 REP #$30 "Full" Native mode 1140 LDA ##$0401 For TOOL = $0401 to $0420 1150 STA TOOL 1160 *-------------------------------- 1170 .1 PEA 0 Make room for version number 1180 LDX TOOL Tool # and function 1190 JSR $E10000 Go get it! 1200 STA ERROR Any error code 1210 PLA Tool version # 1220 STA VERSION 1230 JSR DISPLAY.RESULTS 1240 INC TOOL Next tool set 1250 LDA TOOL ...only go so far... 1260 CMP ##$0421 1270 BCC .1 ...not there yet 1280 XCE ...that is far enough 1290 RTS 1300 *-------------------------------- 1310 DISPLAY.RESULTS 1320 PHP 1330 SEC 1340 XCE 1350 LDY #-1 1360 JSR PRINT.FORMAT 1370 LDA 1,S GET STATUS BYTE 1380 LSR TO SEE IF THERE WAS AN ERROR 1390 BCC .1 ...NO ERROR 1400 JSR PRINT.FORMAT 1410 .1 CLC 1420 XCE 1430 PLP 1440 RTS 1450 *-------------------------------- 1460 PRINT.FORMAT 1470 .1 INY 1480 LDA FORMAT,Y 1490 BMI .2 1500 BEQ .3 ...END OF FORMAT 1510 TAX 1520 LDA 0,X 1530 JSR $FDDA 1540 BRA .1 1550 .2 JSR $FDED 1560 BRA .1 1570 .3 RTS 1580 *-------------------------------- 1590 FORMAT .HS 8D 1600 .AS -"Tool " 1610 .DA #TOOL 1620 .AS -" version " 1630 .DA #VERSION+1,#VERSION 1640 .HS 00 1650 * 1660 .AS -" error " 1670 .DA #ERROR+1,#ERROR 1680 .HS 00 1690 *-------------------------------- |
Apple Assembly Line (ISSN 0889-4302) is published monthly by S-C SOFTWARE CORPORATION, P.O. Box 280300, Dallas, Texas 75228. Phone (214) 324-2050. Subscription rate is $18 per year in the USA, sent Bulk Mail; add $3 for First Class postage in USA, Canada, and Mexico; add $14 postage for other countries. Back issues are available for $1.80 each (other countries add $1 per back issue for postage).
All material herein is copyrighted by S-C SOFTWARE CORPORATION,
all rights reserved. (Apple is a registered trademark of Apple Computer, Inc.)