Apple Assembly Line
Volume 8 -- Issue 4January 1988

The Apple Assembly Line is still growing! I now am sending out over 300 copies per month! It is also growing in size, as you can see: this is the first 20 page issue.

In This Issue...

New Subscription Rates

For the first time since January of 1984, we are going to have to increase our subscription rates. The Post Office is raising the postage again, for the third time since then, and we have to respond. Of course postage is not the only expense that has increased, just the most recent, and most noticeable. Here are the old and new rates for a year's subscription:

  Newsletter Only, 12 issues:                Current  New
      Bulk Mail (USA only) . . . . . . . . . . $18    ---
      First Class Mail (USA, Canada, Mexico) . $21    $24
      All Other Countries  . . . . . . . . . . $32    $36

  Newsletter plus the Monthly Disk, 12 issues:
      USA, Canada, Mexico  . . . . . . . . . . $64    $64
      All Other Countries  . . . . . . . . . . $87    $90

You will notice that the Bulk Mail option is being phased out, as I am planning to mail by First Class to all USA subscribers. The reliability of Bulk Mail has been entirely too erratic, and increasingly so.

You will also notice that the price of a subscription including disk, delivered in the USA, Canada, or Mexico, has not increased at all. Maybe now is the time to upgrade, and save yourself a lot of typing?

The new prices go into effect for new subscriptions as of March 15, 1988. Renewals at the old prices (but no more bulk mail) will continue to be accepted through the 4th of April.


Another Peek Inside AppleWorks:
Interpretive String Display Subroutine
Bob Sander-Cederlof

At least twice in the last eight years I have published fancy message display subroutines. In the original Volume 1, Number 1, way back in October, 1980, I gave a really nice 40-column version. That one was actually used in a slightly different form inside a system that we developed for the American Heart Association for teaching Cardio-Pulmonary Resuscitation (CPR).

In the April 1987 issue I published an 80-column version for the //e and //c, which had similar but more powerful capabilities.

Last month I started revealing some of the code from inside AppleWorks (version 1.3). I covered parameter passing and some string handling subroutines, plus block move. I also described and printed the subroutine which polls the keyboard, so that you can type before being asked. POLL.KEYBOARD is called from all over everywhere, just to be sure no characters are lost. I mention that, because there is a call to it from the code I am unveiling this month. In the listing which follows, I have put a dummy POLL.KEYBOARD subroutine which simply does an RTS. In the real AppleWorks code, DISPLAY.STRING calls the real POLL.KEYBOARD.

As I mentioned last month, I am NOT showing in these pages the exact code you would find inside AppleWorks. Since my purpose is not to document AppleWorks, but rather to cull out generally useful code which we can adapt and use, I have rearranged and modified a little. My versions are, in general, shorter and faster. Maybe whoever is currently maintaining AppleWorks at Claris will notice and use these improvements.

In case you ARE interested in documenting AppleWorks, or just want to see what I changed, I have included comment lines with each segment of code which show what the corresponding address was inside AppleWorks 1.3. As was true last month, all the code shown here is found in the main segment called APLWORKS.SYSTEM, which begins at $1000 once it is up and running.

I will begin with a general description of features. DISPLAY.STRING began at $14D1, and there are numerous calls to it in the code. There is also a JMP vector to it in the JMP table which begins at $1000; if you find any JSR $1015 instructions in any of the other segments, they are calling this DISPLAY.STRING subroutine. Unlike all of the subroutines I discussed last month, this subroutine does not expect to find parameters following the JSR which called it. Instead, it expects the length of the string to be in the A-register, and the address of the string to be in locations $80,81. (There is another subroutine which uses the parameter-passing protocol to display a string which starts with a length byte; it simply sets up $80, $81, and the A-register and calls DISPLAY.STRING. You can find it at $2093, with a JMP vector at $1087.)

DISPLAY.STRING does not use any Apple firmware at all. The display techniques used here work faster than the firmware, because they are dedicated to 80-columns and do not have to retain any compatibility with older machines. If you remember that the original Apple //e firmware scrolled the 80-column screen with a slow zigzag motion, you can see why Rupert Lissner decided to code his own.

The characters within the string to be displayed consist of control codes and displayable characters. Displayable characters include the full upper- and lower-case alphabet, numbers, and punctuation signs; all of these can be displayed in both normal and inverse mode. All 32 "mouse-text" characters can also be displayed, although the only one I have noticed in quickly scanning through the AppleWorks messages is the open-apple.

Inverse and normal mode is controlled by a flag byte, which I have called INVERSE.FLAG in my code. It is located at $14D0 in AppleWorks. If that byte contains $00, characters will display in inverse; if $80, normal. A pair of control codes lets you switch INVERSE.FLAG back and forth from within a string, or you can directly set it between calls to DISPLAY.STRING.

The following table shows the hex values for the various character groups as interpreted by DISPLAY.STRING:

       00-1F  Control Codes
       20-7F  96 ASCII Characters
       80-9F  32 Mouse Text Characters

The codes 20-7F display in either normal or inverse, depending on INVERSE.FLAG, as described above. Codes C0-DF duplicate 80-9F, displaying the mouse text characters; codes A0-BF and E0-FF both display the 32 lower-case characters in inverse mode.

There are a couple of un-features in DISPLAY.STRING. There is a JMP $1815 instruction located at $1815 inside AppleWorks 1.3. This, of course, hangs up the Apple. The only way out is by hitting RESET. DISPLAY.STRING goes to this HANG.UP code under some circumstances. Since it is a deadly trap, I assume the author of AppleWorks used to have some debugging code there. It gets called from all over, when errors occur that are programming bugs. There is even a JMP vector to it in the JMP table, at $101B! I have left the jumps and branches to HANG.UP in my version, but you might want to modify them to do something reasonable if you are going to use DISPLAY.STRING yourself.

The other un-feature is what happens if you try to print past the right edge of the text window. You might think it would automatically wrap to the next line, like the standard Apple firmware does; no, it just piles up the characters at the end of the line like old manual typewriters used to do. Possibly you might consider a third un-feature to be the limitation of 255 characters in a string, but this is easy enough to work around. My demonstration program, included at the end of the following listing, shows one way.

There are 17 control codes interpreted by DISPLAY.STRING, and room for adding 15 more. Most of these are single byte codes, but two are followed by parameter bytes. Here is a table of the codes:

       01 -- Clear from cursor to end of line.
       02 -- Clear entire line cursor is on.
       03 -- Home (go to 0,0 and clear window).
       04 -- Clear from cursor to end of window.
   05xxyy -- Move cursor to column xx, row yy of window
             (HANG.UP if string ends with no xxyy).
       06 -- Move cursor left (HANG.UP if beyond window).
       07 -- Move cursor right (HANG.UP if beyond window).
       08 -- Move cursor up, scroll if already at top.
       09 -- Move cursor down, scroll if alread at bottom.
       0A -- Set inverse mode.
       0B -- Set normal mode.
       0C -- Store current cursor position as bottom-right
             corner of the window.
       0D -- Move cursor to beginning of current line.
       0E -- Store current cursor position as top-left
             corner of the window.
       0F -- Set up a full-screen window.
       10 -- Beep!  (the AppleWorks low-tone bell).
     11xx -- Slide the screen sideways xx columns.
             If xx>0 slide to right; if xx<0, slide left.

You can see that setting windows is fairly easy. Use code 05xxyy to position the cursor where you want the bottom-right corner of the new window to be, and then use code 0C to store it; then use 05xxyy to position the cursor where you want the top-left corner of the new window to be, and then use code 0E to make the new window. Since all 05xxyy moves are relative to the current window, you need to set the new bottom-right corner first. (You should now have a clue how AppleWorks nests the file folders on the screen.)

Since AppleWorks does not use any of the Apple firmware, it is also not tied to the standard page-zero locations for window info and cursor position. DISPLAY.STRING uses $10 through $13 to store the window definition. It is not the same as Apple's window definition bytes at $20-$23. Apple's firmware uses a starting column and a width, whereas AppleWorks uses a starting column and an ending column. The bytes are in a different order, too. See lines 1030-1130 for DISPLAY.STRINGs page zero-usage. The two bytes defined in lines 1120-1130, at $F0 and $F1, are the ones AppleWorks uses. However, you could change those two lines to share $18 and $19 with the labels defined on lines 1090-1100, if you wish.

Lines 1150-1320 define two macros, one for storing a byte on the screen and one for picking a byte off the screen. There is another macro definition in lines 4820-4840, for the function vector table. I decided to put these in as macros to shorten the program listing so it would fit in this issue of AAL. It also makes the code in the left-right scroll subroutine easier to follow. My code inside these macros is different from the code in AppleWorks: it is shorter, and on the average one cycle SLOWER. I more than made up for the speed loss in other places, though.

You will find the main body of DISPLAY.STRING in lines 1390-1920. Lines 1470-1480 are curious. They cause the entire call to DISPLAY.STRING to be ignored if the contents of $A4 is non-zero. I don't know why or when AppleWorks would use this. Probably you will want to delete these two lines if you use a revision of DISPLAY.STRING in your own programs. In that case, you would want to substitute a LDA #0 instruction, so that line 1490 would store a zero as the beginning position in the string to be displayed. Line 1500 calls the POLL.KEYBOARD subroutine, which as I mentioned above is just a dummy routine in this listing. You will probably want to delete this line too.

By the time we get to line 1520, everything is set up. A pointer to the first character of the string is in $80,81; POSITION.IN.STRING holds the index relative to that pointer for the next character to be processed; INVERSE.FLAG is either 00 or 80; and BYTES.IN.STRING is set to the string length. We come back to line 1520 for each character in the string, except for the parameter bytes on control codes 05 and 11.

