Volume 8 -- Issue 4 | January 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...

- Peeking Inside AppleWorks 1.3: Interpretive String Display Subroutine
- Overhauling the S-C Program Selector
- Special Version of S-C Macro for Huge Symbol Tables
- "IIgs Toolbox Reference" Now Available
- Display Volumes

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 Selector | Bob 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 Tables | Bob 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 Available | Bob 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 Volumes | Bob 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.)