Apple Assembly Line
Volume 7 -- Issue 8 May 1987

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.

A New Pattern Search Monitor Command Bob Sander-Cederlof

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

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.

  1010 *--------------------------------
  1020 YSAVE  .EQ $34
  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 *--------------------------------
  1110 CROUT  .EQ $FD8E
  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  
  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 *--------------------------------
  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
  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
  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 *--------------------------------
  2160        PHP
  2170        SEC
  2180        XCE
  2190        PHP
  2200 *--------------------------------
  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 *--------------------------------
  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.

  1010 *--------------------------------
  1020 YSAVE  .EQ $34
  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
  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  
  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 *--------------------------------
  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
  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
  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 *--------------------------------
  2040        INY
  2050        CMP #$E1
  2060        BCC .1
  2070        CMP #$FB
  2080        BCS .1
  2090        AND #$DF
  2100 .1     RTS
  2110 *--------------------------------

Reading the IIgs ROMs Bob Sander-Cederlof

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:

     BSAVE ROM.FE.0,A$1000,L$4000
     BSAVE ROM.FE.4,A$1000,L$4000
     BSAVE ROM.FE.8,A$1000,L$4000
     BSAVE ROM.FE.C,A$1000,L$4000
     BSAVE ROM.FF.0,A$1000,L$4000
     BSAVE ROM.FF.4,A$1000,L$4000
     BSAVE ROM.FF.8,A$1000,L$4000
     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.

More About Patching Apple's ProDOS Releases Bob Sander-Cederlof

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.

     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"
     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"
     810  PRINT  CHR$ (4)"BSAVE PRODOS,TSYS,A$2000"
     820  PRINT  CHR$ (4)"LOCK PRODOS"
     900  REM ---ERROR HANDLER---
     915  ON E GOTO 930,940,950
     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.

ABC*DE=FG*HI Puzzle Solved Bob Sander-Cederlof

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?

  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 *--------------------------------
  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 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
  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
  2550        BEQ .9       ...YES, NO NEED TO LOOK FURTHER
  2560        CPX A
  2570        BEQ .9
  2580        STX B
  2590        TAX
  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 *--------------------------------

Problem with IIgs 3.5"-Drive Firmware Bob Sander-Cederlof

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:

  1010 *--------------------------------
  1020 UNIDISK.SLOT        .EQ 5
  1040 MY.COMMAND          .EQ $26
  1070 IOB.PTR             .EQ $48
  1090 MY.BUFFER           .EQ $BB00
  1110 PATCH.POINT         .EQ $BD12
  1120 PATCH.RETURN        .EQ $BD15
  1140 PC.DISPATCH         .EQ UNIDISK.SLOT*$100+$C000
  1160 PRBYTE              .EQ $FDDA
  1170 COUT                .EQ $FDED
  1180 *--------------------------------
  1190        .OR $803
  1200        .TF RWTS 3.5
  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
  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
  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
  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
  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 *--------------------------------
  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
  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
  2050        LDA #MY.BUFFER
  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
  2130        INY
  2140        LDA (IOB.PTR),Y
  2150        STA IOB.BUFFER.POINTER+1
  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
  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
  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
  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
  2650 READ.CALL .EQ *-2
  2660        .DA #1            READ
  2670        .DA PARMLIST
  2680        BCS ERROR.EXIT
  2700 SKIP.READ
  2710        LDA MY.COMMAND    check command
  2720        CMP #2
  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
  2840        LDY #0
  2850 .1     LDA (IOB.BUFFER.POINTER),Y
  2860        STA (MY.BUFFER.POINTER),Y 
  2870        INY
  2880        BNE .1
  2910 WRITE.CALL .EQ *-2
  2920        .DA #2            WRITE
  2930        .DA PARMLIST
  2940        BCS ERROR.EXIT
  2960 GOOD.EXIT
  2970        CLC
  2980        LDA #0
  2990        BEQ EXIT          ...always
  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
  3090 EXIT   LDY #$D
  3100        JMP BOBS.PATCH
  3120 *--------------------------------
  3140        .DA #3        3 parameters
  3150        .DA #1        unit number
  3160        .DA MY.BUFFER buffer address 
  3170 BLOCK  .BS 3         block number
  3200 *--------------------------------
  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
  3250        .LIF

IIgs Tool Set Version Numbers Bob Sander-Cederlof

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.

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