Lines 1520-1540 test to see if we have finished the string, and go to an RTS if so. Lines 1550-1670 pick up the next character, and decide how to process it according to the range: values 80-FF are treated as mouse text, even though we only expect 80-9F for these; values 00-1F are control (function) codes; and values 20-7F are regular ASCII characters.

Mouse text characters are really put on the screen by using values from $40 to $5F, so lines 1680-1700 do the honors.

Regular ASCII characters are EORed with the INVERSE.FLAG in line 1600. If the result is negative, we have a "normal" character; if positive, "inverse". Normal characters are ready now to display, but inverse take some care. The range 40-5F should print as letters rather than mouse text, so they are mapped down to 00-1F in lines 1620-1670.

Control codes are handled in lines 1830-1920. This is not the same way AppleWorks did it. AppleWorks used the trick of pushing the table address on the stack and doing an RTS to effectively JMP to the function code; then each function code processor finished by doing a JMP $14E1 (my line 1520, label .1). My code effectively does a JSR to the function code, so each processor can finish by doing an RTS. This saves space, but is a tiny bit slower. However, I made up for the speed loss by eliminating one unnecessary range check. AppleWorks tested the function code range to be sure it was no larger than $11; if it was larger, AppleWorks jumped to HANG.UP. My code gives the same results, by merely extending the function code table in lines 4870-5190 to include all 32 vectors. The extra 28 bytes of table are more than saved elsewhere. By making the function code processors into subroutines which end with an RTS I have made them accessible to JSR calls from anywhere. This could save even more space.

You can see that to add more functions you merely have to write the processing subroutines and enter the vectors in the function code table using the >VEC macro. I can think of several neat additions. For example, I might use code 00 to initialize full screen, home, normal mode all in one code. It might also be useful to add a code to draw a file folder in the current window. A single code could shrink the window one notch, clear it, and draw a folder. Another code could pop back out to the next larger window. Let your imagination and creativity loose!

I wrote a little demonstration program, shown in lines 6040-6270. This program steps through a list of strings. Each string starts with a length byte. When a length byte of 00 is found, the demonstration stops. I have listed the demonstration strings in raw form to save paper, in lines 6290-6970. The demonstration is not too fancy, but it is fun. I display some characters, then move them around the screen in all four directions, with intermittent beeps to slow it down enough to see. Then I wipe it clean and display the entire character set.

