Apple Assembly Line
Volume 8 -- Issue 3 December 1987

In This Issue...

About Back Issues...

Don't be bashful, just tell me what you need! I have copies of all back issues in stock, at $1.80 each, $18 per volume, or $120 for all seven of the completed volumes. Remember, our volume-year runs from October through September, and I started in 1980.

I also have Quarterly Disks for the entire period, $15 each or four for $60. Each Quarterly Disk covers a calendar quarter, so just specify which months you need. Starting with January 1986 I went to Monthly Disks, so you can get any individual month for only $5 from then till now. Each Quarterly or Monthly Disk contains all of the source code printed in the AAL issues it covers, in the format for the S-C Macro Assembler. Early ones are all DOS, later are split with both DOS and ProDOS directories on the same disk. And since sometime last year we have also been including all of the text files for the articles themselves, as a service to readers who like to have the Echo Speech Synthesizer (or other brands) read it all to them.

Toward a New Standard Assembly Language...

Randy Hyde, who you may remember as author of the Lisa 6502 Assembler, is attempting to organize interested parties to produce a new definitive 65816 assembly language standard. He claims the existing standard, based on Orca, is confusing, overly complex, and idiosyncratic; a new standard could allow assemblers with more power than the Microsoft 8086 assembler to be written for the 65816. Right now Randy is collecting ideas and contacting key individuals (such as the authors of the various existing 65816 assemblers and 65816 books, and the chip's designer), and planning for a conference at WDC in Arizona some time this summer. If you are interested in participating in any way, write to him: Randall Hyde, 65C816 Standards, 2271 Indian Horse Drive, Norco, CA 91760.


Peeking Inside AppleWorks 1.3
+ Subroutine Call Parameter Passage
+ String Handling Subroutines
Bob Sander-Cederlof

There are a lot of useful subroutines inside AppleWorks. I have been looking at a raw disassembly, and have learned a few new tricks. Even though AppleWorks is ProDOS-based, the subroutines are general enough that you can use them in your own code in any operating system.

All my observations are based on version 1.3, as that is the only one I have. Meanwhile, Apple has moved on to version 2.0 and turned it all over to Claris. That is all right, because I am not proposing that we use the subroutines by loading AppleWorks and calling them; I am proposing that we copy the code or some modification of it into our own programs.

When you boot AppleWorks 1.3 the first thing it does is to copy the APLWORKS.SYSTEM image down from $2000 to $1000. I simply loaded it there from inside the S-C assembler with "BLOAD APLWORKS.SYSTEM,TSYS,A$1000". Then I printed out a huge listing with the monitor's "L" command, and went to work with a pencil. I don't even know what section of AppleWorks I am looking at yet, but it is chock full of interesting code I can use.

The first thing I noticed was that a lot of code did not disassemble correctly: The "L" command went weird after a lot of JSR's. It seems the author liked to call subroutines with parameters in data form following the JSR, like ProDOS MLI calls. In most cases (all I could find) there are either two, four, or six bytes of parameters after these JSR's. The subroutines all call, in turn, on a magic little subroutine which copies the parameter bytes to a standard area in page zero, starting at $9A. This GET.x.PARMS subroutine also updates the return address on the stack so that, when the parameterized call is completed, execution will resume after the parameter bytes.

The GET.x.PARMS subroutine is shown as I found it inside AppleWorks 1.3, in lines 1300-1580. I used the .PH $18AD line to make it look exactly the same as the AppleWorks image. The code looked a little fluffy to me, so I wrote my own version (which is shorter and swifter); you can see it in lines 1830-2070.

The AppleWorks version is evidently one of the busiest pieces of code in the system. I say that, because the author chose to poll the keyboard inside GET.x.PARMS. Line 1550 calls the POLL.KEYBOARD subroutine, which I show with comments in lines 1600-1810. I left this out of my rendition of GET.x.PARMS, because I am building a little package of routines for my own use. I included the listing here because I thought you might like to see how it is done. Notice the buffer holds only ten characters, as written.

Notice that there are three entry points to the GET.x.PARMS subroutine. The first copies four bytes following the JSR to $9A...9D; the second copies only two bytes; and the third copies any number, which you specify in the A-register. I used a memory search to uncover all the calls on this third entry, and I only found calls which wanted to copy six bytes. There may be others, hidden in other sections of the AppleWorks code.

My more efficient rendition saves one byte by using the BIT opcode ($2C) to skip over the two-byte LDA #2 instruction (see lines 1370 and 1870). I also save by pushing the byte count on the stack instead of saving it in RAM: the storage location is saved, and the PLA is two bytes shorter than a STA. However, I have to pull the byte back off the stack at line 2050, so the net saving is only two bytes. Line 2060, the LDY #0, can be deleted and save another two bytes (Y is already 0, in order for the loop in lines 2010-2040 to terminate). I don't need it because I have not called POLL.KEYBOARD. By the way, we do want to be sure that Y=0 here, because a lot of the subroutines depend on it. If we don't make it one of the functions of GET.x.PARMS, we will have to add a line to most of the subroutines which call GET.x.PARMS.

Inside AppleWorks a lot of string processing goes on. All of the strings I have observed are stored in memory as a length byte followed by the string data bytes. The maximum string will have 255 data bytes. A byte count equal to $00 represents a null string. Lines 2980-3130 are my own code, simply to illustrate how you might code a subroutine to display a string stored in this fashion. Lines 3640-3690 show some strings built by the assembler. In my DEMO code, starting at line 3470, I called on DISPLAY.STRING to print these strings.

Lines 2080-2970 show my renditions of four subroutines I found inside AppleWorks, which I have named MOVE.STRING, COMPARE.STRING, APPEND.STRINGS, and FILTER.LC.TO.UC. These are not identical to the AppleWorks code, as I found some easy improvements here and there. I showed in comment lines where you can find these subroutines inside the AppleWorks 1.3 image. My DEMO program, lines 3470 to the end, shows some of these in action.

Not all of the subroutines which I found have to do with strings. Lines 3140-3410 show MOVE.BLOCK, which does the equivalent of a monitor "M" command. The three parameters are the destination starting address, the source starting address, and the number of bytes to be moved. (I say "moved", perhaps I should really say "copied".) A similar subroutine which starts immediately after this one, at $1BAC, moves a block of memory "up", by starting at the last byte of the source block and moving backwards. This gives the ability to move a block of memory up to an overlapping area, without clobbering the data.

At the beginning of the code at $1000 there is a JMP table (a long series of JMP xxxx instructions, one right after the other) which is evidently used when other segments want to use some of these general subroutines. Each of the other segments also begins with a JMP table. This is a good scheme for joining together pieces of a large system, and is easy to do. I find it a lot handier than the use of a Link Editor approach, as is used under ProDOS-16. On the other hand, subroutines such as these I have shown in this article are the very type you might want to keep in assembled form as relocatable, linkable, object files, on a library ready to be used by all your future code. Even better, they are the type of subroutines I wish were in the //gs tool boxes, in ROM. And there, they would best be called via a JMP table, for efficiency. These routines are too short to afford the tremendous overhead of "real" toolbox calls.

The handiest way to use subroutines like these would involve writing macros for the calls. For example, here are some macro definitions for MOVE.STRING, APPEND.STRINGS, and MOVE.BLOCK:

       .MA MOVE.STRING
       JSR MOVE.STRING
       .DA ]1,]2
       .EM
       
       .MA APPEND.STRINGS
       JSR APPEND.STRINGS
       .DA ]1,]2
       .EM

       .MA MOVE.BLOCK
       JSR MOVE.BLOCK
       .DA ]1,]2,]3
       .EM

These are simple enough, but they can make coding a program easier. If you are error prone, or simply enjoy being cautious, you might add code to the definitions to check for the correct number of macro parameters. For example, MOVE.BLOCK requires three parameters:

       .MA MOVE.BLOCK
       .DO ]#=3
       JSR MOVE.BLOCK
       .DA ]1,]2,]3
       .ELSE
==> ERROR:  WRONG NUMBER OF PARAMETERS.  You have ]#, I need 3.
       .FIN
       .EM

The line starting "==>" will not be assembled as long as you have 3 parameters. If you have some other number, that line will cause an assembly error, since it starts with an illegal character. This will make it display during assembly, so you can catch and correct your call.

For example, if I try to assemble the following line:

    1140       >MOVE.BLOCK BUFFER,SIZE

you will get the message: >1140 ==> ERROR: WRONG NUMBER OF PARAMETERS. You have 2, I need 3.