Let me know how you like this series of articles, and what kind of uses you find for the code. If you come up with some really great new function codes for DISPLAY.STRING, send them in and we'll share them in future issues.

  1000        .LIST MOFF   Do not show Macro Expansions (save paper)
  1010 *SAVE AW.SUBS.2
  1020 *--------------------------------
  1030 AW.LEFT    .EQ $10  DEFINES CURRENT WINDOW
  1040 AW.TOP     .EQ $11       "
  1050 AW.RIGHT   .EQ $12       "
  1060 AW.BOTTOM  .EQ $13       "
  1070 AW.CH      .EQ $14  CURSOR HORIZONTAL
  1080 AW.CV      .EQ $15  CURSOR VERTICAL
  1090 AW.BASE    .EQ $16,17
  1100 AW.BASE2   .EQ $18,19
  1110 *--------------------------------
  1120 SHUFFLE.SOURCE      .EQ $F0
  1130 SHUFFLE.DEST        .EQ $F1
  1140 *--------------------------------
  1150        .MA ST.SCRN
  1160        LSR          LSB into Carry
  1170        TAY          Index into Y-reg
  1180        PLA          Get char again
  1190        BCS :1       ...Odd character, in Main RAM
  1200        STA $C055    ...Even character, in Aux RAM
  1210 :1     STA (AW.BASE),Y
  1220        STA $C054
  1230        .EM
  1240 *--------------------------------
  1250        .MA LD.SCRN
  1260        LSR          LSB into Carry
  1270        TAY          Index into Y-reg
  1280        BCS :1       ...Odd character, in Main RAM
  1290        STA $C055    ...Even character, in Aux RAM
  1300 :1     LDA (AW.BASE),Y
  1310        STA $C054
  1320        .EM
  1330 *--------------------------------
  1340 INVERSE.FLAG .BS 1  00=display chars inverse, 80=display chars normal
  1350 BYTES.IN.STRING     .BS 1
  1360 POSITION.IN.STRING  .BS 1
  1370 SCROLL.DIRECTION    .BS 1
  1380 *--------------------------------
  1390 *   DISPLAY A STRING WITH FUNCTION CODES
  1400 *      (A) = # BYTES IN STRING
  1410 *      ($80,81) = Address of string
  1420 *      ($A4) = flag:  if 00 display, else ignore.
  1430 *   at $14D1 in AppleWorks 1.3
  1440 *--------------------------------
  1450 DISPLAY.STRING
  1460        STA BYTES.IN.STRING
  1470        LDA $A4      If non-zero, don't display anything
  1480        BNE .99      ...to an RTS
  1490        STA POSITION.IN.STRING
  1500        JSR POLL.KEYBOARD    GET KEY IF ANY
  1510 *--------------------------------
  1520 .1     LDY POSITION.IN.STRING
  1530        CPY BYTES.IN.STRING
  1540        BCS .99      ...to an RTS
  1550        INC POSITION.IN.STRING
  1560        LDA ($80),Y  GET CHAR FROM STRING
  1570        BMI .2       ...80-FF, Mouse char for screen
  1580        CMP #$20
  1590        BCC .5       ...00-1F
  1600        EOR INVERSE.FLAG
  1610        BMI .3       ...Normal Char
  1620        CMP #$40     ...Inverse char
  1630        BCC .3       ...00-3F, inverse A-Z, 0-9, punctuation
  1640        CMP #$60
  1650        BCS .3       ...60-7F, inverse a-z, punctuation
  1660        AND #$BF     map 40-5F into 00-1F, more A-Z
  1670        BCC .3       ...always
  1680 *---Display a Mouse Char---------
  1690 .2     AND #$7F     ...map into 40-5F, mouse char range
  1700        ORA #$40
  1710 *---Display Char in A-register---
  1720 .3     PHA          Save the character
  1730        JSR BASE.CALC.CV
  1740        LDA AW.CH    Char position on line
  1750        >ST.SCRN     Store character on screen
  1760 *--------------------------------
  1770        LDX AW.CH
  1780        CPX #79      End of line yet?
  1790        BCS .1       ...yes, stick there, pile em' up
  1800        INC AW.CH    ...no, advance CH
  1810        BNE .1       ...always
  1820 *--------------------------------
  1830 .5     JSR .6       CALL THE FUNCTION
  1840        JMP .1       ...ALWAYS
  1850 *--------------------------------
  1860 .6     ASL          Convert code to index
  1870        TAX
  1880        LDA FUNTBL+1,X
  1890        PHA
  1900        LDA FUNTBL,X
  1910        PHA
  1920 .99    RTS
  1930 *--------------------------------
  1940 *   FUNCTION 03 -- CLEAR ENTIRE WINDOW, CURSOR TO TOP-LEFT
  1950 *   at $154A in AppleWorks 1.3
  1960 *--------------------------------
  1970 FUN.HOME
  1980        LDA AW.LEFT
  1990        STA AW.CH
  2000        LDA AW.TOP
  2010        STA AW.CV
  2020 *--------------------------------
  2030 *   FUNCTION 04 -- CLEAR REST OF WINDOW
  2040 *   at $1552 in AppleWorks 1.3
  2050 *--------------------------------
  2060 FUN.CLR.CH.TO.EOS
  2070        LDA AW.CH    SAVE CH AND CV
  2080        PHA
  2090        LDA AW.CV
  2100        PHA
  2110 .1     JSR CLR.CH.TO.EOL
  2120        LDA AW.CV
  2130        CMP AW.BOTTOM
  2140        BCS .2       ...THAT WAS THE BOTTOM LINE
  2150        INC AW.CV
  2160        LDA AW.LEFT
  2170        STA AW.CH
  2180        BCC .1       ...ALWAYS
  2190 .2     PLA
  2200        STA AW.CV
  2210        PLA
  2220        STA AW.CH
  2230        RTS
  2240 *--------------------------------
  2250 *   FUNCTION 05 -- GO TO X,Y IN THIS WINDOW
  2260 *      Equivalent to HTAB X, VTAB Y
  2270 *      05 XX YY in string
  2280 *   at $1576 in AppleWorks 1.3
  2290 *--------------------------------
  2300 FUN.GOTO.XY
  2310        LDY POSITION.IN.STRING
  2320        LDA ($80),Y
  2330        INY
  2340        CPY BYTES.IN.STRING
  2350        BCS FUN.HANG.UP
  2360        ADC AW.LEFT
  2370        STA AW.CH
  2380        LDA ($80),Y
  2390        ADC AW.TOP
  2400        STA AW.CV
  2410        INY
  2420        STY POSITION.IN.STRING
  2430        RTS
  2440 *--------------------------------
  2450 *   FUNCTION 06 -- BACK UP CURSOR
  2460 *   at $1593 in AppleWorks 1.3
  2470 *--------------------------------
  2480 FUN.CURSOR.LEFT
  2490        LDA AW.CH
  2500        CMP AW.LEFT
  2510        BEQ FUN.HANG.UP
  2520        DEC AW.CH
  2530        RTS
  2540 *--------------------------------
  2550 *   at $1599 and $1815 in AppleWorks 1.3
  2560 FUN.HANG.UP JMP FUN.HANG.UP
  2570 *--------------------------------
  2580 *   FUNCTION 07 -- CURSOR RIGHT
  2590 *   at $15A1 in AppleWorks 1.3
  2600 *--------------------------------
  2610 FUN.CURSOR.RIGHT
  2620        LDA AW.CH
  2630        CMP AW.RIGHT
  2640        BEQ FUN.HANG.UP
  2650        INC AW.CH
  2660        RTS
  2670 *--------------------------------
  2680 *   FUNCTION 08 -- CURSOR UP (SCROLL DOWN IF NECESSARY)
  2690 *   at $15AB in AppleWorks 1.3
  2700 *--------------------------------
  2710 FUN.CURSOR.UP
  2720        LDA AW.CV
  2730        CMP AW.TOP
  2740        BEQ .1       ...ALREADY AT TOP, SCROLL DOWN
  2750        DEC AW.CV
  2760        RTS
  2770 .1     LDA AW.BOTTOM
  2780        LDX #-1
  2790        BNE SCROLL   ...ALWAYS
  2800 *--------------------------------
  2810 *   FUNCTION 09 -- CURSOR DOWN (SCROLL UP IF NECESSARY)
  2820 *   at $15BC in AppleWorks 1.3
  2830 *--------------------------------
  2840 FUN.CURSOR.DOWN
  2850        LDA AW.CV
  2860        CMP AW.BOTTOM
  2870        BEQ .1       ...ALREADY AT BOTTOM, SCROLL UP
  2880        INC AW.CV
  2890        RTS
  2900 .1     LDA AW.TOP
  2910        LDX #1
  2920 SCROLL
  2930        STX SCROLL.DIRECTION  01 IF UP, FF IF DOWN
  2940        PHA          SAVE LINE NUMBER
  2950        JSR BASE.CALC.A
  2960 .1     LDA AW.BASE
  2970        STA AW.BASE2
  2980        LDA AW.BASE+1
  2990        STA AW.BASE2+1
  3000        PLA          GET LINE NUMBER AGAIN
  3010        CMP AW.CV    IS IT THE LAST LINE?
  3020        BEQ FUN.CLR.LINE
  3030        CLC
  3040        ADC SCROLL.DIRECTION
  3050        PHA          SAVE SOURCE LINE NUMBER
  3060        JSR BASE.CALC.A
  3070        LDA AW.LEFT
  3080        TAX
  3090        LSR
  3100        TAY
  3110        BCS .3       START WITH ODD COLUMN
  3120 .2     STA $C055    EVEN COLUMNS IN AUX MEM
  3130        LDA (AW.BASE),Y
  3140        STA (AW.BASE2),Y
  3150        STA $C054    BACK TO MAIN MEM
  3160        CPX AW.RIGHT
  3170        BEQ .1       ...END OF THIS LINE
  3180        INX
  3190 .3     LDA (AW.BASE),Y
  3200        STA (AW.BASE2),Y
  3210        CPX AW.RIGHT
  3220        BEQ .1       ...END OF THIS LINE
  3230        INX
  3240        INY
  3250        BNE .2       ...ALWAYS
  3260 *--------------------------------
  3270 *   FUNCTION 02 -- CLEAR ENTIRE CURRENT LINE
  3280 *   at $1540 in AppleWorks 1.3
  3290 *--------------------------------
  3300 FUN.CLR.LINE
  3310        LDA AW.LEFT
  3320        STA AW.CH
  3330 *--------------------------------
  3340 *   FUNCTION 01 -- CLEAR REST OF CURRENT LINE
  3350 *   at $1544 in AppleWorks 1.3
  3360 *--------------------------------
  3370 FUN.CLR.CH.TO.EOL
  3380        JMP CLR.CH.TO.EOL
  3390 *--------------------------------
  3400 *   FUNCTION 0A -- INVERSE
  3410 *   at $160B in AppleWorks 1.3
  3420 *--------------------------------
  3430 FUN.INVERSE
  3440        LDA #$00
  3450        .HS 2C       SKIP OVER LDA #$80
  3460 *--------------------------------
  3470 *   FUNCTION 0B -- NORMAL
  3480 *   at $160F in AppleWorks 1.3
  3490 *--------------------------------
  3500 FUN.NORMAL
  3510        LDA #$80
  3520        STA INVERSE.FLAG
  3530        RTS
  3540 *--------------------------------
  3550 *   FUNCTION 0C -- DEFINE BOTTOM RIGHT CORNER
  3560 *   at $1617 in AppleWorks 1.3
  3570 *--------------------------------
  3580 FUN.CORNER.BR
  3590        LDA AW.CH
  3600        LDX AW.CV
  3610 SET.CORNER.BR
  3620        STA AW.RIGHT
  3630        STX AW.BOTTOM
  3640        RTS
  3650 *--------------------------------
  3660 *   FUNCTION 0D -- CURSOR TO BEGINNING OF LINE
  3670 *      Equivalent to RETURN without LINEFEED
  3680 *   at $1622 in AppleWorks 1.3
  3690 *--------------------------------
  3700 FUN.CURSOR.BOL
  3710        LDA AW.LEFT
  3720        STA AW.CH
  3730        RTS
  3740 *--------------------------------
  3750 *   FUNCTION 0E -- DEFINE TOP LEFT CORNER
  3760 *   at $1629 in AppleWorks 1.3
  3770 *--------------------------------
  3780 FUN.CORNER.TL
  3790        LDA AW.CH
  3800        LDX AW.CV
  3810        STA AW.LEFT
  3820        STX AW.TOP
  3830        RTS
  3840 *--------------------------------
  3850 *   FUNCTION 0F -- SET FULL SCREEN WINDOW
  3860 *   at $1634 in AppleWorks 1.3
  3870 *--------------------------------
  3880 FUN.FULL.SCREEN
  3890        LDA #0
  3900        STA AW.TOP
  3910        STA AW.LEFT
  3920        LDA #79
  3930        LDX #23
  3940        BNE SET.CORNER.BR   ...ALWAYS
  3950 *--------------------------------
  3960 *   FUNCTION 10 -- "BEEP!"
  3970 *   at $1645 in AppleWorks 1.3
  3980 *--------------------------------
  3990 FUN.BEEP
  4000        LDY #149
  4010 .1     LDA #149
  4020 .2     SEC
  4030        SBC #1
  4040        SEC
  4050        SEC
  4060        BNE .2       11*149-1 = 1638 CYCLES IN INNER LOOP
  4070        LDA $C030    TOGGLE SPEAKER
  4080        DEY
  4090        BNE .1       1649 CYCLES BETWEEN CLICKS
  4100        LDA #1       DELAY ABOUT 1/10 SECOND
  4110        JMP DELAY.TENTHS
  4120 *--------------------------------
  4130 *   FUNCTION 11 -- SHUFFLE SCREEN LEFT OR RIGHT
  4140 *      Following byte is scroll distance and direction:
  4150 *      If positive, SCROLL RIGHT; else SCROLL LEFT.
  4160 *   at $165E in AppleWorks 1.3
  4170 *--------------------------------
  4180 FUN.SHUFFLE
  4190        LDY POSITION.IN.STRING
  4200        LDA ($80),Y  If positive, SCROLL RIGHT; else SCROLL LEFT
  4210        STA SCROLL.DIRECTION
  4220        INC POSITION.IN.STRING
  4230        LDA AW.TOP   POINT TO TOP LINE FIRST
  4240 .1     PHA
  4250        JSR BASE.CALC.A
  4260        LDA AW.RIGHT
  4270        BIT SCROLL.DIRECTION
  4280        BPL .2
  4290        LDA AW.LEFT
  4300 .2     STA SHUFFLE.DEST
  4310        SEC
  4320        SBC SCROLL.DIRECTION
  4330        STA SHUFFLE.SOURCE
  4340        JSR SCROLL.LEFT.OR.RIGHT
  4350        PLA
  4360        CMP AW.BOTTOM
  4370        BEQ .3
  4380        CLC
  4390        ADC #1
  4400        BNE .1
  4410 .3     RTS
  4420 *--------------------------------
  4430 SCROLL.LEFT.OR.RIGHT
  4440        LDA SCROLL.DIRECTION
  4450        BPL .6       ...shuffle right
  4460        BMI .2       ...shuffle left
  4470 *---Scroll Left------------------
  4480 .1     INC SHUFFLE.DEST
  4490        INC SHUFFLE.SOURCE
  4500 .2     LDA #" "     blank, in case off edge
  4510        LDY SHUFFLE.SOURCE
  4520        CPY AW.RIGHT
  4530        BCC .3
  4540        BNE .4       ...off the edge, use a blank
  4550 .3     TYA          Get source pointer
  4560        >LD.SCRN     Get character from screen
  4570 .4     PHA          Save the character
  4580        LDA SHUFFLE.DEST  destination pointer
  4590        >ST.SCRN     Store the character on the screen
  4600        LDA SHUFFLE.DEST  destination pointer
  4610        CMP AW.RIGHT      was it the right edge?
  4620        BNE  .1           ...no, keep shuffling
  4630        RTS
  4640 *---Scroll Right-----------------
  4650 .5     DEC SHUFFLE.DEST
  4660        DEC SHUFFLE.SOURCE
  4670 .6     LDA #" "     blank, in case off edge
  4680        LDY SHUFFLE.SOURCE
  4690        BMI .7       ...off edge, use blank
  4700        CPY AW.LEFT
  4710        BCC .7       ...off edge, use blank
  4720        TYA          Get source pointer
  4730        >LD.SCRN     Get character from screen
  4740 .7     PHA          Save the character
  4750        LDA SHUFFLE.DEST  destination pointer
  4760        >ST.SCRN     Store the character on the screen
  4770        LDA SHUFFLE.DEST  destination pointer
  4780        CMP AW.LEFT       was it the left edge?
  4790        BNE  .5           ...no, keep shuffling
  4800        RTS
  4810 *--------------------------------
  4820        .MA VEC
  4830        .DA FUN.]1-1
  4840        .EM
  4850 *--------------------------------
  4860 *   at $1779 in AppleWorks 1.3
  4870 FUNTBL
  4880      >VEC HANG.UP        00
  4890      >VEC CLR.CH.TO.EOL  01
  4900      >VEC CLR.LINE       02
  4910      >VEC HOME           03
  4920      >VEC CLR.CH.TO.EOS  04
  4930      >VEC GOTO.XY        05
  4940      >VEC CURSOR.LEFT    06
  4950      >VEC CURSOR.RIGHT   07
  4960      >VEC CURSOR.UP      08
  4970      >VEC CURSOR.DOWN    09
  4980      >VEC INVERSE        0A
  4990      >VEC NORMAL         0B
  5000      >VEC CORNER.BR      0C
  5010      >VEC CURSOR.BOL     0D
  5020      >VEC CORNER.TL      0E
  5030      >VEC FULL.SCREEN    0F
  5040      >VEC BEEP           10
  5050      >VEC SHUFFLE        11
  5060      >VEC HANG.UP        12
  5070      >VEC HANG.UP        13
  5080      >VEC HANG.UP        14
  5090      >VEC HANG.UP        15
  5100      >VEC HANG.UP        16
  5110      >VEC HANG.UP        17
  5120      >VEC HANG.UP        18
  5130      >VEC HANG.UP        19
  5140      >VEC HANG.UP        1A
  5150      >VEC HANG.UP        1B
  5160      >VEC HANG.UP        1C
  5170      >VEC HANG.UP        1D
  5180      >VEC HANG.UP        1E
  5190      >VEC HANG.UP        1F
  5200 *--------------------------------
  5210 POLL.KEYBOARD RTS    (at $1FA7 in AppleWorks 1.3)
  5220 *--------------------------------
  5230 *   Calculate Address of Screen Line
  5240 *   at $1717 in AppleWorks 1.3
  5250 *--------------------------------
  5260 BASE.CALC.CV
  5270        LDA AW.CV
  5280 BASE.CALC.A
  5290        CMP #$FF     <<<filled in with current line number>>>
  5300 CURR.LINE  .EQ *-1
  5310        BEQ .2
  5320        STA CURR.LINE
  5330        LSR          0000ABCD--E
  5340        AND #$03     000000CD--E
  5350        ORA #$04     000001CD--E
  5360        STA AW.BASE+1
  5370        LDA CURR.LINE
  5380        AND #$18     000AB000--E
  5390        BCC .1       E=0
  5400        ADC #$7F     E00AB000
  5410 .1     STA AW.BASE
  5420        ASL          00AB0000
  5430        ASL          0AB00000
  5440        ORA AW.BASE  EABAB000
  5450        STA AW.BASE
  5460 .2     RTS
  5470 *--------------------------------
  5480 *   Blank Line from Cursor to End of Line
  5490 *   at $173A in AppleWorks 1.3
  5500 *--------------------------------
  5510 CLR.CH.TO.EOL
  5520        LDA AW.RIGHT
  5530        LSR
  5540        STA N.OVER.2
  5550        ROL          GET AW.RIGHT AGAIN
  5560        SEC
  5570        SBC #1
  5580        LSR
  5590        STA N.MINUS.1.OVER.2
  5600        JSR BASE.CALC.CV
  5610        LDA AW.CH
  5620        LSR
  5630        PHA
  5640 *---Clear Even Columns-----------
  5650        TAY
  5660        LDA #" "
  5670        STA $C055    INTO AUX MEM
  5680        BCC .2
  5690 .1     INY
  5700 .2     CPY N.OVER.2
  5710        BEQ .3
  5720        BCS .4
  5730 .3     STA (AW.BASE),Y
  5740        JMP .1
  5750 .4     STA $C054    BACK TO MAIN MEM
  5760 *---Clear Odd Columns------------
  5770        PLA
  5780        TAY
  5790        LDA #" "
  5800 .5     STA (AW.BASE),Y
  5810        INY
  5820        CPY N.MINUS.1.OVER.2
  5830        BCC .5
  5840        BEQ .5
  5850        RTS
  5860 *--------------------------------
  5870 N.OVER.2            .BS 1
  5880 N.MINUS.1.OVER.2    .BS 1
  5890 *--------------------------------
  5900 *   DELAY ABOUT (A) TENTHS OF A SECOND
  5910 *   at $1FD1 in AppleWorks 1.3
  5920 *--------------------------------
  5930 DELAY.TENTHS
  5940        TAY
  5950 .1     LDX #100     ...ABOUT 100 MILLISECONDS
  5960 .2     CLC
  5970 .3     ADC #1
  5980        BCC .3       ...LOOP IS 256*5 CYCLES
  5990        DEX
  6000        BNE .2       (5*256+6)*100 = 128600 CYCLES
  6010        DEY
  6020        BNE .1       128606*A CYCLES
  6030        RTS
  6040 *--------------------------------
  6050 *   Some Demonstrations
  6060 *--------------------------------
  6070 T
  6080        LDA #MY.STRINGS
  6090        LDX /MY.STRINGS
  6100 .1     STA $80
  6110        STX $81
  6120        LDY #0
  6130        LDA ($80),Y
  6140        BEQ .99      ...FINISHED
  6150        PHA          SAVE LENGTH
  6160        INC $80
  6170        BNE .2
  6180        INC $81
  6190 .2     JSR DISPLAY.STRING
  6200        CLC
  6210        PLA          GET LENGTH
  6220        ADC $80      ADD TO POINTER
  6230        LDX $81
  6240        BCC .1
  6250        INX
  6260        BCS .1       ...ALWAYS
  6270 .99    RTS
  6280 *--------------------------------
  6290 MY.STRINGS
  6300        .DA #Z1
  6310 A1     .HS 0F.03.0B     Full Scrn, Home, Normal
  6320        .AS /ABCDECDEFGHIJKLMNOPQRSTUVWXYZ !@#$%^&*()-+=[]\;:'",.<>?{}|/
  6330        .HS 0A.0D.09      Inverse, RETURN, LINEFEED
  6340        .AS /ABCDECDEFGHIJKLMNOPQRSTUVWXYZ !@#$%^&*()-+=[]\;:'",.<>?{}|/
  6350 Z1     .EQ *-A1
  6360 *--------------------------------
  6370        .DA #Z2
  6380 A2     .HS 0B.0D.09      Normal, RETURN, LINEFEED
  6390        .AS "abcdefghijklmnopqrstuvwxyz / 0123456789 _ "
  6400        .HS 0A.0D.09      Inverse, RETURN, LINEFEED
  6410        .AS "abcdefghijklmnopqrstuvwxyz / 0123456789 _ "
  6420 Z2     .EQ *-A2
  6430 *--------------------------------
  6440        .DA #Z3
  6450 A3     .HS 0B.0D.09      Normal, RETURN, LINEFEED
  6460        .AS -/@ABCDECDEFGHIJKLMNOPQRSTUVWXYZ[\]^_/
  6470        .HS 05.00.00      GO TO 0,0
  6480        .HS 10.08.08.08.08.08.08.08  Beep, 7 cursor ups
  6490        .HS 10.08.08.08.08.08.08.08  Beep, 7 cursor ups
  6500        .HS 05.00.17      GO TO 0,23
  6510        .HS 10.09.09.09.09.09.09.09  Beep, 7 cursor downs
  6520        .HS 10.09.09.09.09.09.09.09  Beep, 7 cursor downs
  6530 Z3     .EQ *-A3
  6540 *--------------------------------
  6550        .DA #Z4
  6560 A4     .HS 10.11.08      Beep, Shuffle Right 8
  6570        .HS 10.11.F8      Beep, Shuffle Left 8
  6580        .HS 10.11.01.11.01.11.01.11.01.11.01.11.01.11.01
  6590        .HS 10.11.FF.11.FF.11.FF.11.FF.11.FF.11.FF.11.FF
  6600        .HS 10.11.F8
  6610        .HS 10.11.08
  6620        .HS 10.11.28
  6630        .HS 10.11.D8
  6640 Z4     .EQ *-A4
  6650 *--------------------------------
  6660        .DA #Z5
  6670 A5     .HS 03            HOME
  6680        .HS 20.21.22.23.24.25.26.27.28.29.2A.2B.2C.2D.2E.2F
  6690        .HS 30.31.32.33.34.35.36.37.38.39.3A.3B.3C.3D.3E.3F.0D.09
  6700        .HS 40.41.42.43.44.45.46.47.48.49.4A.4B.4C.4D.4E.4F
  6710        .HS 50.51.52.53.54.55.56.57.58.59.5A.5B.5C.5D.5E.5F.0D.09
  6720        .HS 60.61.62.63.64.65.66.67.68.69.6A.6B.6C.6D.6E.6F
  6730        .HS 70.71.72.73.74.75.76.77.78.79.7A.7B.7C.7D.7E.7F.0D.09
  6740        .HS 0A            INVERSE
  6750        .HS 20.21.22.23.24.25.26.27.28.29.2A.2B.2C.2D.2E.2F
  6760        .HS 30.31.32.33.34.35.36.37.38.39.3A.3B.3C.3D.3E.3F.0D.09
  6770        .HS 40.41.42.43.44.45.46.47.48.49.4A.4B.4C.4D.4E.4F
  6780        .HS 50.51.52.53.54.55.56.57.58.59.5A.5B.5C.5D.5E.5F.0D.09
  6790        .HS 60.61.62.63.64.65.66.67.68.69.6A.6B.6C.6D.6E.6F
  6800        .HS 70.71.72.73.74.75.76.77.78.79.7A.7B.7C.7D.7E.7F.0D.09
  6810        .HS 0B            NORMAL
  6820 Z5     .EQ *-A5
  6830 *--------------------------------
  6840        .DA #Z6
  6850 A6     .HS 0D.09         CRLF
  6860        .HS 80.81.82.83.84.85.86.87.88.89.8A.8B.8C.8D.8E.8F
  6870        .HS 90.91.92.93.94.95.96.97.98.99.9A.9B.9C.9D.9E.9F.0D.09
  6880        .HS A0.A1.A2.A3.A4.A5.A6.A7.A8.A9.AA.AB.AC.AD.AE.AF
  6890        .HS B0.B1.B2.B3.B4.B5.B6.B7.B8.B9.BA.BB.BC.BD.BE.BF.0D.09
  6900        .HS C0.C1.C2.C3.C4.C5.C6.C7.C8.C9.CA.CB.CC.CD.CE.CF
  6910        .HS D0.D1.D2.D3.D4.D5.D6.D7.D8.D9.DA.DB.DC.DD.DE.DF.0D.09
  6920        .HS E0.E1.E2.E3.E4.E5.E6.E7.E8.E9.EA.EB.EC.ED.EE.EF
  6930        .HS F0.F1.F2.F3.F4.F5.F6.F7.F8.F9.FA.FB.FC.FD.FE.FF.0D.09
  6940 Z6     .EQ *-A6
  6950 *--------------------------------
  6960        .HS 00
  6970 *--------------------------------

Overhauling the S-C Program SelectorBob Sander-Cederlof

About a year and a half ago, in the July 1986 issue of Apple Assembly Line, I published my replacement for the ProDOS "QUIT" code. It turned out to be a very popular article and program, and various updates and corrections were printed in the August, September, October, and December issues that same year.

In review, the QUIT code is a 768-byte program inside ProDOS-8 which is executed when you leave a system program, such as FILER, AppleWorks, BASIC.SYSTEM (Applesoft), Copy II Plus, the S-C Macro Assembler, and so on. When you QUIT you are really executing the MLI Quit call ($65), which copies those 768 bytes down to RAM starting at $1000 and jumps there. Apple's built-in QUIT code is about as user-friendly as an angry porcupine: if you haven't MEMORIZED the volume names and file names of all your system programs, you almost always end up resetting and re-booting instead of filling in the blanks.

My 1986 Program Selector exactly fits in the 768-byte space Apple's version occupies in the ProDOS system file. It puts a menu of online volumes on the screen, allowing you to select one with the arrow keys and RETURN. Once you select a volume, you see a menu of the SYS and DIR files in the main directory of that volume. Using the arrows and RETURN you can either start up a SYS program or select a subdirectory. If you choose a subdirectory, you get a menu of SYS and DIR files inside it. Hitting ESCAPE always takes you back to the Volume Menu. As I wrote the Program Selector, it requires an Apple //e, //c, or //gs, and works in 80-columns.