Did you know you could do that? I wasn't sure, but I tried it and it works.

  1000 *SAVE S.AW.SUBS
  1010 *--------------------------------
  1020 PNTR   .EQ $98,99
  1030 P0     .EQ $9A
  1040 P1     .EQ $9B
  1050 P2     .EQ $9C
  1060 P3     .EQ $9D
  1070 P4     .EQ $9E
  1080 P5     .EQ $9F
  1090 COUNT  .EQ $A0
  1100 *--------------------------------
  1110 MON.CROUT  .EQ $FD8E
  1120 MON.COUT   .EQ $FDED
  1130 *--------------------------------
  1140 *   GET PARMS
  1150 *      (A) = # bytes of parameter info
  1160 *      Copy the bytes to $9A, 9B, ... etc.
  1170 *      Update Return Address
  1180 *      Poll Keyboard for Type-Ahead
  1190 *      Set Y=0, clobbers A and X
  1200 *
  1210 *   For Example:
  1220 *              JSR subroutine
  1230 *              .DA parm1,parm2
  1240 *           <return here>
  1250 *
  1260 *   subroutine JSR GET.4.PARMS
  1270 *              ...
  1280 *              RTS
  1290 *
  1300 *--------------------------------
  1310 *   The following code is as it exists in AppleWorks 1.3
  1320        .PH $18AD
  1330 *--------------------------------
  1340 AW.GET.PARM.TEMP .BS 1
  1350 *--------------------------------
  1360 AW.GET.4.PARMS LDA #4
  1370             BNE AW.GET.A.PARMS
  1380 AW.GET.2.PARMS LDA #2
  1390 AW.GET.A.PARMS TAY
  1400        STA AW.GET.PARM.TEMP
  1410        TSX
  1420        LDA $0103,X
  1430        STA PNTR
  1440        CLC
  1450        ADC AW.GET.PARM.TEMP
  1460        STA $0103,X
  1470        LDA $0104,X
  1480        STA PNTR+1
  1490        ADC #0
  1500        STA $0104,X
  1510 .1     LDA (PNTR),Y
  1520        STA PNTR+1,Y
  1530        DEY
  1540        BNE .1
  1550        JSR POLL.KEYBOARD
  1560        LDY #0
  1570        RTS
  1580        .EP
  1590 *--------------------------------
  1600 *   POLL KEYBOARD
  1610 *--------------------------------
  1620        .PH $1FA7
  1630 POLL.KEYBOARD
  1640        LDA $C000    ANY KEY PRESSED?
  1650        BPL .3       ...NO, RETURN NOW
  1660        STA $C010    ...YES, CLEAR STROBE
  1670        LDX $C061    OPEN APPLE PRESSED?
  1680        BMI .1       ...YES
  1690        LDX $C062    SOLID APPLE PRESSED?
  1700        BMI .1       ...YES
  1710        AND #$7F     ...NO APPLES, SO CLEAR BIT 7
  1720 .1     LDX $1184 <<KEY.BUFFER.INDEX>>
  1730        STA $117A,X  <<KEY.BUFFER>>
  1740        INX
  1750        CPX #10      AT END OF BUFFER YET?
  1760        BCC .2       ...NO END YET
  1770        LDX #0       ...END, SO WRAP AROUND
  1780 .2     CPX $1185 <<KEY.BUFFER.OUTDEX>>
  1790        BEQ .3       BUFFER IS FULL
  1800        STX $1184 <<KEY.BUFFER.INDEX>>
  1810 .3     RTS
  1820        .EP
  1830 *--------------------------------
  1840 *   My More Efficient Version
  1850 *--------------------------------
  1860 GET.4.PARMS LDA #4
  1870             .HS 2C    SKIP NEXT 2 BYTES
  1880 GET.2.PARMS LDA #2
  1890 GET.A.PARMS TAY
  1900        PHA
  1910        TSX
  1920        LDA $0104,X  GET RETURN ADDR-LO
  1930        STA PNTR      KEEP FOR VECTOR
  1940        CLC
  1950        ADC $0101,X  ADD # BYTES
  1960        STA $0104,X  UPDATE RETURN ADDR-LO
  1970        LDA $0105,X  GET RETURN ADDR-HI
  1980        STA PNTR+1      SAVE FOR VECTOR
  1990        ADC #0
  2000        STA $0105,X  UPDATE RETURN ADDR-HI
  2010 .1     LDA (PNTR),Y  USING VECTOR, COPY PARMS
  2020        STA PNTR+1,Y    ...TO 9A,9B,etc.
  2030        DEY
  2040        BNE .1
  2050        PLA          GET LENGTH OFF STACK
  2060        LDY #0
  2070        RTS
  2080 *--------------------------------
  2090 *   MOVE STRING
  2100 *      JSR MOVE.STRING
  2110 *      .DA destination,source
  2120 *   (at $1EF8 in AppleWorks 1.3)
  2130 *--------------------------------
  2140 MOVE.STRING
  2150        JSR GET.4.PARMS
  2160        LDA (P2),Y   COPY THE LENGTH BYTE
  2170        STA (P0),Y
  2180        BEQ .2       STRING IS EMPTY
  2190        TAY          LENGTH TO Y
  2200 .1     LDA (P2),Y
  2210        STA (P0),Y
  2220        DEY
  2230        BNE .1
  2240 .2     RTS
  2250 *--------------------------------
  2260 *   COMPARE TWO STRINGS
  2270 *      JSR COMPARE.STRINGS
  2280 *      .DA str1,str2
  2290 *    return Carry Clear if str1 < str2; else Carry Set
  2300 *   (at $1ED9 in AppleWorks 1.3)
  2310 *--------------------------------
  2320 COMPARE.STRINGS
  2330        JSR GET.4.PARMS
  2340        LDA (P0),Y   GET LENGTH OF SHORTER STRING
  2350        CMP (P2),Y
  2360        BCC .1
  2370        LDA (P2),Y
  2380 .1     TAX          LENGTH OF SHORTEST TO X-REG
  2390        BEQ .3       SHORTEST IS NULL
  2400 .2     INY          COMPARE BODY OF STRINGS
  2410        LDA (P0),Y
  2420        CMP (P2),Y
  2430        BNE .4       NOT SAME, SO CARRY GIVES RELATION
  2440        DEX
  2450        BNE .2       MORE TO COMPARE
  2460 .3     LDY #0       STRINGS MATCH TO END OF SHORTEST
  2470        LDA (P0),Y   COMPARE ON BASIS OF LENGTH
  2480        LDA (P2),Y
  2490 .4     RTS
  2500 *--------------------------------
  2510 *   APPEND TWO STRINGS
  2520 *      JSR APPEND.STRINGS
  2530 *      .DA stringA,stringB
  2540 *   (at $1341 in AppleWorks 1.3)
  2550 *--------------------------------
  2560 APPEND.STRINGS
  2570        JSR GET.4.PARMS
  2580        LDA (P0),Y   GET LENGTH OF STRING A
  2590        PHA          SAVE IT
  2600        CLC
  2610        ADC (P2),Y   ADD LENGTH OF STRING B
  2620        STA (P0),Y   MAKES LENGTH OF COMBINED STRING
  2630        PLA          GET LENGTH OF STRING A AGAIN
  2640        CLC
  2650        ADC P0       BUMP POINTER TO END OF STRING A
  2660        STA P0
  2670        BCC .1
  2680        INC P1
  2690 .1     LDA (P2),Y   LENGTH OF STRING B
  2700        BEQ .3       ...NULL, SO FINISHED
  2710        TAY
  2720 .2     LDA (P2),Y
  2730        STA (P0),Y
  2740        DEY
  2750        BNE .2
  2760 .3     RTS
  2770 *--------------------------------
  2780 *   FILTER LOWER CASE to UPPER CASE in a STRING
  2790 *      JSR FILTER.LC.TO.UC
  2800 *      .DA string
  2810 *   (at $1BF in AppleWorks 1.3)
  2820 *--------------------------------
  2830 FILTER.LC.TO.UC
  2840        JSR GET.2.PARMS
  2850        LDA (P0),Y   GET LENGTH OF STRING
  2860        BEQ .3       NULL STRING
  2870        TAY          LENGTH TO Y-REG
  2880 .1     LDA (P0),Y
  2890        CMP #'a'
  2900        BCC .2
  2910        CMP #'z'+1
  2920        BCS .2
  2930        AND #$DF     TURN OFF LOWER/CASE BIT
  2940        STA (P0),Y
  2950 .2     DEY
  2960        BNE .1       ...MORE BYTES
  2970 .3     RTS
  2980 *--------------------------------
  2990 *   DISPLAY STRING
  3000 *      JSR DISPLAY.STRING
  3010 *      .DA string.address
  3020 *--------------------------------
  3030 DISPLAY.STRING
  3040        JSR GET.2.PARMS
  3050        LDA (P0),Y   GET LENGTH
  3060        BEQ .2       ...NULL STRING
  3070        STA COUNT
  3080 .1     INY
  3090        LDA (P0),Y
  3100        JSR MON.COUT
  3110        CPY COUNT
  3120        BCC .1
  3130 .2     RTS
  3140 *--------------------------------
  3150 *   MOVE MEMORY BLOCK
  3160 *      JSR MOVE.BLOCK
  3170 *      .DA destination,source,num.bytes
  3180 *   (at $1B84 in AppleWorks 1.3)
  3190 *--------------------------------
  3200 MOVE.BLOCK
  3210        LDA #6       GET 6 PARM BYTES
  3220        JSR GET.A.PARMS
  3230        LDA P5       GET NUMBYTES-HI (# FULL PAGES)
  3240        BEQ .2       ...NO FULL PAGES TO MOVE
  3250 *---Move Full Pages--------------
  3260 .1     LDA (P2),Y
  3270        STA (P0),Y
  3280        INY
  3290        BNE .1       ...UNTIL FULL PAGE MOVED
  3300        INC P1       SOURCE-HI
  3310        INC P3       DESTINATION-HI
  3320        DEC P5       # FULL PAGES LEFT
  3330        BNE .1       ...STILL MORE
  3340 *---Move Partial Page------------
  3350 .2     CPY P4       FINISHED PARTIAL PAGE?
  3360        BEQ .3       ...YES
  3370        LDA (P2),Y
  3380        STA (P0),Y
  3390        INY
  3400        BNE .2       ...ALWAYS
  3410 .3     RTS
  3420 *--------------------------------
  3430 *
  3440 *   DEMONSTRATION OF SOME STRING SUBROUTINES
  3450 *
  3460 *--------------------------------
  3470 DEMO   JSR DISPLAY.STRING
  3480        .DA STR.A
  3490        JSR MON.CROUT
  3500        JSR DISPLAY.STRING
  3510        .DA STR.B
  3520        JSR MON.CROUT
  3530        JSR MOVE.STRING
  3540        .DA STR.C,STR.B
  3550        JSR DISPLAY.STRING
  3560        .DA STR.C
  3570        JSR MON.CROUT
  3580        JSR APPEND.STRINGS
  3590        .DA STR.C,STR.A
  3600        JSR DISPLAY.STRING
  3610        .DA STR.C
  3620        RTS
  3630 *--------------------------------
  3640 STR.A  .DA #SZ.A
  3650        .AS -/THIS IS STRING A./
  3660 SZ.A   .EQ *-STR.A-1
  3670 STR.B  .DA #SZ.B
  3680        .AS -/THIS IS STRING B./
  3690 SZ.B   .EQ *-STR.B-1
  3700 STR.C  .BS 80
  3710 *--------------------------------

Screen Dump PLUS! Louis Pitz

Enclosed is SCRNDUMP.PLUS, an enhancement to Steve Knouse's Generic Screen Dump in Apple Assembly Line, September 1983. The main enhancements are 40/80/Lores capability and, via conditional assembly, either a DOS 3.3 or a ProDOS version. The Lores capability is modified from a routine by R.M. Mottola in Nibble/#3/p.18.

The idea is to squeeze in as many features as possible and still have a utility that will fit in good old page 3 space ($300.3CF). However, there are times you have something else in page 3 and need your screendump utility elsewhere. This is easy to do just by reassembling the screendump at another address. However, I'm lazy, and when I need a screendump utility installed I don't want to have to hunt for my assembler disk. So this version is self-relocating in that you can BLOAD and CALL to install it at any address where you have 284 bytes ($11C) free.

This is too big to fit in page 3, so I borrowed Bill Morgan's idea from AAL/Nov.82, to use the upper part of page 2. The input trap and dump portion of SCRNDUMP.PLUS fits in page 3, while the installation takes the top of page 2. Since installation is a one-time affair, it's disposable, although it will remain there if you don't make too heavy use of the input buffer.

In addition to Steve Knouse and R.M. Mottola, I also learned and borrowed from Bill Parker's EPSON TEXT SCREEN DUMP, 1982 and Gary Little's ProDOS DUMP# utility in A+/Jul.85/p.69. I give credit to Roger Wagner's "Assembly Lines: The Book" for relocation ideas. There are a lot of neat techniques in this short utility, most of course 'borrowed' from other sources.

Now for some comments on why I chose BLOAD & CALL logic. You can install the dump via BRUN in immediate mode in DOS 3.3. However, readers of AAL know (AAL/JUN.86, AUG.86, SEP.86) that there are problems in DOS 3.3 with BRUN from within an Applesoft program. So I chose BLOAD & CALL to avoid the extra code you'd need to solve the problem. The BLOAD and CALL 41876 approach of AAL/AUG.86 is a good alternative to BRUN.

However, 3.3 DOSologists need not be ashamed, for ProDOS has its quirks also! See Call-Apple/Apr.84/p.39/Cecil Fretwell for details, but in ProDOS you must choose between a BLOAD & CALL or BRUN approach even for immediate mode! Replace Lines 2120 & 2130 by

     2120  STX KSWL
     2130  STY KSWH
     2134  RTS
     2138  .BS 2  (filler)

for BRUN logic. Again, I chose BLOAD & CALL logic for both DOS 3.3 and ProDOS to be consistent. Also, you can CALL 692 (or 2B4G from monitor) to rehook if you haven't made too heavy use of the input buffer.

Now to describe the utility in more detail. Lines 1540-1630 find where it has been BLOADed. I think it's a good idea to inhibit interrupts while getting information from the stack. Then lines 1640-1720 modify (SETRAP+1) and (SETRAP+3) to point at TRAP. Lines 1730-1900 put the current INPUT address at TRAP+1.TRAP+2 and TRAP+8.TRAP+10: the first so we can 'daisy-chain' our input TRAP at the end of the current INPUT routine, and the second so we can terminate via CTRL-T to remove our INPUT trap. Lines 1910-1980 set default parameters at locations 6-9 and $ED. If you BLOAD without CALL-ing, you must set these parameters yourself. As far as I can tell, there are 11 page 0 locations not used by Applesoft, Integer Basic, DOS 3.3, ProDOS (including interrupt handlers), or the System Monitor. They are 6-9, $1E, $EB-EF, and $F9.

Anyway, 6 (PRFLAG) is used as an enable/disable (1/0) flag- for when you want to disable dumping without removing the input trap. 7 (WINTOP) and 8 (WINBOT) are used to set a 'window' to dump if you don't want to dump the whole screen. Here the top line is 0 and bottom is 23 ($17). Note that I couldn't fit left and right margin windows in. If you want a routine that does both 40 and 80 column dumps you'll have to be careful in adding left and right margins. $ED (FLAG) is used to flag if a dump has been done via CTRL-P from the TRAP (value $FF) or just a CALL 802 (JSR $322) (value 0).

SETRAP, Lines 2050-2150, hooks TRAP into DOS or ProDOS.

TRAP, our input 'filter', does its thing by first daisy-chaining to the current input routine and letting it do all the dirty work. Only then does TRAP check for either CTRL-T (Terminate TRAP and reset input hooks) or CTRL-P (Print the screen). If neither, exit to caller of input routine. If a CTRL-P, Set FLAG ($ED) to $FF to show we came thru TRAP. This avoids a JSR DUMP, which would not be relocatable. You could have a JSR DUMP with address filled in during the first portion of the utility just like SETRAP+1, +3 are filled in from Lines 1640-1720. That way you wouldn't need FLAG at all, but the approach takes 3 more bytes. Roger Wagner's book has other ways to do a relocatable JSR, but remember that we want a DUMP that can be called either directly or from TRAP.

Note that lines 2280 and 2290 inside TRAP avoid another ProDOS 'gotcha'. A JMP $3D0 (DOS.WARM) in ProDOS (Q from IIgs Monitor) returns to immediate mode but erases Applesoft variables, unlike the friendly DOS 3.3! Thanks to Steve and Marsha Meuse for this one (Nibble/Nov.85/p.20). So from ProDOS JSR RSTINT and JSR BASIC2 exit without erasing variables. BASIC2 ($E003) is the address a CTRL-C <Return> from monitor uses. RSTINT ($9A17) is the same in BASIC.SYSTEM versions 1.0 and 1.1, the only ones so far. Future versions may change, so beware.

For the DUMP itself, we disable interrupts and save the A-X-Y registers, the cursor location CH (so after the dump is done you end up with the cursor where it was when you started), and the output hooks, in lines 2370-2490.

Lines 2510 and 2520 test the enable flag and exit if it's zero. Lines 2530-2650 simulate a PR#SLOT, and kill video echo. Change SLOT in Line 1060 if printer isn't in slot 1, and change NOAIO to 0 in Line 1080 if you have an AIO interface, which is code from Steve Knouse.

Lines 2670-2720 start the dump with screenline specified by WINTOP. In Lines 2730-2820 we handle 80-column dumps. This is good only for //e or //c or IIgs using firmware 80-column routines. There are too many video interface cards that work in different ways to support them all. Some cards have their own dump utilities, so you may not be out of luck if you have a non-standard interface. In line 2810 I use $FF to flag the X-register if 80-columns are active. Otherwise, lines 2840-2870 in MORCHR will end with X in range 0-23. That way in Lines 3090 and 3100 if X gets incremented to 0 then branch back to MORCHR to get even column character, so 2*40=80 characters/line get dumped. This trick of using a register value out of the range you would normally get in the rest of your code is a handy way of saving bytes.

In Line 2840 PRFN gets tested. 0->LORES, and 1->TEXT. If LORES, still only do a mixed mode dump of 20 LORES lines and 4 TEXT lines. Replace Line 2880 by 2 NOP's ($EA) to do full-screen LORES. Note that if you're in 80-column mode, the odd columns are dumped as text regardless of PRFN, since 2820 skips MORCHR. So this dump doesn't do double-LORES. Also, your LORES-creating program should start with TEXT:HOME:GR since TEXT:HOME clears even 80-columns to spaces, whereas GR only clears 40 columns to nulls. If you just do GR, some text may be left in the aux memory text pages, and you'll see it when you do a LORES dump.

So a LORES dump in 80-column mode still dumps only 40 column LORES, spaced out (if you did TEXT:HOME:GR) to match the 4 lines of 80-column text at the bottom.

In Lines 2890-3010 a quasi-40-by-40 resolution for LORES is done by using a space for both LORES blocks off, a caret for top LORES block on, an underline for bottom LORES block on, and an X for both LORES blocks on. This should work for most printers, but feel free to use other characters or symbols, especially if your printer has graphics. The resulting dump is good for monochrome LORES pictures, and especially Bar Charts. Multi-colored LORES and even abstract monochrome patterns lose a lot in the 'translation' to print.

Note especially how 2910 turns off the lo-nibble, while the exclusive-or in 2930 turns off the hi-nibble and turns back on the lo-nibble. Exclusive-or is a handy opcode to learn, and to save bytes by using. Also, the BIT opcode is used in Lines 2960, 2980, and 3000 as a 2-byte NOP to skip the following 2 bytes. BIT changes the N,V, and Z flags, but since 3020 does another CMP anyway, this dosn't matter here. Another 'gotcha' is to avoid inadvertently toggling a soft-switch in the $C000-C0FF range via BIT. I did that once and it took me a long time to figure out!

Lines 3020-3050 make sure a value of $A0 or greater is sent to printer, to avoid control or inverse or flashing characters. Line 3060 then masks off the MSB to avoid graphics in EPSON printers. Lines 3080-3190 send the character to the printer and do loop checking for 80-columns, line length, and WINBOT screen checking.

Lines 3210-3410 restore everything saved upon entry to DUMP. If FLAG was set to show we came thru TRAP, it's cleared in case next time we don't. Besides, if we came thru TRAP, we're in input mode and need to JMP RDKEY to get the next keystroke. If we didn't come thru TRAP, we can just exit via RTS.

Some Demonstrations

So much for the dump. Now it's time for some demo programs. The following, which I call CHARDEMO, shows the active character set, including MouseText if you're in 80-column mode. //e's, c's, and IIgs's have different character sets for 40 and 80 column modes, and you'll see that most printers match the 40-column character set best for INVERSE and FLASHing characters. So what you dump isn't always what you see! If you avoid MouseText and INVERSE and FLASH, you'll do all right except that most printers use code $7F=127 as 'delete previous char' instead of a checkerboard. So if you don't see a tilde (code $7E=126) or a checkerboard, you'll know what happened.

      5  REM CHARDEMO
      6  PRINT  CHR$ (4)"BLOAD SCRNDUMP.PLUS"
      7  CALL 692: REM INSTALL HOOKS
      10  INVERSE 
      20  PRINT  CHR$ (27);"@ABCDEFGHIJKLMNOPQRS
          TUVWXYZ[\]^_"; CHR$ (24)
      30  NORMAL 
      40  GOSUB 1000
      50  INVERSE : GOSUB 1000: NORMAL 
      60  FLASH : GOSUB 1000: NORMAL 
      70  END 
      1000  FOR I = 32 TO 127: PRINT  CHR$ (I);: NEXT
      1010  PRINT : RETURN 

TESTGR does a LORES dump by CALLing DUMP directly. Try in 40 and 80-column mode to see the difference.

      5  TEXT : HOME : GR 
      10  COLOR= 15
      20  FOR I = 0 TO 39
      30  VLIN I,39 AT I: NEXT 
      40  PRINT "THIS IS A TEST."
      50  POKE 9,0: CALL 802: POKE 9,1
      60  REM ASSUNES M/L BLOADED & PARAMETERS SET

TEST40 and TEST80 merely show that no top lines or characters get dropped.

      5  TEXT : HOME 
      10  FOR I = 1 TO 22
      20  PRINT "LINE NUMBER ";I;: HTAB 16
      25  PRINT "THIS IS A TEST."
      30  NEXT 
     
      5  TEXT : HOME 
      10  FOR I = 1 TO 22
      20  PRINT "LINE NUMBER ";I;: HTAB 16
      25  PRINT "THIS IS A TEST.";
      26  FOR J = 1 TO 40: PRINT "A";: NEXT : PRINT 
      30  NEXT 

LORES.PIC, adapted from a David Thornburg program to do LORES and not HIRES patterns, allows you to experiment with patterns. Try Inputs x=0,y=0,s=field size=10, and 2 colors to see an abstract monochrome pattern-it loses a lot in the translation to print.

      5  REM LORES.PIC/DAVID THORNBURG/A+/DEC.86&JUN.87
      6  REM ASSUMES M/L BLOADED & PARAMETERS SET
      7  POKE 9,0: REM FOR LORES DUMP
      10  PRINT "ENTER STARTING X-VALUE ";
      20  INPUT A
      30  PRINT "ENTER STARTING Y-VALUE ";
      40  INPUT B
      50  PRINT "ENTER FIELD SIZE ";
      60  INPUT S
      70  PRINT "ENTER NUMBER OF COLORS (2-16) ";
      80  INPUT N
      90  TEXT : HOME : GR 
      100  FOR I = 0 TO 39
      110  FOR J = 0 TO 39
      120 X = A + (S * (I - 20) / 100)
      130 Y = B + (S * (J - 20) / 100)
      140 Z = 10 *  SIN (X * X) + 10 *  SIN (Y * Y)
      160  COLOR= (Z - (N *  INT (Z / N)))
      170  PLOT I,J
      180  NEXT J
      190  NEXT I
      200  CALL 802: POKE 9,1: REM DUMP, THEN BACK TO TEXT DEFAULT

Try changing Line 140 to Z= X*X + Y*Y and use inputs 0,0,25,2 to see circles. If the pattern isn't too abstract, the resulting dump is ok. Also try 140 Z = X*Y and inputs 0,0,20,2 to see hyperbolas. It's a neat program to play with, but unfortunately abstract and/or multi-colored patterns go past the capabilities of the simplistic LORES dump here.

  1000 *SAVE S.SCRNDUMP.PLUS
  1010 *--------------------------------
  1020 * See AAL/SEP.83/P.22-24/Steve Knouse
  1030 * and NIBBLE/#3/P.19&40/R.M. Mottola
  1040 * ...modified 5/10/87 Louis Pitz
  1050 *--------------------------------
  1060 SLOT   .EQ 1        PRINTER SLOT#
  1070 DOS    .EQ 1        DOS3.3 ACTIVE
  1080 NOAIO  .EQ 1        GENERIC INTERFACE
  1090        .LIST CON    SHOW ALL CONDITIONAL LINES
  1100 *--------------------------------
  1110 PRFLAG .EQ $6       0->DISABLE, 1->ENABLE
  1120 WINTOP .EQ $7       TOP OF WINDOW TO PRINT
  1130 WINBOT .EQ $8       BOTTOM
  1140 PRFN   .EQ $9       0->LORES, 1->TEXT
  1150 PTR    .EQ $EB      USE FOR RELOCATING
  1160 CH     .EQ $24
  1170 BASL   .EQ $28
  1180 CSWL   .EQ $36
  1190 CSWH   .EQ $37
  1200 KSWL   .EQ $38
  1210 KSWH   .EQ $39
  1220 FLAG   .EQ $ED
  1230 LINCTR .EQ $EE
  1240 *--------------------------------
  1250 STACK  .EQ $100     USE FOR RELOCATING
  1260 *--------------------------------
  1270 DOS.WARM   .EQ $3D0
  1280 DOS.HOOK   .EQ $3EA
  1290 DOSKSW .EQ $AA55    DOS ACTIVE->TRUE KSW
  1300 VECTIN .EQ $BE32    PRODOS ACTIVE->TRUE KSW
  1310 RSTINT .EQ $9A17
  1320 BASIC2 .EQ $E003
  1330 *--------------------------------
  1340 NOVID  .EQ $578
  1350 COL80  .EQ $C01F    80-COLUMN ON IF MSB=1
  1360 REGRAM .EQ $C054    SELECT MAIN RAM TEXT PAGE
  1370 AUXRAM .EQ $C055    ...AUX RAM
  1380 IDBYTE .EQ $FBB3    6->//e OR c OR gs
  1390 BASCAL .EQ $FBC1
  1400 VTAB   .EQ $FC22
  1410 RDKEY  .EQ $FD0C
  1420 KEYIN  .EQ $FD1B
  1430 CROUT  .EQ $FD8E
  1440 COUT   .EQ $FDED
  1450 OUTPRT .EQ $FE95
  1460 RTRN   .EQ $FF58
  1470 *--------------------------------
  1480 * First find out 'WHERE AM I?'
  1490 * See "Assembly Lines:  The Book", chapter 14
  1500 *   by Roger Wagner
  1510 *--------------------------------
  1520        .OR $2B4     FIT $2B4.3CF A$2B4,L$11C [A692,L284]
  1530        .TF SCRNDUMP.PLUS
  1540 DISINT PHP          SAVE INTERRUPT STATUS
  1550        SEI          DISABLE INTERRUPTS
  1560 START  JSR RTRN     PUT START+2 ON STACK
  1570        TSX          NOW GET STACK POINTER
  1580        LDA STACK,X  GET (START+2)-HI BYTE
  1590        STA PTR+1
  1600        DEX
  1610        LDA STACK,X  GET (START+2)-LO BYTE
  1620        STA PTR
  1630        PLP          RESTORE INTERRUPT STATUS
  1640        LDY #SETRAP-START-1 OFFSET (SETRAP+1)-(START+2)
  1650        CLC
  1660        ADC #TRAP-START-2   OFFSET (TRAP)-(START+2)
  1670        STA (PTR),Y  PUT IN SETRAP+1
  1680        LDA PTR+1    
  1690        ADC #0       IN CASE CROSS PAGE BOUNDARY
  1700        INY
  1710        INY          SO Y=OFFSET (SETRAP+3)-(START+2)
  1720        STA (PTR),Y  PUT IN SETRAP+3
  1730     .DO DOS      IF DOS ACTIVE
  1740        LDA DOSKSW   SAVE INPUT HOOKS INSIDE TRAP
  1750     .ELSE        IF PRODOS
  1760        LDA VECTIN
  1770     .FIN
  1780        LDY #TRAP+6-START OFFSET (TRAP+8)-(START+2)
  1790        STA (PTR),Y
  1800        LDY #TRAP-START-1 OFFSET (TRAP+1)-(START+2)
  1810        STA (PTR),Y
  1820        INY          NOW OFFSET (TRAP+2)-(START+2)
  1830     .DO DOS      IF DOS ACTIVE
  1840        LDA DOSKSW+1
  1850     .ELSE        IF PRODOS
  1860        LDA VECTIN+1
  1870     .FIN
  1880        STA (PTR),Y
  1890        LDY #TRAP+8-START OFFSET (TRAP+10)-(START+2)
  1900        STA (PTR),Y
  1910        LDX #0       SET DEFAULT VALUES
  1920        STX WINTOP   TOP OF SCREEN=LINE 0
  1930        STX FLAG     CLEAR FLAG AT START
  1940        INX
  1950        STX PRFLAG   ENABLE ROUTINE
  1960        STX PRFN     SET FOR TEXT
  1970        LDX #23      SET BOTTOM SCREEN
  1980        STX WINBOT   TO LINE 23
  1990 *--------------------------------
  2000 * NOW FOR SETRAP -SET INPUT HOOKS
  2010 * TO POINT AT 'TRAP' OR INPUT FILTER
  2020 * NOTE HOW FIRST SECTION MODIFIES (SETRAP+1)
  2030 * AND (SETRAP+3) TO POINT AT TRAP!
  2040 *--------------------------------
  2050 SETRAP LDX #TRAP
  2060        LDY /TRAP
  2070     .DO DOS      IF DOS ACTIVE
  2080        STX KSWL
  2090        STY KSWH
  2100        JMP DOS.HOOK
  2110     .ELSE        PRODOS
  2120        STX VECTIN
  2130        STY VECTIN+1
  2140        RTS
  2150     .FIN
  2160 *--------------------------------
  2170 TRAP   JSR KEYIN    GET KEYPRESS
  2180        CMP #$94     CTRL-T FOR TERMINATE?
  2190        BNE .1       NO
  2200        LDX #KEYIN   RESET INPUT HOOKS
  2210        LDY /KEYIN   WHICH HAVE BEEN SAVED
  2220        STX KSWL
  2230        STY KSWH
  2240     .DO DOS      IF DOS ACTIVE
  2250        JSR DOS.HOOK PASS TO DOS
  2260        JMP DOS.WARM AND EXIT
  2270     .ELSE        PRODOS
  2280        JSR RSTINT   PASS TO PRODOS
  2290        JMP BASIC2   AND EXIT
  2300     .FIN
  2310 .1     CMP #$90     CTRL-P FOR PRINT?
  2320        BEQ .2       YES->BRANCH
  2330        RTS          NO-> EXIT
  2340 .2     LDA #$FF     RESET FLAG TO SHOW
  2350        STA FLAG     HAVE COME THRU TRAP
  2360 *--------------------------------
  2370 DUMP   PHP          SAVE INTERRUPT STATUS
  2380        SEI          DISABLE INTERRUPTS
  2390        PHA          SAVE A,X,Y
  2400        TXA
  2410        PHA
  2420        TYA
  2430        PHA
  2440        LDA CH       SAVE CH
  2450        PHA
  2460        LDA CSWL     SAVE OUTPUT HOOKS
  2470        PHA
  2480        LDA CSWH
  2490        PHA
  2500 *
  2510        LDA PRFLAG   ROUTINE ENABLED?
  2520        BEQ RESTOR   IF NOT, GET OUT
  2530        LDA #SLOT    COLD START BOARD
  2540        JSR OUTPRT   IN SLOT
  2550     .DO NOAIO    GENERIC INTERFACE
  2560        LDA #$89     KILL VIDEO ECHO
  2570        JSR COUT     VIA
  2580        LDA #"N      CTRL-I"N"
  2590        JSR COUT
  2600     .ELSE        SSM AIO INTERFACE
  2610        LDA #$80
  2620        JSR COUT
  2630        LDX SLOT
  2640        STA NOVID,X
  2650     .FIN
  2660 *
  2670        JSR CROUT    START ON A NEW LINE
  2680        LDA WINTOP   
  2690        STA LINCTR   COUNT SCREEN LINES
  2700 NXTLN  JSR BASCAL   GET ADDR OF LINE
  2710        LDY #0       START W/ 1ST CHAR (0-TH)
  2720        STY CH       SET CH=0 TO START LEFT EDGE
  2730 NXTCHR BIT COL80    80-COL ON?
  2740        BPL MORCHR   NO->BRANCH
  2750        LDA IDBYTE   //e OR c OR gs?
  2760        CMP #6
  2770        BNE MORCHR   NO->BRANCH
  2780        STA AUXRAM   READ ODD COLUMN
  2790        LDA (BASL),Y
  2800        STA REGRAM   READY FOR EVEN COLUMN
  2810        LDX #$FF     SHOW 80-COL ON
  2820        BNE INVCHK   SEND ODD COLUMN CHAR
  2830 MORCHR LDA (BASL),Y
  2840        LDX PRFN     LORES OR TEXT?
  2850        BNE INVCHK   IF TEXT THEN BRANCH
  2860        LDX LINCTR   SO NOW LORES
  2870        CPX #20      BUT DO AT MOST 20 LORES & 4 TEXT
  2880        BPL INVCHK   SO TEXT IF PAST LINE 20
  2890        CMP #0       ZERO GRAPHICS?
  2900        BEQ .1       YES, USE SPACE
  2910        AND #$F0     HI-NIBBLE=LO-LORES BLOCK
  2920        BEQ .2       0->USE ^, HI-LORES BLOCK ON
  2930        EOR (BASL),Y LO-NIBBLE=HI-LORES BLOCK
  2940        BEQ .3       0->USE _, LO-LORES BLOCK ON
  2950        LDA #"X      USE X, BOTH BLOCKS ON
  2960        .HS 2C       (BIT TO SKIP NEXT 2 BYTES)
  2970 .1     LDA #$A0     SPACE FOR ZERO
  2980        .HS 2C
  2990 .2     LDA #"^      CARET FOR HI-LORES BLOCK ONLY
  3000        .HS 2C
  3010 .3     LDA #"_      UNDERLINE FOR LO-LORES BLOCK ONLY
  3020 INVCHK CMP #$A0     INVERSE OR FLASHING?
  3030        BCS REGCHR   NO, SO REGULAR CHAR
  3040        ADC #$40     YES, ALTER BITS 6 & 7
  3050        BNE INVCHK   AND KEEP CHECKING
  3060 REGCHR AND #$7F     MASK OFF HI BIT TO AVOID
  3070 *                   EPSON BLOCK GRAPHICS
  3080        JSR COUT     PRINT IT
  3090        INX          80-COL ON-> FROM $FF TO 0
  3100        BEQ MORCHR   IF SO, GET EVEN COLUMN CHAR
  3110        INY
  3120        CPY #40      WHOLE LINE DONE?
  3130        BCC NXTCHR   NO-GET NEXT CHAR
  3140        JSR CROUT    END OF LINE
  3150        INC LINCTR   GOTO NEXT LINE #
  3160        LDA LINCTR   
  3170        CMP WINBOT   AT BOTTOM SCREEN YET?
  3180        BMI NXTLN    NO-GOTO NEXT LINE
  3190        BEQ NXTLN    YES-GET LAST LINE
  3200   
  3210 RESTOR PLA          RESTORE OUTPUT HOOKS
  3220        STA CSWH
  3230        PLA
  3240        STA CSWL
  3250        PLA          RESTORE CH
  3260        STA CH
  3270        JSR VTAB     AND LINE
  3280        PLA          RESTORE Y,X,A
  3290        TAY
  3300        PLA
  3310        TAX
  3320        LDA FLAG     DID WE COME THRU TRAP?
  3330        BNE CLEAR    YES->BRANCH
  3340        PLA          NO-> RESTORE (A)
  3350        PLP          RESTORE INTERRUPT STATUS
  3360        RTS          AND EXIT.
  3370 CLEAR  LDA #0       CLEAR FLAG
  3380        STA FLAG
  3390        PLA          RESTORE (A)
  3400        PLP          RESTORE INTERRUPT STATUS
  3410        JMP RDKEY    GET NEXT KEY-JMPS TO (KSWL)
  3420 *--------------------------------

It's 1988, and ProDOS Thinks it's 1982 Bob Sander-Cederlof

If you are still using ProDOS 1.1.1, and you have some sort of clock card such as Thunderclock, TimeMaster, or any other "standard" ProDOS clock, you have a problem. Apple built this bug into ProDOS, and they came out with the new versions (they call it ProDOS-8 version 1.4 now) just in time.

In my article about the clock driver in the November 1983 issue of AAL (pages 25-28), I discussed the problem. It seemed a little more remote at the time. Apple based ProDOS on the Thunderclock, even though that device does not keep track of the year. The ProDOS clock driver reads the Month, Day, and Day of Week information and does some arithmetic to determine which of six years could produce that day of week on the corresponding month and day. ProDOS 1.1.1 and earlier versions could produce dates from 1982 through 1987. When 1988 rolled around a few weeks ago, hundreds of thousands of Applers around the world slipped back in time to 1982.

And it is not funny! Some programs will not let you operate if the dates are not correct!

Well, there are at least four ways around the problem. You can remove your clock card, and type the date in manually wherever it is really needed. Not very nice.

Or, you can get the up-to-date version of ProDOS, now called ProDOS-8 Version 1.4. You can get it, and then you can copy it to every floppy (both 3 1/2 and 5 1/4), to every RamFactor, to every hard disk in sight. This is tedious, but it is the best solution. If you have a friendly dealer, you can get it from the IIgs system disk. But don't copy the file named PRODOS from this disk (that is only a loader now). Instead, copy the file named P8 from the subdirectory SYSTEM. P8 is a longer file than version 1.1.1 of PRODOS was, so if you use BSAVE to put it on your disks be sure to specify the L parameter. Something like this should do the trick:

Boot any ProDOS disk, preferably one with version 1.4 so the correct dates will get into the file directories you are updating. Get into the S-C Macro Assembler or Applesoft. With the latest IIgs system disk in your drive, type:

    BLOAD SYSTEM/P8,TSYS,A$2000

Now put the disk you want to update into a drive, and type the following. You may want to include slot and drive parameters, or set the prefix to the appropriate value for a ram disk or hard disk.

    UNLOCK PRODOS
    BSAVE PRODOS,TSYS,A$2000,L$3C7D
    LOCK PRODOS

A third approach saves you a trip to the dealer. You can simply PATCH the copies of ProDOS version 1.1.1 to give you the correct year. When you BLOAD the file named PRODOS at $2000, the six-year table is at $4F76. If you look there now you will find the following bytes:

       4F76: 54 54 53 52 57 56 55

These correspond to the years 1984, 1984, 1983, 1982, 1987, 1986, and 1985. Notice that 1984, being a leap year, takes up two of the values. Patch these seven bytes, using the monitor, as follows:

       4F76:5A 59 58 58 57 56 5B

The table now includes the years from 1986 through 1991. If you want 1992 in there also, substitute 5C where I have 57 and 56 above. Both 1988 and 1992 are leap years, so they both take two table positions. When ProDOS 1.4 was released it was still 1987, so there was not room for 1992 in the table.

A fourth possible solution was suggested by reader Garth O'Donnell. You can replace the clock driver inside ProDOS with one that reads the year directly from your clock card! This is what happens when you boot Version 1.4 in a IIgs, because P8 senses that you are in a IIgs and plugs in a different driver. But if you are still using an older Apple, as most of us are, you can modify the PRODOS file to load an intelligent driver for your own clock card. Of course, if you are using a Thunderclock, the driver with the above patches is the best you can do. But if you have a TimeMaster, as Garth does, you can use a program like he wrote.

I decided to try my hand at modifying the standard clock driver so that it uses the year information in the TimeMaster. The following program is derived directly from the standard driver, with as few modifications as possible. It still resides in the ProDOS SYS file at $4F00, but it is a lot shorter. (Maybe you can think of something useful to do with the extra 45 bytes!) It still depends upon the standard ProDOS loader to plug in the actual slot number in lines 1260 and 1310. The major change I made was to call on the ":" instead of the "#" mode. The "#" mode is a ThunderClock mode, which does not return the year. The ":" mode is a TimeMaster mode, which does return the year.

If you have an Applied Engineering Serial Pro card, which includes a TimeMaster compatible clock, you can use the driver I wrote by making the single change as shown in the comments on line 1090. Or, maybe you could use those extra 45 bytes for a subroutine that would check which clock is in the slot and make the appropriate changes at run time.

  1000 *SAVE S.CLOCK.1988
  1010 *--------------------------------
  1020 *  IF THE PRODOS BOOT RECOGNIZES A TIMEMASTER,
  1030 *  A "JMP $D742" IS INSTALLED AT $BF06 AND
  1040 *  THE SLOT ADDRESS IS PATCHED INTO THE FOLLOWING
  1050 *  CODE AT SLOT.A AND SLOT.B BELOW.
  1060 *--------------------------------
  1070 *   DEFINE CLOCK ENTRY POINT
  1080 *--------------------------------
  1090 CLOCK  .EQ $C108    <<<USE $C11D FOR AE SERIAL PRO>>>
  1100 *--------------------------------
  1110 DATE   .EQ $BF90    $BF91 = YYYYYYYM
  1120 *                   $BF90 = MMMDDDDD
  1130 TIME   .EQ DATE+2   $BF93 = 000HHHHH
  1140 *                   $BF92 = 00MMMMMM
  1150 MODE   .EQ $5F8-$C0 TIMEMASTER MODE IN SCREEN HOLE
  1160 *--------------------------------
  1170        .OR $4F00
  1180        .TF B.CLOCK.DRIVER
  1190        .PH $D742
  1200 *--------------------------------
  1210 PRODOS.TIMEMASTER.DRIVER
  1220        LDX SLOT.B   $CN
  1230        LDA MODE,X   SAVE CURRENT TIMEMASTER MODE
  1240        PHA
  1250        LDA #":"     SEND ":" TO TIMEMASTER
  1260        JSR CLOCK+3        SELECT TIMEMASTER MODE
  1270 SLOT.A .EQ *-1
  1280 *--------------------------------
  1290 *      READ TIME & DATE INTO $200...$211 IN FORMAT:
  1300 *--------------------------------
  1310        JSR CLOCK
  1320 SLOT.B .EQ *-1
  1330 *--------------------------------
  1340 *      CONVERT ASCII VALUES TO BINARY
  1350 *      $3E -- MINUTE
  1360 *      $3D -- HOUR
  1370 *      $3C -- YEAR
  1380 *      $3B -- DAY OF MONTH
  1390 *      $3A -- MONTH
  1400 *--------------------------------
  1410        CLC
  1420        LDX #4
  1430        LDY #12      POINT AT MINUTE
  1440 .1     LDA $203,Y   TEN'S DIGIT
  1450        AND #$0F     IGNORE TOP BIT
  1460        STA $3A      MULTIPLY DIGIT BY TEN
  1470        ASL          *2
  1480        ASL          *4
  1490        ADC $3A      *5
  1500        ASL          *10
  1510        ADC $204,Y   ADD UNIT'S DIGIT
  1520        SEC
  1530        SBC #$B0     SUBTRACT ASCII ZERO
  1540        STA $3A,X    STORE VALUE
  1550        DEY          BACK UP TO PREVIOUS FIELD
  1560        DEY
  1570        DEY
  1580        DEX          BACK UP TO PREVIOUS VALUE
  1590        BPL .1       ...UNTIL ALL 5 FIELDS CONVERTED
  1600 *--------------------------------
  1610 *      PACK MONTH AND DAY OF MONTH,
  1620 *--------------------------------
  1630        TAY          MONTH (1...12)
  1640        LSR          00000ABC--D
  1650        ROR          D00000AB--C
  1660        ROR          CD00000A--B
  1670        ROR          BCD00000--A
  1680        ORA $3B      MERGE DAY OF MONTH
  1690        STA DATE     SAVE PACKED DAY AND MONTH
  1700 *--------------------------------
  1710        LDA $3C      YEAR
  1720        ROL          MERGE TOP MONTH BIT
  1730        STA DATE+1   YYYYYYYM
  1740 *--------------------------------
  1750        LDA $3D      GET HOUR
  1760        STA TIME+1
  1770        LDA $3E      GET MINUTE
  1780        STA TIME
  1790        PLA          RESTORE TIMEMASTER MODE
  1800        LDX SLOT.B   GET $CN FOR INDEX
  1810        STA MODE,X
  1820        RTS
  1830 *--------------------------------
  1840        .EP
  1850 *--------------------------------

Border Disorder Dewayne Van Hoozer

BONUS PROGRAM (not printed in the original newsletter)

  1000        .list off
  1010 *SAVE border.disorder
  1020 *--------------------------------
  1030 * This pgm messes with the border color
  1040 *--------------------------------
  1050        .op 65816
  1060        .or $800
  1070        .in /hard2/asm/macros/gs.macros
  1080        .in /hard2/asm/toolsets/tool.locator
  1090        .in /hard2/asm/toolsets/miscellaneous
  1100        .in /hard2/asm/toolsets/Integer.Math
  1110        .in /hard2/asm/toolsets/Text
  1120 *--------------------------------
  1130 Keyboard                .eq $C000
  1140 Keyboard.strobe         .eq $C010
  1150 Screen.Color.Register   .eq $C022      Upper=foreground, Lower=background
  1160 Border.Color.Register   .eq $C034      Lower nibble only (16 colors)
  1170 *                                      Upper nibble effects real-time clock
  1180 *--------------------------------
  1190 Start  jmp Are.You.Ready                         This table is here
  1200        jmp Cycle.Border.Color                    for the purpose of
  1210        jmp Cycle.Background.Color                making it easier to
  1220        jmp Cycle.Foreground.Color                mess around using the monitor
  1230        jmp Fiddle.Border.Color.by.HB
  1240        jmp Fiddle.Foreground.Color.by.HB
  1250        jmp Fiddle.Background.Color.by.HB
  1260        jmp Enable.VBL.IRQs
  1270        jmp Disable.VBL.IRQs
  1280        jmp Task1.on
  1290        jmp Task2.on
  1300        jmp Task1.off
  1310        jmp Task2.off
  1320        jmp Kill.All.Tasks
  1330        jmp Whats.Going.On
  1340 *--------------------------------
  1350 Cycle.Border.Color
  1360        >emulate
  1370        clc
  1380 .0     lda Keyboard     has a key been pressed?
  1390        bmi .9           ... YES, so quit
  1400        lda #$40         %0100.0000  set to read clock chip w/ border=0
  1410 .1     sta Border.Color.Register
  1420        adc #1           increment border color
  1430        cmp #$50         have we gone through all 16 colors?
  1440        bne .1           ...NO, so get the next color
  1450        beq .0           ...YES, so keep looping, ain't it pruddy
  1460 *
  1470 * Watch for wider band at bottom of display area
  1480 * which indicates that VBL interrupts are enabled
  1490 *
  1500 .9     sta Keyboard.Strobe
  1510        rts
  1520 *--------------------------------
  1530 Cycle.Background.Color
  1540        >emulate
  1550        clc
  1560 .0     lda Keyboard     has a key been pressed?
  1570        bmi .9           ... YES, so quit
  1580        lda Screen.Color.Register
  1590        and #$F0
  1600        pha
  1610 .1     pla
  1620        sta Screen.Color.Register
  1630        clc
  1640        adc #1           increment background color
  1650        pha
  1660        and #$0F
  1670        cmp #$0F         have we gone through all 16 colors?
  1680        bne .1           ...NO, so get the next color
  1690        pla
  1700        bra .0           ...YES, so keep looping, ain't it pruddy
  1710 *
  1720 .9     sta Keyboard.Strobe
  1730        rts
  1740 *--------------------------------
  1750 Cycle.Foreground.Color
  1760        >emulate
  1770        clc
  1780 .0     lda Keyboard     has a key been pressed?
  1790        bmi .9           ... YES, so quit
  1800        lda Screen.Color.Register
  1810        and #$0F
  1820        pha
  1830 .1     pla
  1840        sta Screen.Color.Register
  1850        clc
  1860        adc #$10         increment Foreground color
  1870        pha
  1880        and #$F0
  1890        cmp #$F0         have we gone through all 16 colors?
  1900        bne .1           ...NO, so get the next color
  1910        pla
  1920        bra .0           ...YES, so keep looping, ain't it pruddy
  1930 *
  1940 .9     sta Keyboard.Strobe
  1950        rts
  1960 *--------------------------------
  1970 Foreground.Color.Task.Header
  1980        >HB.task.header HC,1
  1990 FFCT
  2000        php                        HB tasks are invoked in native mode with
  2010        pha                        8 bit registers
  2020        lda Screen.Color.Register
  2030        clc
  2040        adc #$10
  2050        sta Screen.Color.Register
  2060        lda #$03                   Change Foreground color every 3 ticks
  2070        sta HC
  2080        pla
  2090        plp
  2100        rtl                        Must always RTL from an HB task
  2110 *--------------------------------
  2120 Background.Color.Task.Header
  2130        >HB.task.header BC,1
  2140 BFCT
  2150        php                        HB tasks are invoked in native mode with
  2160        pha                        8 bit registers
  2170        lda Screen.Color.Register
  2180        and #$0F
  2190        cmp #$0F
  2200        beq .1
  2210        lda Screen.Color.Register
  2220        clc
  2230        adc #$01
  2240        bra .2
  2250 .1     lda Screen.Color.Register
  2260        and #$F0
  2270 .2     sta Screen.Color.Register
  2280        lda #$20                   Change Foreground color every 32 ticks
  2290        sta BC
  2300        pla
  2310        plp
  2320        rtl                        Must always RTL from an HB task
  2330 *--------------------------------
  2340 Border.Disorder.Task.Header
  2350        >hb.task.header TC,1
  2360 Border.Disorder
  2370        php              This is not necessary, but lets be safe this year
  2380        pha
  2390        clc
  2400        lda Border.Color.Register
  2410        adc #1           Increment clock/border color register
  2420        cmp #$50         don't exceed last color available
  2430        bne .2
  2440        lda #$40         Reset to first color
  2450 .2     sta Border.Color.Register
  2460        lda #$3C         Setup task count to invoke task about once a second
  2470        sta TC
  2480        pla
  2490        plp
  2500        rtl
  2510 *--------------------------------
  2520 Fiddle.Border.Color.by.HB
  2530        >native
  2540        >setmode16
  2550 *
  2560        >PushConstL Border.Disorder.Task.Header
  2570        >TK SetHeartBeat
  2580        >jsr.if.error Print.Error
  2590 *
  2600        >setmode16
  2610        >emulate
  2620        rts
  2630 *--------------------------------
  2640 Fiddle.Foreground.Color.by.HB
  2650        >native
  2660        >setmode16
  2670 *
  2680        >PushConstL Foreground.Color.Task.Header
  2690        >TK SetHeartBeat
  2700        >jsr.if.error Print.Error
  2710 *
  2720        >setmode16
  2730        >emulate
  2740        rts
  2750 *--------------------------------
  2760 Fiddle.Background.Color.by.HB
  2770        >native
  2780        >setmode16
  2790 *
  2800        >PushConstL Background.Color.Task.Header
  2810        >TK SetHeartBeat
  2820        >jsr.if.error Print.Error
  2830 *
  2840        >setmode16
  2850        >emulate
  2860        rts
  2870 *--------------------------------
  2880 Enable.vbl.irqs
  2890        >native
  2900        >setmode16
  2910        pea $0002        enable Vertical Blanking interrupts
  2920        >TK IntSource
  2930        >jsr.if.error Print.Error
  2940        >emulate
  2950        rts
  2960 *--------------------------------
  2970 Disable.vbl.irqs
  2980        >native
  2990        >setmode16
  3000        pea $0003        disable Vertical Blanking interrupts
  3010        >TK IntSource
  3020        >jsr.if.error Print.Error
  3030        >emulate
  3040        rts
  3050 *--------------------------------
  3060 Whats.going.on
  3070        >native
  3080        >setmode16
  3090        pea 0                 save room for results
  3100        >TK GetIRQenable
  3110        >jsr.if.error Print.Error
  3120        pla
  3130        sta IRQ.status
  3140        ldx ##$0007
  3150        XBA              Ignore upper eight bits (they're unused)
  3160 .1     rol
  3170        bcc .2
  3180        jsr Print.an.IRQ.Name
  3190 .2     dex
  3200        cpx ##$FFFF
  3210        bne .1
  3220        >emulate
  3230        rts
  3240 *
  3250 *--------------------------------
  3260 Print.an.IRQ.Name
  3270        pha
  3280        phx
  3290        phy
  3300        txa              Get offset into message index table
  3310        asl
  3320        tay
  3330 *
  3340        lda msg.addr,y   Get address of message
  3350        pea 0            push upper 16-bits of long word pointer to string
  3360        pha              push lower 16-bits of long word pointer to string
  3370        >TK WriteString
  3380        >jsr.if.error Print.Error
  3390 *
  3400        pea 0
  3410        pea IE
  3420        >TK WriteCstring
  3430        >jsr.if.error Print.Error
  3440 *
  3450        ply
  3460        plx
  3470        pla
  3480        rts
  3490 *--------------------------------
  3500        >word.boundary             I don't know what 65816 does about 16-bit
  3510 IRQ.status .bs 2,0                fetches from non-word alined addresses
  3520 *--------------------------------
  3530 Msg.Addr
  3540        .da M0           Index to interrupt name strings
  3550        .da M1
  3560        .da M2
  3570        .da M3
  3580        .da M4
  3590        .da M5
  3600        .da M6
  3610        .da M7
  3620        .da IE
  3630 *
  3640 M0     >str "External VGC"
  3650 M1     >str "Scan Line"
  3660 M2     >str "ADB Data"
  3670 M3     >str "Reserved"
  3680 M4     >str "One-Second"
  3690 M5     >str "Quarter-Second"
  3700 M6     >str "Vertical-Blanking"
  3710 M7     >str "Keyboard"
  3720 *
  3730 IE     .as " Interrupt Enabled"  in C-String format
  3740        .hs 0D.00
  3750 *--------------------------------
  3760 Kill.All.Tasks
  3770        >native
  3780        >setmode16
  3790        >TK clrheartbeat           Removes all tasks from HeartBeat Queue
  3800        >jsr.if.error Print.Error
  3810        >emulate
  3820        rts
  3830 *
  3840 Task1.on
  3850        >native
  3860        >setmode16
  3870        >pushconstl Task1hdr
  3880        >TK setheartbeat           Adds a task to the HeartBeat Queue
  3890        >jsr.if.error Print.Error
  3900        >emulate
  3910        rts
  3920 *
  3930 Task1.off
  3940        >native
  3950        >setmode16
  3960        >pushconstl Task1hdr
  3970        >TK delheartbeat           Removes a task from the HeartBeat Queue
  3980        >jsr.if.error Print.Error
  3990        >emulate
  4000        rts
  4010 *
  4020 Task2.on
  4030        >native
  4040        >setmode16
  4050        >pushconstl Task2hdr
  4060        >TK setheartbeat
  4070        >jsr.if.error Print.Error
  4080        >emulate
  4090        rts
  4100 *
  4110 task2.off
  4120        >native
  4130        >setmode16
  4140        >pushconstl Task2hdr
  4150        >TK delheartbeat
  4160        >jsr.if.error Print.Error
  4170        >emulate
  4180        rts
  4190 *--------------------------------
  4200 * Task One:
  4210 * This task increments a location in display screen memory
  4220 * on every 10th VBL
  4230 *--------------------------------
  4240 Task1Hdr
  4250        >hb.task.header Task1Cnt,1
  4260 *--------------------------------
  4270 Task1
  4280        >setmode16
  4290        phk
  4300        plb
  4310        lda ##$0001
  4320        sta task1cnt
  4330        inc $400         The first two bytes of the 40-column text display screen
  4340        >setmode8
  4350        rtl
  4360 *--------------------------------
  4370 * Task Two:
  4380 * This task increments a location in display screen memory
  4390 * on every 30th VBL
  4400 *--------------------------------
  4410 Task2Hdr
  4420        >hb.task.header Task2Cnt,1
  4430 *--------------------------------
  4440 Task2
  4450        >setmode16
  4460        phk
  4470        plb
  4480        lda ##$0001
  4490        sta task2cnt
  4500        inc $402         The next two bytes of the 40-column text display screen
  4510        >setmode8
  4520        rtl
  4530 *--------------------------------
  4540 Print.Error
  4550        pha                               Push word -- error code
  4560        >pushconstl System.Error.Message  Push FullWord -- pointer to message
  4570        >TK SysFailMgr                    Call system failure -- don't come back
  4580        rts
  4590 *--------------------------------
  4600 System.Error.Message
  4610        >str "Error return by ROM toolkit -- $"
  4620 *--------------------------------
  4630 Are.You.Ready
  4640        >native
  4650        >setmode16
  4660 *
  4670        pea 0                 Save room for return button number
  4680        >pushconstl Line.1
  4690        >pushconstl Line.2
  4700        >pushconstl Button.1
  4710        >pushconstl Button.2
  4720        >TK TLTextMountVolume
  4730        BCC .1
  4740        jsr Print.Error
  4750        pla                   Remove button number from stack
  4760        bra .9
  4770 *
  4780 .1     pla                   Get the button number (1..2)
  4790        sta Button.number     and save it for later
  4800        pha                   Put it back
  4810        >pushconstl button.ascii
  4820        pea 2                 All we want is two ascii characters
  4830        pea 0
  4840        >TK Int2Dec
  4850 *
  4860        >pushconstl Button.msg
  4870        >TK WriteCstring
  4880 *
  4890        lda button.number     Button one was for start, two for abort
  4900        lsr
  4910        bcc .9
  4920 *
  4930        >emulate              Turn everything on to impress the casual observer
  4940        jsr kill.all.tasks
  4950        jsr enable.vbl.irqs
  4960        jsr fiddle.border.color.by.HB
  4970        jsr fiddle.foreground.color.by.HB
  4980        jsr fiddle.background.color.by.HB
  4990        jsr task1.on
  5000        jsr task2.on
  5010        jsr Whats.going.on
  5020        rts
  5030 *--------------------------------
  5040 .9     >emulate              Turn everything off
  5050        jsr kill.all.tasks
  5060        jsr disable.vbl.irqs
  5070        rts
  5080 *--------------------------------
  5090 Button.number      .bs 2,0
  5100 *
  5110 Button.msg         .hs 0D.0D           "C-string format"
  5120                    .as "Button #"
  5130 Button.ascii       .bs 2,0             Save some space for ASCII characters
  5140                    .as " was pressed"
  5150                    .hs 0D.00
  5160 *--------------------------------
  5170 Line.1 >str "Border.Disorder"
  5180 Line.2 >str "                By: D. VanHoozer"
  5190 Button.1 >str "Start <rtn>"
  5200 Button.2 >str "Abort <esc>"
  5210 *--------------------------------

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 $18 per year in the USA, sent Bulk Mail; add $3 for First Class postage in USA, Canada, and Mexico; add $14 postage for other countries. Back issues are $1.80 each for Volumes 1-7 (other countries inquire for postage). A subscription to the newsletter and the Monthly Disk containing all source code is $64 per year in the USA, Canada and Mexico, and $87 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.)