The modifications already published include fixing one bug, making it work with a Videx-compatible 80-column card for Apple II Plus owners, following Apple's published spec's for substitute QUIT-code, and making it begin with the menu bar on something other than the first volume in the menu.

Quite a few readers of AAL are now using this Program Selector. Some have gone the extra mile by adapting the S-C Program Selector to their own preferences. Jim Hammond (of FastFind SUPER INDEX) liked it so well he turned it into a product which he sells (with my permission) as "STARTER/QUITTER". Larry Skutchan (a blind user who adapted the S-C Word Processor into a talking version) adapted it to work with the Echo Speech Synthesizer. Brooke Boering (creator of CeeMac and Fire Organ) put in a feature allowing you to limit the Volume Menu to a particular disk drive. Brooke's ideas are what led me to try to improve and upgrade my program.

The main computer here at the office is an Apple //e with a 10-meg Sider (ProDOS sees it as two drives in slot 7), two standard Apple floppies in slot 6, a RamFactor card in slot 4 (simulates one drive), a 1-meg RamWorks card in the AuxSlot (simulates a drive in slot 3, Drive 2), and a 3.5 inch drive in slot 2 (ProDOS thinks there are two drives there, even though I only have one). When I type BYE or in some other way select the ProDOS QUIT code, my S-C Program Selector takes over. The old version seemed to go away and die for several seconds while ProDOS did a complete ONLINE check to find out what volume if any was mounted in each and every drive. If my hard disk is not turned on, that takes several seconds for the firmware to timeout. If no floppies are in the 5.25 drives, they go through spinning, re-calibration, and the works before giving up.

It finally dawned on me that what I wanted was to direct the Program Selector to only try the slot and drive I booted from. Most likely if I booted from it, there is some kind of volume there. Of course I still wanted the capability of seeing every volume, but I did not want to waste all that time EVERY time!

Brooke told me six months or more ago that I could plug a drive ID into my ONLINE call (line 2760 of the original program) and it would limit the display to that one volume. It turned out to be a little harder than that, but I did get that to work. But I could not decide on just one slot and drive. I wanted that byte to be set up by ProDOS at boot-time to point to the booting drive.

I remembered that the ProDOS startup code began by plugging the boot drive ID into its own ONLINE call, which it uses to get the Volume Name. I looked up the code in the Supplement to "Beneath Apple ProDOS", and found it. In ProDOS 1.1.1 it is done by the first two instructions at $2000; in ProDOS 1.2, 1.3, and 1.4 it starts six bytes later at $2006. The first instruction is "LDA $43", which picks up the drive ID (slot number times 16) that was used to load the PRODOS or P8 file. The second one is "STA $21FE" for ProDOS 1.1.1, and "STA" somewhere else for later versions. I decided I could patch into that "STA" instruction a call to a piece of patch code which would not only do the patched-over STA for Apple, but also an additional one for me. More on this later, when we get into the code for my new Program Selector.

Anyway, the Program Selector now comes up only showing the Volume Name of the volume currently in the drive ProDOS was booted from. If that is the volume I want, I just hit RETURN and see the menu of SYS and DIR files on that volume. If I booted from RamFactor, this all happens at blinding speed.

If the boot-drive is not the drive I want, I can type a digit and select a different drive or all drives. Typing a digit 1 through 7 changes to drive 1 of that slot and displays the Volume Name in that drive. Typing the digit "8" changes to drive 2 in the same slot. Why 8? Why not? It made the code shorter, and it worked. Typing "0" changes back to the old way, making a menu of all online volumes. If you select a drive which has no volume mounted, you will get an empty menu. No problem, just select a different drive or all-drives, and continue.

I also made various other improvements here and there, such as making sure that text mode with a full-screen window is selected. I had to revise the "help" message at the bottom of the menu display to include information on the digits 0-8.

A major constraint in adding new features was that I wanted to retain the advantage of fitting inside the 768-byte hole in the ProDOS file. In developing the new version I decided not to worry about size too much until all the new features were working. I tested them by BRUNning the code at $1000, instead of going through the process of putting it into ProDOS every time. Then when it was all ready, I started looking for ways to shrink the code and make it fit in only 768 bytes.

It ran about 32 bytes over, so I needed a lot of shrinking. For some reason I don't remember, back in 1986 I decided to keep a lot of variables out of page zero. There is no requirement to do this, so I moved these variables and saved almost all the bytes I wanted. All instructions referencing these variables shrank from three to two bytes. The rest of the savings were found by careful study of the code. If you compare the new listing which follows with the one I published in 1986 you can find the tricks I pulled. The new version, even with all the new features, is now shorter than the original! There are actually four unused bytes!

I also wrote a program to automatically install the new Program Selector inside the ProDOS file. Well, almost automatically. You still have to BLOAD PRODOS or BLOAD P8, and UNLOCK the file if it is LOCKed. Then you "-" or BRUN my INSTALL.QUITTER program, and it automatically does the installation. If successful, you get a nice message to that effect; then you have to BSAVE the image and re-LOCK it. I thought it would be too dangerous to make all of the above entirely automatic. If my installer made a mistake.... So, I left the crucial part manual.

The auto-installer does differentiate between ProDOS 1.1.1 and the later versions. It makes the boot-drive patch at either $2002 or $2008, depending on where it finds the STA instruction. And if it cannot find that instruction, it tells you so and quits. The Program Selector image is copied either to $5700 (for ProDOS 1.1.1) or $5900 (for later versions).

The code for the auto-installer is executed when you BRUN INSTALL.QUITTER. Lines 1460-2270 are the installation code, and lines 2280 to the end are the Program Selector image. Lines 1490-1710 try to determine which version of ProDOS, if any, is in memory starting at $2000. If it finds a recognizable version, it sets up various pointers according to the version. If not, it prints out the long message from lines 2160-2220.

Lines 1720-1800 copy a JSR to my patch code over the top of the STA xxxx instruction which starts at either $2002 or $2008. It also modifies my patch code to include exactly the correct STA xxxx instruction which we are patching over. The patch code is at the very end of the Program Selector image, in lines 5970-6020. Later, when this patched ProDOS file is booted or otherwise executed, my patch code will install the boot drive ID into the ONLINE call block at line 5880.

Lines 1810-1950 copy the Program Selector image into the ProDOS image, at either $5700 or $5900 depending on version. Assuming we got this far, lines 1970-1990 will print out the "SUCCESSFUL" message.

  900        .LIST MOFF
  1000 *SAVE NEW.QUIT.CODE
  1010 *--------------------------------
  1020 *    Installation:
  1030 *      1.  BLOAD PRODOS,TSYS,A$2000
  1040 *      2.  BRUN INSTALL.QUITTER
  1050 *      3.  BSAVE PRODOS,TSYS,A$2000
  1051 *--------------------------------
  1052        .MA ASC           Macro to shorten listing
  1053        .AS -"]1"
  1054        .EM
  1060 *--------------------------------
  1070            .DUMMY
  1080            .OR 0000
  1090 BPNTR      .BS 2
  1100 SPNTR      .BS 2
  1110 DPNTR      .BS 2
  1120 DIR.INDEX  .BS 1
  1130 DIR.START  .BS 1
  1140 MAX.DIRPNT .BS 1
  1150 SEL.LINE   .BS 1
  1160 MAX.LINE   .BS 1
  1170 UNIT       .BS 1
  1180 LENGTH     .BS 1
  1190 CURTYP     .BS 1
  1200 CURBLK     .BS 1
  1210 *--------------------------------
  1220            .OR $800
  1230 OPNBUF     .BS 1024
  1240 DIRBUF     .BS 512
  1250            .ED
  1260 *--------------------------------
  1270 PATHNAME   .EQ $280
  1280 BUFFER     .EQ $2000
  1290 ENTLEN     .EQ BUFFER+$23   ENTRY LENGTH
  1300 ENTCNT     .EQ BUFFER+$24   # ENTRIES PER BLOCK
  1310 *--------------------------------
  1320 CV     .EQ $25
  1330 INVFLG .EQ $32
  1340 *--------------------------------
  1350 INIT       .EQ $FB2F
  1360 HOME       .EQ $FC58
  1370 CLREOL     .EQ $FC9C
  1380 COUT       .EQ $FDED
  1390 CROUT      .EQ $FD8E
  1400 SETINV     .EQ $FE80
  1410 SETNORM    .EQ $FE84
  1420 *--------------------------------
  1430 MLI    .EQ $BF00
  1440 BITMAP .EQ $BF58
  1450 *--------------------------------
  1460        .OR $1000
  1470        .TF INSTALL.QUITTER
  1480 *--------------------------------
  1490 INSTALL.QUITTER
  1500        LDX /$5700   WHERE IMAGE IS IN 1.1.1
  1510        LDA /$2000   WHERE LDA $43 IS IN 1.1.1
  1520        STA BPNTR+1
  1530        LDY #0
  1540        STY DPNTR
  1550        LDA $2000    SEE IF PRODOS IMAGE IS HERE
  1560        CMP #$4C     IF "JMP" THEN PROBABLY 1.4
  1570        BNE .1       ...PROBABLY 1.1.1
  1580        LDY #6       WHERE LDA $43 IS IN 1.4
  1590        LDX /$5900   WHERE IMAGE IS IN 1.4
  1600 .1     STY BPNTR    POINT AT LDA $43
  1610        STX DPNTR+1  POINT AT IMAGE OF QUITTER
  1620        INX
  1630        INX
  1640        STX QPATCH+2 ADDRESS IN IMAGE OF "ONLINE+1"
  1650        STX JSR.QPATCH+2 ADDRESS IN IMAGE OF QPATCH
  1660        LDY #2
  1670 .2     LDA PRODOS.ID.STRING,Y
  1680        CMP (BPNTR),Y
  1690        BNE .99      ...NEITHER, QUIT.
  1700        DEY
  1710        BPL .2
  1720 *---Trust we have ProDOS image---
  1730        LDY #2       POINT AT STA XXXX
  1740 .3     LDA (BPNTR),Y
  1750        STA QPATCH-2+3,Y
  1760        LDA JSR.QPATCH-2,Y
  1770        STA (BPNTR),Y
  1780        INY
  1790        CPY #5
  1800        BCC .3
  1810 *---Copy Quitter into ProDOS-----
  1820        LDA #QUIT.IMAGE
  1830        STA SPNTR
  1840        LDA /QUIT.IMAGE
  1850        STA SPNTR+1
  1860        LDX #3       COPY 3 PAGES
  1870        LDY #0
  1880 .4     LDA (SPNTR),Y
  1890        STA (DPNTR),Y
  1900        INY
  1910        BNE .4
  1920        INC SPNTR+1
  1930        INC DPNTR+1
  1940        DEX
  1950        BNE .4
  1960 *---Successful, say so and end---
  1970        LDY #IQ.GOOD
  1980        JSR IQ.PRINT
  1990        RTS          FINISHED
  2000 *---No ProDOS, or already patched---
  2010 .99    LDY #IQ.BAD
  2020        JSR IQ.PRINT
  2030        RTS
  2040 *--------------------------------
  2050 IQ.OUT JSR COUT
  2060        INY
  2070 IQ.PRINT
  2080        LDA IQ,Y
  2090        BNE IQ.OUT
  2100        RTS
  2110 *--------------------------------
  2120 IQ     .EQ *
  2130 IQ.GOOD .EQ *-IQ
  2140        >ASC "SUCCESSFULLY INSTALLED NEW QUIT CODE."
  2150        .HS 8D00
  2160 IQ.BAD .EQ *-IQ
  2170        >ASC "EITHER PRODOS NOT HERE,"
  2180        .HS 8D
  2190        >ASC "OR NOT VERSION 1.1.1 OR 1.4,"
  2200        .HS 8D
  2210        >ASC "OR ALREADY INSTALLED QUIT CODE."
  2220        .HS 8D00
  2230 *--------------------------------
  2240 PRODOS.ID.STRING
  2250        .HS A5.43.8D
  2260 JSR.QPATCH
  2270        JSR QPATCH.EP
  2280 *--------------------------------
  2290 QUIT.IMAGE
  2300        .PH $1000
  2310 *--------------------------------
  2320 QUITTER
  2330        CLD          REQUIRED BY "STANDARDS"
  2340        LDA $C082    MOTHERBOARD ROMS
  2350        JSR INIT     TEXT MODE, FULL SCREEN WINDOW
  2360        JSR SETNORM
  2370        LDX #$16
  2380        LDA #0       PREPARE VIRGIN BITMAP
  2390 .1     STA BITMAP,X
  2400        STX BITMAP+$17    LAST TIME STORES $01, LOCK OUT $BF00 PAGE
  2410        DEX
  2420        BNE .1
  2430        LDA #$CF
  2440        STA BITMAP
  2450 *---LIST VOLUME NAMES------------
  2460 .2     LDA #$99     CTRL-Y
  2470        JSR $C300    SET I/O HOOKS, 80-COL MODE, CLEAR SCREEN
  2480        LDY #Q.SDV
  2490        JSR MSG
  2500        JSR CLOSE.ALL.FILES
  2510        LDY #0
  2520        STY MAX.DIRPNT
  2530        STY DIR.START
  2540        STY PATHNAME
  2550        STY BUFFER+16  (IN CASE "ONLINE" PRESET TO SPECIFIC S/D)
  2560        JSR MLI
  2570        .DA #$C5,ONLINE
  2580 .3     STY SEL.LINE
  2590        JSR DISPLAY.VOLUMES
  2600        LDY #Q.VHELP
  2610        JSR MSG
  2620        JSR GET.KEY
  2630        BCC .3       ...ARROW KEYS
  2640        BNE .2       ...ESCAPE KEY
  2650 *---READ DIRECTORY---------------
  2660 .4     JSR READ.THE.FILE
  2670        BCS .7
  2680 *---PRINT PATHNAME---------------
  2690        JSR HOME
  2700        LDY #0
  2710 .5     LDA PATHNAME+1,Y
  2720        ORA #$80
  2730        JSR COUT
  2740        INY
  2750        CPY PATHNAME
  2760        BCC .5
  2770 *---COLLECT FILENAMES------------
  2780        LDX #0
  2790        LDA #$FF          FIRST JUST "SYS" FILES
  2800        JSR SCAN.DIRECTORY
  2810        LDA #$0F          THEN JUST "DIR" FILES
  2820        JSR SCAN.DIRECTORY
  2830        TXA          SEE IF ANY FILES FOUND
  2840        BEQ .2       ...NO, BACK TO THE TOP
  2850        LDA #0       MARK END OF LIST
  2860        STA DIRBUF+256,X
  2870        STX MAX.DIRPNT
  2880 *---LIST THE FILENAMES-----------
  2890        TAY          Y=0
  2900        STY DIR.START
  2910 .6     STY SEL.LINE
  2920        JSR DISPLAY.FILES
  2930        LDY #Q.VHELP
  2940        JSR MSG
  2950        JSR GET.KEY
  2960        BCC .6       ...ARROW KEYS
  2970        BNE .2       ...ESCAPE KEY
  2980        LDY #$10
  2990        LDA (SPNTR),Y     GET FILE TYPE
  3000        BPL .4            DIRECTORY ($0F)
  3010 *---SYS FILE, LOAD & EXECUTE-----
  3020        JSR MLI      SET PREFIX
  3030        .DA #$C6,PATH
  3040        JSR READ.THE.FILE
  3050        BCS .7       ...ERROR IN READING
  3060        JMP BUFFER
  3070 .7     JMP QUITTER
  3080 *--------------------------------
  3090 READ.THE.FILE
  3100        LDY #0       APPEND CURRENTLY SELECTED NAME
  3110        LDA (SPNTR),Y     GET LENGTH OF NAME
  3120        AND #$0F
  3130        STA LENGTH
  3140        LDX PATHNAME      CURRENT LENGTH
  3150        LDA #'/'
  3160 .1     INX
  3170        INY
  3180        STA PATHNAME,X
  3190        LDA (SPNTR),Y
  3200        DEC LENGTH
  3210        BPL .1
  3220        STX PATHNAME
  3230        JSR MLI           OPEN THE FILE
  3240        .DA #$C8,OPEN
  3250        BCS RF.ERR
  3260        LDA O.REF         FILE REFERENCE NUMBER
  3270        STA R.REF
  3280        JSR MLI           READ THE WHOLE FILE
  3290        .DA #$CA,READ
  3300        BCC CLOSE.ALL.FILES
  3310        CMP #$4C          IS IT JUST EOF?
  3320        SEC
  3330        BNE RF.ERR        ...NO
  3340 CLOSE.ALL.FILES
  3350        JSR MLI           CLOSE THE FILE
  3360        .DA #$CC,CLOSE
  3370 RF.ERR RTS
  3380 *--------------------------------
  3390 SCAN.DIRECTORY
  3400        STA CURTYP        TYPE WE ARE COLLECTING
  3410        LDA #0            START WITH FIRST BLOCK
  3420 .1     STA CURBLK
  3430        LDA #BUFFER+4     FIRST 4 BYTES OF BLOCK SKIPPED
  3440        STA DPNTR
  3450        CLC               COMPUTE PAGE OF PNTR
  3460        LDA /BUFFER+4
  3470        ADC CURBLK
  3480        STA DPNTR+1
  3490        LDA ENTCNT
  3500        STA LENGTH
  3510 *--------------------------------
  3520 .2     LDY #0
  3530        LDA (DPNTR),Y
  3540        AND #$F0
  3550        BEQ .4       ...DELETED FILE
  3560        CMP #$E0     ...HEADER?
  3570        BCS .4       ...YES
  3580        LDY #$10
  3590        LDA (DPNTR),Y     LOOK AT FILE TYPE
  3600        CMP CURTYP
  3610        BNE .4            ...NOT CURRENT TYPE
  3620 *---DIR or SYS file--------------
  3630 .3     LDA DPNTR
  3640        STA DIRBUF,X
  3650        LDA DPNTR+1
  3660        STA DIRBUF+256,X
  3670        INX
  3680 *---ADVANCE TO NEXT ENTRY--------
  3690 .4     CLC
  3700        LDA DPNTR
  3710        ADC ENTLEN
  3720        STA DPNTR
  3730        BCC .5
  3740        INC DPNTR+1
  3750 .5     DEC LENGTH        AT END OF BLOCK YET?
  3760        BNE .2            ...NO, CONTINUE IN BLOCK
  3770        CLC
  3780        LDA CURBLK
  3790        ADC #2
  3800        CMP ACTLEN+1
  3810        BCC .1            ...YES, READ NEXT BLOCK
  3820        RTS
  3830 *--------------------------------
  3840 DISPLAY.VOLUMES
  3850        JSR SETUP.DISPLAY.LOOP
  3860        LDA /BUFFER
  3870        STA BPNTR+1
  3880        LDA #BUFFER
  3890 .1     STA BPNTR
  3900        LDY #0
  3910        LDA (BPNTR),Y
  3920        BEQ .5            ...END OF LIST
  3930        AND #$0F
  3940        BEQ .3       ...NO VOLUME HERE
  3950 *--------------------------------
  3960        JSR CHECK.FOR.SEL.LINE
  3970 *--------------------------------
  3980        LDA (BPNTR),Y     GET UNIT NUMBER
  3990        LSR               ISOLATE SLOT NUMBER
  4000        LSR
  4010        LSR
  4020        LSR
  4030        AND #7
  4040        ORA #"0"
  4050        JSR COUT          PRINT SLOT NUMBER
  4060        LDA #"/"
  4070        JSR COUT
  4080        LDA (BPNTR),Y     GET UNIT NUMBER AGAIN
  4090        ASL               SET CARRY IF DRIVE 2
  4100        LDA #"1"          ASSUME DRIVE 1
  4110        ADC #0            CHANGE TO 2 IF TRUE
  4120        JSR COUT
  4130        LDA #" "     PRINT TWO SPACES
  4140        JSR COUT
  4150        JSR COUT
  4160        JSR PRINT.BPNTR.NAME
  4170 *--------------------------------
  4180 .3     CLC               POINT TO NEXT VOLUME NAME
  4190        LDA BPNTR
  4200        ADC #16
  4210        BCC .1       STILL IN SAME PAGE
  4220 .5     RTS
  4230 *--------------------------------
  4240 PRINT.BPNTR.NAME
  4250        LDY #0
  4260        LDA (BPNTR),Y      GET NAME LENGTH
  4270        AND #$0F
  4280        TAX
  4290 .1     INY          PRINT THE VOLUME OR FILE NAME
  4300        LDA (BPNTR),Y
  4310        ORA #$80
  4320        JSR COUT
  4330        DEX
  4340        BNE .1
  4350 *--------------------------------
  4360 .2     LDA #" "     PRINT TRAILING BLANKS
  4370        JSR COUT
  4380        INY
  4390        CPY #16
  4400        BCC .2
  4410        JSR SETNORM  NORMAL MODE NOW
  4420        INC MAX.LINE      COUNT THE LINE
  4430        JMP CROUT
  4440 *--------------------------------
  4450 GET.KEY
  4460        LDY SEL.LINE      CURRENT BRIGHT LINE
  4470 .1     LDA $C000    READ KEY FROM KEYBOARD
  4480        BPL .1
  4490        STA $C010    CLEAR THE STROBE
  4500        CMP #$B0     CHECK FOR "0"..."7"
  4510        BCC .12
  4520        CMP #$B8
  4530        BEQ .25
  4540        BCS .1
  4550        ASL
  4560        ASL
  4570        ASL
  4580        ASL          LEAVE SLOT*16 IN A, CARRY SET
  4590 .11    STA ONLINE+1 CHANGE ONLINE CALL
  4600        LDA #$9B     SIMULATE AN <ESCAPE>
  4610 .12    CMP #$8D
  4620        BEQ .2       <RETURN>
  4630        CMP #$88     <--
  4640        BEQ .3
  4650        CMP #$95     -->
  4660        BEQ .7
  4670        CMP #$8A     DOWN ARROW
  4680        BEQ .7
  4690        CMP #$8B     UP ARROW
  4700        BEQ .3
  4710        CMP #$9B     ESCAPE
  4720        BNE .1       GET ANOTHER CHARACTER
  4730        ASL          ...SET .NE. and CARRY
  4740 .2     RTS
  4750 .25    LDA ONLINE+1
  4760        ORA #$80          TRY DRIVE 2
  4770        BNE .11           ...ALWAYS
  4780 *---<UP OR LEFT ARROW>-----------
  4790 .3     CLC
  4800        DEY               IS CURRENT BRIGHT LINE TOP LINE?
  4810        BPL .8            ...NOT TOP LINE
  4820        LDY DIR.START     ARE WE DISPLAYING THE FIRST ONE?
  4830        BEQ .5            ...YES
  4840        DEC DIR.START     ...NO, MOVE TOWARD FIRST LINE
  4850 .4     LDY #0            MAKE FIRST LINE BRIGHT
  4860        RTS
  4870 .5     LDY MAX.LINE      MAKE LAST LINE BRIGHT
  4880        DEY
  4890        RTS
  4900 *---<DOWN OR RIGHT ARROW>--------
  4910 .7     INY               MOVE TOWARD LAST LINE
  4920        CPY MAX.LINE      BEYOND END OF SCREEN?
  4930        BCC .8            ...NO
  4940        LDA MAX.DIRPNT    ...YES, CHECK IF SHOWING LAST LINE
  4950        SBC #17
  4960        BCC .4            ...YES
  4970        CMP DIR.START
  4980        BCC .4            ...YES
  4990        INC DIR.START     ...NO, MOVE TOWARD LAST LINE
  5000        LDY SEL.LINE
  5010        CLC
  5020 .8     RTS
  5030 *--------------------------------
  5040 DISPLAY.FILES
  5050        JSR SETUP.DISPLAY.LOOP
  5060        LDA DIR.START
  5070        STA DIR.INDEX
  5080        JSR CLEAR.LINE.OR.PRINT.MORE.MSG
  5090 *--------------------------------
  5100 .1     LDX DIR.INDEX
  5110        LDY DIRBUF+256,X
  5120        BEQ .4       ...END OF LIST
  5130        STY BPNTR+1
  5140        LDA DIRBUF,X
  5150        STA BPNTR
  5160        JSR CHECK.FOR.SEL.LINE
  5170 *--------------------------------
  5180 .2     LDY #$10
  5190        LDA (BPNTR),Y
  5200        BMI .3       ...SYS FILE
  5210        LDY #Q.DIR
  5220        .HS 2C
  5230 .3     LDY #Q.SYS
  5240        JSR MSG
  5250        JSR PRINT.BPNTR.NAME
  5260 *--------------------------------
  5270        INC DIR.INDEX
  5280        DEC LENGTH
  5290        BNE .1
  5300 .4     LDA DIR.INDEX
  5310        CMP MAX.DIRPNT
  5320 *--------------------------------
  5330 CLEAR.LINE.OR.PRINT.MORE.MSG
  5340        BEQ .1       CLEAR LINE
  5350        LDY #Q.MORE
  5360        BNE MSG      ...ALWAYS
  5370 .1     JSR CLREOL
  5380        JMP CROUT
  5390 *--------------------------------
  5400 SETUP.DISPLAY.LOOP
  5410        LDA #16      MAX 16 LINES IN LIST
  5420        STA LENGTH
  5430        LDY #0
  5440        STY MAX.LINE
  5450        INY          SAME AS VTAB 3, HTAB 1
  5460        STY CV
  5470        JMP CROUT
  5480 *--------------------------------
  5490 CHECK.FOR.SEL.LINE
  5500        LDA MAX.LINE      SEE IF CURRENT LINE SHOULD
  5510        CMP SEL.LINE           BE INVERSE MODE
  5520        BNE .1            ...NO
  5530        LDA BPNTR         ...YES, SO SETUP POINTER
  5540        STA SPNTR
  5550        LDA BPNTR+1
  5560        STA SPNTR+1
  5570        LDA #$3F                  & SET INVERSE MODE
  5580        STA INVFLG
  5590 .1     RTS
  5600 *--------------------------------
  5610 MSG1   JSR COUT
  5620        INY
  5630 MSG    LDA QTS,Y
  5640        BNE MSG1
  5650        RTS
  5660 *--------------------------------
  5670 QTS    .EQ *
  5680 Q.SDV  .EQ *-QTS
  5690        >ASC "S/D  Volume Name"
  5700        .HS 00
  5710 Q.VHELP .EQ *-QTS
  5720        .HS 8D
  5730        >ASC "Select with ARROWS and <RETURN>"
  5740        .HS 8D
  5750        >ASC "See Volumes with <ESCAPE> or 0-8"
  5760        .HS 8D00
  5770 Q.SYS  .EQ *-QTS
  5780        >ASC "SYS -- "
  5790        .HS 00
  5800 Q.DIR  .EQ *-QTS
  5810        >ASC "DIR -- "
  5820        .HS 00
  5830 Q.MORE .EQ *-QTS
  5840        >ASC "<<<MORE>>>"
  5850        .HS 8D00
  5860 *--------------------------------
  5870 CLOSE  .DA #1,#0
  5880 ONLINE .DA #2,#0,BUFFER
  5890 OPEN   .DA #3,PATHNAME,OPNBUF
  5900 O.REF  .BS 1
  5910 READ   .DA #4
  5920 R.REF  .BS 1
  5930        .DA BUFFER,$9F00
  5940 ACTLEN .BS 2
  5950 PATH   .DA #1,PATHNAME
  5960 *--------------------------------
  5970        .BS $1300-*-7
  5980 QPATCH.EP
  5990        .EP
  6000 QPATCH STA ONLINE+1
  6010        STA *
  6020        RTS
  6030 *--------------------------------

Special Version of S-C Macro for Huge Symbol TablesBob Sander-Cederlof

Sometimes there just is not enough memory to hold the entire symbol table of a large assembly, especially in the ProDOS version of the S-C Macro Assembler. In that version, the symbol table normally begins at $1000 and grows upward, while the source program snugs up against $7400, hanging downward from there. Even with the use of the .INB directive to bring in segments of the source code one disk block at a time, extra-large programs can run out of memory during assembly. It can be especially frustrating in an Apple //e or later machine, when you know there is a lot of free memory just across the Soft-Switch River, over there in Aux-land.

Until now I have resisted using this memory, trying to remain fully compatible with older 64K machines and trying to keep the speed advantages of all-main-memory. But finally, the need became personal enough. I created a special version which puts the entire symbol table in Aux RAM. It will run on a 128K or larger //e, //c, or IIgs; I haven't tried it, but it should also run in an Apple II Plus with an Applied Engineering Transwarp card plugged and turned on. The symbol table still begins at $1000, but in AuxRAM; and it can rise as high as $BFFF without challenge. That is $B000 total bytes, or about twice as many as were available between $1000 and the bottom of the source program in MainRAM. The space below $1000, down as far as $800, is occupied by macro private labels, if you use any. Obviously, there is also now more room in Main RAM for your source code and/or object code. A Minus: if you have a /RAM disk installed in AuxRAM, the symbol table walks all over it. However, if you are using a RamWorks card or the like, with their PRODRIVE software, bank 0 of AuxRAM is left available for just these type uses.

If you need something like this, it's finally here. I'm calling it Version 2.1, and as a registered owner of the ProDOS version of the S-C Macro Assembler you can have a copy for only $10.


"IIgs Toolbox Reference" Now AvailableBob Sander-Cederlof

At long last, Addison-Wesley assures me they have printed the "IIgs Toolbox Reference" manuals, Volumes 1 and 2. These are probably indispensable tools for any serious IIgs programmers. Until now, you could only get them in beta versions through APDA. They are over 750 pages each, and cost $26.95 each ($24 plus shipping charge if you order from us). Each of the standard tools is described in great detail, with examples in both assembly language and C.

If they kept the same arrangement as my pre-beta copy (dated Nov 1986), there are a total of 23 chapters in the set. In my edition, Volume 1 covers toolsets in general, Desktop Bus, Control Mngr, Desk Mngr, Dialog Mngr, Event Mngr, Font Mngr, Integer Math, Line Edit, Memory Mngr, Menu Mngr, Misc.Tools, and Print Mngr; Volume 2 covers Quickdraw, SANE, Scheduler, Scrap Mngr, Sound Mngr, Std. File Operations, Text.tools, Tool Locater, and Window Manager. Volume 2 also includes an appendix on writing your own tool set.

There remains one more book in the IIgs series, the "IIgs Programmer's Introduction". A-W is now predicting April '88 for the publishing date, and a price of $32.95. I guess I am old-fashioned, but to my mind this book should have been published FIRST, and included FREE with every IIgs purchase. Oh, well.... We will accept orders on this one now, and hold them until the book comes, at a price of $30 plus shipping (see note on page 3 for details on shipping charges).


Display VolumesBob Sander-Cederlof

BONUS PROGRAM (not printed in the original newsletter)

  1000        .LIST XOFF
  1010 *SAVE ONLINE.CMD
  1020 *--------------------------------
  1030 BUFFER     .EQ $2000
  1040 *--------------------------------
  1050 PRBYTE     .EQ $FDDA
  1060 COUT       .EQ $FDED
  1070 CROUT      .EQ $FD8E
  1080 *--------------------------------
  1090 MLI    .EQ $BF00
  1100 *--------------------------------
  1110 T
  1120 DISPLAY.VOLUMES
  1130        JSR MLI
  1140        .DA #$C5,ONLINE
  1150        LDA #0
  1160 .1     PHA
  1170        TAY
  1180        LDA BUFFER,Y
  1190        BEQ .5            ...END OF LIST
  1200        PHA
  1210        LDA #"S"
  1220        JSR COUT
  1230        PLA
  1240        PHA
  1250        LSR               ISOLATE SLOT NUMBER
  1260        LSR
  1270        LSR
  1280        LSR
  1290        AND #7
  1300        ORA #"0"
  1310        JSR COUT          PRINT SLOT NUMBER
  1320        LDA #","
  1330        JSR COUT
  1340        LDA #"D"
  1350        JSR COUT
  1360        PLA
  1370        PHA
  1380        ASL               SET CARRY IF DRIVE 2
  1390        LDA #"1"          ASSUME DRIVE 1
  1400        ADC #0            CHANGE TO 2 IF TRUE
  1410        JSR COUT
  1420        LDA #" "     PRINT SPACE
  1430        JSR COUT
  1440        PLA          get dsssllll again
  1450        AND #$0F     isolate length
  1460        BEQ .3       no name, show error code
  1470        TAX
  1480        LDA #"/"
  1490 .2     JSR COUT
  1500        INY          PRINT THE VOLUME OR FILE NAME
  1510        LDA BUFFER,Y
  1520        ORA #$80
  1530        DEX
  1540        BPL .2
  1550        LDA #"/"
  1560        BNE .4       ...ALWAYS
  1570 .3     LDA #"("
  1580        JSR COUT
  1590        LDA BUFFER+1,Y    GET ERROR CODE
  1600        JSR PRBYTE
  1610        LDA #")"
  1620 .4     JSR COUT
  1630        JSR CROUT
  1640 *--------------------------------
  1650 .5     CLC               POINT TO NEXT VOLUME NAME
  1660        PLA
  1670        ADC #16
  1680        BCC .1       STILL IN SAME PAGE
  1690        RTS
  1700 *--------------------------------
  1710 ONLINE .DA #2,#0,BUFFER
  1720 *--------------------------------
  1730        .LIF

Apple Assembly Line (ISSN 0889-4302) is published monthly by S-C SOFTWARE CORPORATION, P. O. Box 280300, Dallas, TX 75228 Phone (214) 324-2050. Subscription rate is $24 per year in the USA, Canada, and Mexico, or $36 in other countries. Back issues are $1.80 each for Volumes 1-7, $2.40 each for Volume 8 (plus postage). A subscription tothe newsletter with a Monthly Disk containing all program source code and article text is $64 per year in the USA, Canada and Mexico, and $90 to other countries.

All material herein is copyrighted by S-C SOFTWARE, all rights reserved. Unless otherwise indicated, all material herein is authored by Bob Sander-Cederlof. (Apple is a registered trademark of Apple Computer, Inc.)