Volume 7 -- Issue 11 | August 1987

In This Issue...

- Using Double Lo-Res Graphics
- Patch .TF to Assemble System Files
- Viewing AppleWorks Word Processor Files
- Modify S-C Macro for Overlay Assebmly

New Chips for the IIgs

Apple is now shipping version 2 of the IIgs system ROM, and you are supposed to be able to get a free upgrade at your dealer. If your IIgs has the color fringe problem, they will also change a graphics chip. The new ROM fixes a lot of bugs in the various ROM-based tools, and adds some new features. One new feature I like is a new monitor command ("#") which installs the monitor as a desk accessory. Once done, the monitor is always available in a few keystrokes, and you can easily return to exactly the point from which you called it.

Naturally, the new ROM makes the "$" command in the S-C Macro Assembler non-functional again. I will have to dig into the new code and see why, and fix it again. Unfortunately, as of the day I am typing this, I have not actually been able to upgrade MY IIgs. Mine is a prototype, and I have the "airplane" ROM board where Apple's single ROM chip is supposed to be. Maybe they will send me the appropriate upgrade soon, or maybe I'll be able to make a copy into 27256 EPROMs.

New Apple Accessories Coming Soon

You have read about Applied Engineering's PC Transporter in A+ magazine. But how about the ZIP CHIP from ZIP Technology? Or the GSX IIgs Accelerator from MDIdeas? Or the Memory Saver from Checkmate? ZIP CHIP is one of several new accelerators for pre-IIgs machines which are each a single chip that replace the 6502 or 65C02. The replacement contains the equivalent of a 4 MHz 65C02 with its own clock circuit and some super fast cache RAM; it is supposed to be inexpensive, compatible with everything, and make it all happen 3 or 4 times faster. GSX is a more traditional accelerator, fitting in any IIgs slot (1-7), and having a faster 65816 and RAM than is on the motherboard. It can run at 5.6 MHz and can include as much as 1 Mbyte of fast static RAM. Memory Saver turns an ordinary Apple RAM card or AE gsRAM card into a battery-backed up RAM, much like RamCharger does for RamFactor. All available soon, they say....

Using Double Lo-Res Graphics | Bob Sander-Cederlof

That's right, low resolution, not high. Apple never talks about it, but the //e, //c, and IIgs all have a double low resolution mode, displaying either 40 or 48 lines of 80 pixels, in 16 colors. I know it is not in fashion, but lo-res still has some advantages over hi-res. For one, it is faster; for another, it takes less memory.

Subroutines to plot in lo-res graphics are included in the monitor firmware, and statements are included in both Integer BASIC and Applesoft. I have written some subroutines which provide all the same functions in double lo-res mode, and also added BOX and LINE subroutines. Since all //c and IIgs machines can execute the 65C02 extensions, and since all enhanced //e's also can, I decided to include some 65C02 instructions. They make the code a lot easier to write. I used the PHY, PLY, and BRA opcodes; I also used the INC opcode with no operand to increment the A-register. If you have an old unenhanced //e, you will have to re-write the code where these instructions occur.

Turning Double Lo-Res On and Off:

The same soft switch that changes normal hi-res into double hi-res does the trick for lo-res also. Reading or writing $C05E will double the resolution, and $C05F will put it back to normal. You also have to be in 80-column mode.

In 40-column mode with the "double" switch turned on you get a weird result: You still have only 40 pixels per line, and only four colors rather than 16. Furthermore, somehow which color is displayed for a particular pixel value depends on which column you are in! This is not a particularly useful mode, so I am not going to talk about it any more. Let's just stay in 80-column mode when we want to use double lo-res.

Applesoft's "GR" statement turns on the Lo-Res mode, clears the top 40 graphics lines, and sets the text window to the bottom four text lines. My subroutine "DLR", in lines 1700-1780, only turns on the double lo-res mode. I did not include clearing the graphics screen, or setting the text window. I included a separate subroutine "CLRTOP" for clearing the graphics screen, and "SETTOP" for setting the entire graphics screen to whatever the current color is (lines 2220-2250).

Lines 1640-1670 turn the double lo-res graphics back off, resetting to text mode. You could add a line here to clear the text screen and restore a full text window, like the Applesoft TEXT statement, if you wish.

Setting the Plotting Color:

The monitor firmware contains two entry points that my code uses: SETCOL ($F864) and PLOT ($F800). SETCOL uses the low-order four bits in the A-register to store a color value at COLOR ($30). This value is formed by stuffing the 4-bit color value in both the left and right nybbles of COLOR.

Plotting Single Points:

The monitor PLOT subroutine at $F800 will plot a single point on the normal Lo-Res screen. It expects the Y-coordinate in the A-register, and the X-coordinate in the Y-register. I set up my DPLOT subroutine the same way, but the X-coordinate can be any value from 0 to 79 instead of just 0 to 39. Double Lo-Res works the same way 80-column text does: columns 0, 2, 4, ...78 are stored in AuxRAM, and columns 1, 3, 5, ...79 are stored in MainRAM. Since the softswitches 80COL and 80STORE are both in 80-mode, the PAGE2 switch controls whether text screen addresses reference AUX or MAIN memory.

My DPLOT subroutine is in lines 1800-1960. I look at the least significant bit of the X-coordinate to determine whether the pixel will be in MAIN or AUX memory. If it will be in AUX memory, then I flip to AUX memory before calling PLOT. I also divide the X-coordinate by two, so that PLOT will put it in the right position in the selected bank. After PLOT has painted the pixel, I make sure I am back in MAIN memory at line 1930. I made my DPLOT subroutine save and restore the A- and Y-registers, even though the monitor PLOT subroutine did not. This made some of the other subroutines cleaner.

Lines 1800-1820 are a special entry to DPLOT, and plot the point whose coordinates are stored in the X1 and Y1 variables.

Horizontal and Vertical Lines:

Both Integer BASIC and Applesoft include the HLIN and VLIN statements for drawing horizontal and vertical lines. These are faster to draw and easier to implement than the more general line drawing algorithm included in Hi-Res graphics. Lines 1980-2080 are my double Lo-Res HLIN and VLIN subroutines. Like the ones in the monitor firmware, the coordinates for the beginning of the line must be in the A- and Y-registers; the other end of the line is specified by the value in X2 for HLIN, or in Y2 for VLIN.

My version of HLIN is slower than it ought to be, because I used the monitor PLOT subroutine inside DPLOT. This causes GBASCALC to be called for every pixel, even though it does not need to be called after the first pixel. I could write a second version of DPLOT which calls PLOT1 in the firmware, bypassing the GBASCALC call, but I decide not to bother.

Drawing Boxes:

Lines 2100-2200 will draw a rectangle (box) of the currently selected color. The A- and Y-registers specify the lower-right corner of the rectangle; the variables X1 and Y1 specify the upper-left corner.

To make it easier to program BOX-calls, I defined three macros: FROM, BOX, and BOX.TO. ">FROM valueX,valueY" will assemble a call to SET.X1.Y1 in lines 3160-3200. This subroutine makes sure neither coordinate is beyond the screen and stores them in X1 and Y1. For example:

           >FROM 3,10              (the macro call)
           LDA #10                 (the generated lines)
           LDY #3                          "
           JSR SET.X1.Y1                   "

           >BOX.TO 9,13     (assumes X1,Y1 already set)
           LDA #13
           LDY #9
           JSR BOX

           >BOX 3,10,9,13 will generate a >FROM
                          and a >BOX.TO macro call,
                          which will then be expanded.

Drawing Diagonal Lines:

Or, more properly, "Drawing Lines from Any Point to Any Other Point". In Applesoft Hi-Res graphics you can use "HPLOT x,y TO x2,y2". I wrote a similar subroutine in lines 2300 and following. This subroutine assumes you have already stored the coordinates of the beginning of the line in X1 and Y1, and that the coordinates of the end of the line are in the A- and Y-registers. (Just like BOX did.)

To make it easy to write program which draw lines, I defined some more macros. Use >FROM to set up X1 and Y1, just as with BOX. Use >LINE.TO to draw a line from that point to another. Use >LINE if you want to specify both ends at once. After a line has been drawn, X1 and Y1 will contain the coordinates of the end of that line; this means that you can draw a continuous path around the screen by using a string of >LINE.TO calls.

The algorithm for drawing a straight line from one point to another is probably not familiar to all. I believe it was developed many years ago (middle 1950's?) for the early XY-plotters. My implementation may be a little hasty, because I seem to remember there is a simplification that involves shuffling variable around to fold two cases into one, but it does work and is quick. It does not require any multiplication or division.

In order to be sure I had it right, I wrote a lo-res line-drawing subroutine in Applesoft first. Here is the listing of that program. It may help you to understand the assembly language in my LINE subroutine.

         100  GR : COLOR= 15
         110  INPUT X1,Y1,X2,Y2
         120  GOSUB 1000
         130  GOTO 110
         200  GR
         210  COLOR=  INT ( RND (1) * 4) * 5
         220 X1 =  INT ( RND (1) * 40)
         230 Y1 =  INT ( RND (1) * 40)
         240 X2 =  INT ( RND (1) * 40)
         250 Y2 =  INT ( RND (1) * 40)
         260  GOSUB 1000
         270  IF  PEEK ( - 16384) < 128 THEN 210
         280  POKE  - 16368,0
         290  END
         1000  REM  PLOT X1,Y1 TO X2,Y2
         1010  REM  IN LO-RES WITH CURRENT COLOR
         1020 DX =  ABS (X2 - X1):DY =  ABS (Y2 - Y1)
         1030 SX =  SGN (X2 - X1):SY =  SGN (Y2 - Y1)
         1040 X = X1:Y = Y1
         1050  IF DX < DY THEN 1200
         1060 D = DX - DY
         1070  PLOT X,Y
         1080 D = D - DY:X = X + SX: IF D < 1 THEN Y = Y + SY:D = D + DX
         1090  IF X <  > X2 THEN 1070
         1100  PLOT X2,Y2
         1110  RETURN
         1200 D = DY - DX
         1210  PLOT X,Y
         1220 D = D - DX:Y = Y + SY: IF D < 1 THEN X = X + SX:D = D + DY
         1230  IF Y <  > Y2 THEN 1210
         1240  PLOT X2,Y2
         1250  RETURN

Is There Any Future In It?

I think so. One application that comes to mind is educational programs for young children. These do not need all of the sophistication of the IIgs Super Hi-Res, and do not usually warrant the investment it takes to produce nice programs which use the higher resolution. Lots (I might guess over 100,000) of "quick and dirty" programs have been written since 1977 by mothers and fathers for their children to help them learn something or maybe just to play on the Apple. Most of these, if they use graphics at all, use Lo-Res graphics. The double lo-res is just as nice (if not twice as nice), and if it were accessible from Applesoft it would be just as easy to use.

Now we need to put my subroutines into a form that can be easily used from within Applesoft, write some whiz-bang demos and some documentation, and get it into everyone's hands. Maybe next month....

Meanwhile, I did whip up a demo. Lines 1390-1620 will turn on the double lo-res mode, clear the screen, draw a series of diagonals, place a solid square in the center, and then draw a border around the entire screen. When you press any key it will flip back to text mode.

      1000  .LIF
      1010 *SAVE S.DOUBLE LO-RES
      1020        .OP 65C02
      1030 *--------------------------------
      1040 X2     .EQ $2C
      1050 Y2     .EQ $2D
      1060 COLOR  .EQ $30
      1070 *--------------------------------
      1080 PLOT   .EQ $F800
      1090 SETCOL .EQ $F864
      1100 *--------------------------------
      1110        .MA FROM
      1120        LDA #]2      Y1
      1130        LDY #]1      X1
      1140        JSR SET.X1.Y1
      1150        .EM
      1160 *--------------------------------
      1170        .MA LINE.TO
      1180        LDA #]2      Y2
      1190        LDY #]1      X2
      1200        JSR LINE
      1210        .EM
      1220 *--------------------------------
      1230        .MA LINE
      1240        >FROM ]1,]2     X1,Y1
      1250        >LINE.TO ]3,]4  X2,Y2
      1260        .EM
      1270 *--------------------------------
      1280        .MA BOX.TO
      1290        LDA #]2      Y2
      1300        LDY #]1      X2
      1310        JSR BOX
      1320        .EM
      1330 *--------------------------------
      1340        .MA BOX
      1350        >FROM ]1,]2    X1,Y1
      1360        >BOX.TO ]3,]4  X2,Y2
      1370        .EM
      1380 *--------------------------------
      1390 T
      1400        JSR DLR      Turn on Double Lo-Res
      1410        JSR CLRTOP   Clear screen
      1420        LDA #15      Set Color = White
      1430        JSR SETCOL
      1440 *---DRAW DIAGONAL LINES----------
      1450        >LINE 0,0,39,39
      1460        >LINE 0,39,39,0
      1470        >LINE 79,39,40,0
      1480        >LINE 79,0,40,39
      1490 *---BOX 35,15 TO 44,24-----------
      1500        >BOX 35,15,44,24
      1510 *---BORDER AROUND SCREEN---------
      1520        >LINE 0,0,79,0
      1530        >LINE.TO 79,39
      1540        >LINE.TO 0,39
      1550        >LINE.TO 0,0
      1560 *--------------------------------
      1570 .2     LDA $C000
      1580        BPL .2
      1590        STA $C010
      1600 *--------------------------------
      1610        JSR TEXT
      1620        RTS
      1630 *--------------------------------
      1640 TEXT
      1650        STA $C051    TEXT
      1660        STA $C05F    SINGLE
      1670        STA $C054
      1680        RTS
      1690 *--------------------------------
      1700 DLR
      1710        LDA #$99
      1720        JSR $C300    TURN ON 80-COLUMN MODE
      1730        STA $C050    GRAPHICS
      1740        STA $C053    MIXED TEXT/GRAPHICS
      1750        STA $C054    START IN MAIN MEM
      1760        STA $C056    LO-RES
      1770        STA $C05E    DOUBLE
      1780        RTS
      1790 *--------------------------------
      1800 DPLOT.X1.Y1
      1810        LDA Y1
      1820        LDY X1
      1830 DPLOT  PHY
      1840        PHA          SAVE Y-COORD
      1850        TYA          X-COORD
      1860        LSR
      1870        TAY          X/2
      1880        PLA
      1890        PHA
      1900        BCS .1
      1910        STA $C055
      1920 .1     JSR PLOT
      1930        STA $C054
      1940        PLA
      1950        PLY
      1960        RTS
      1970 *--------------------------------
      1980 HLIN1  INY
      1990 HLIN   JSR DPLOT
      2000        CPY X2
      2010        BCC HLIN1
      2020        RTS
      2030 *--------------------------------
      2040 VLIN1  ADC #1
      2050 VLIN   JSR DPLOT
      2060        CMP Y2
      2070        BCC VLIN1
      2080        RTS
      2090 *--------------------------------
      2100 *   BOX   X2 IN Y, Y2 IN A
      2110 *--------------------------------
      2120 BOX    JSR SET.X2.Y2
      2130        LDA Y1
      2140 .1     LDY X1
      2150        JSR HLIN
      2160        CMP Y2
      2170        INC
      2180        BCC .1
      2190        RTS
      2200 *--------------------------------
      2210 CLRTOP LDA #0
      2220        STA COLOR
      2230 SETTOP >BOX 0,0,79,39
      2240        RTS
      2250 *--------------------------------
      2260 *   LINE FROM X1,Y1 TO X2,Y2
      2270 *         X2 IN Y, Y2 IN A
      2280 *--------------------------------
      2290 LINE   JSR SET.X2.Y2
      2300        LDX #1       First Y's, then X's
      2310 .1     LDY #0       START WITH SGN()=0
      2320        SEC
      2330        LDA X2,X     X2 or Y2
      2340        SBC X1,X     X1 or Y1
      2350        BEQ .3       VERTICAL or HORIZONTAL
      2360        BPL .2       Positive DELTA X or DELTA Y
      2370        DEY          Negative DELTA X or DELTA Y
      2380        EOR #$FF     2's Complement
      2390        INC
      2400        DEY          (overcome following INY)
      2410 .2     INY          SGN() = -1, 0, +1
      2420 .3     STA DX,X     DX or DY
      2430        TYA
      2440        STA SX,X     SX or SY
      2450        DEX
      2460        BPL .1
      2470 *--------------------------------
      2480        SEC
      2490        LDA DY
      2500        SBC DX       DY-DX
      2510        BPL .8       ...DX < DY
      2520        EOR #$FF     2'S COMPLEMENT
      2530        INC
      2540        STA D
      2550 .5     JSR DPLOT.X1.Y1
      2560        CLC
      2570        LDA X1
      2580        ADC SX
      2590        STA X1
      2600        SEC
      2610        LDA D
      2620        SBC DY
      2630        STA D
      2640        BEQ .6
      2650        BPL .7
      2660 .6     CLC          Y = Y + SY
      2670        LDA Y1
      2680        ADC SY
      2690        STA Y1
      2700        LDA D        D = D + DX
      2710        ADC DX
      2720        STA D
      2730 .7     LDY X1
      2740        CPY X2
      2750        BNE .5
      2760        LDA Y2
      2770        STA Y1
      2780        JMP DPLOT
      2790 *--------------------------------
      2800 .8     STA D
      2810 .9     JSR DPLOT.X1.Y1
      2820        CLC
      2830        LDA Y1       Y = Y + SY
      2840        ADC SY
      2850        STA Y1
      2860        SEC          D = D - DX
      2870        LDA D
      2880        SBC DX
      2890        STA D
      2900        BEQ .10      ...D = 0
      2910        BPL .11      ...D > 0
      2920 .10    CLC          X = X + SX
      2930        LDA X1
      2940        ADC SX
      2950        STA X1
      2960        LDA D        D = D + DY
      2970        ADC DY
      2980        STA D
      2990 .11    LDA Y1
      3000        CMP Y2
      3010        BNE .9
      3020        LDY X2
      3030        STY X1
      3040        JMP DPLOT
      3050 *--------------------------------
      3060 LIMIT.XY
      3070        CMP #40
      3080        BCC .1
      3090        LDA #39
      3100 .1     CPY #80
      3110        BCC .2
      3120        LDY #79
      3130 .2     RTS
      3140 *--------------------------------
      3150 SET.X1.Y1
      3160        JSR LIMIT.XY
      3170        STY X1
      3180        STA Y1
      3190        RTS
      3200 *--------------------------------
      3210 SET.X2.Y2
      3220        JSR LIMIT.XY
      3230        STY X2
      3240        STA Y2
      3250        RTS
      3260 *--------------------------------
      3270 X1     .BS 1
      3280 Y1     .BS 1
      3290 SX     .BS 1
      3300 SY     .BS 1
      3310 DX     .BS 1
      3320 DY     .BS 1
      3330 D      .BS 1
      3340 *--------------------------------

Patch .TF to Assemble System Files | Bob Sander-Cederlof

The ProDOS version of S-C Macro Assembler can put your object code directly on a target file, by using the .TF directive to specify the file name. The way it is designed, you have no control over the file type: it always comes out type BIN, which is type $06. Sometimes it would be nice to be able to have the file end up a different file type, such as SYS ($FF), but no such capability is available in the S-C Macro Assembler.

Well, let's change it then! At least temporarily, whenever we need it. If such a patch is possible, we could make an EXEC file to install the patch whenever we need it, and simply re-load when we want to go back to normal.

The code which handles the .TF directive is located somewhere between $8F70 and $9000, depending on the particular serial number you have. The way to locate it in yours is to look for the following code somewhere between $8F70 and $9000. (Use the monitor L command to do the looking.)

         A9 00     LDA #0
         8D C8 BE  STA $BEC8
         8D C9 BE  STA $BEC9
         8D CA BE  STA $BECA
         AD D0 BE  LDA $BED0
         85 98     STA $98
         8D C7 BE  STA $BEC7
         A9 CE     LDA #$CE        Set Mark = 000000
         20 70 BE  JSR $BE70
         B0 ..     BCS ...
         A9 D0     LDA #$D0        Set End-of-File = 000000
         20 70 BE  JSR $BE70
         B0 ..     BCS ...
         A9 0A     LDA #$0A        10 Parameters
         8D B4 BE  STA $BEB4
         A9 C4     LDA #$C4        Get File Info
         20 70 BE  JSR $BE70
         B0 ..     BCS ...
         A9 07     LDA #$07        7 Parameters
         8D B4 BE  STA $BEB4
      ** A9 06     LDA #$06        File Type = $06 (BIN)
         8D B8 BE  STA $BEB8
         A5 BC     LDA $BC         Lo-byte of Origin
         8D B9 BE  STA $BEB9          into AuxType
         A5 BD     LDA $BD         Hi-byte of Origin
         8D BA BE  STA $BEBA          into AuxType+1
         A9 C3     LDA #$C3        Set File Info
         20 70 BE  JSR $BE70
         B0 ..     BCS ...         ...error

It is the starred line "LDA #$06" which we would need to change to get a differenct file type. By changing it to "LDA #$FF" we would get type SYS files for .TF directives. We might also want to change the lines which store into the AuxType field. AuxType holds the Load Address for BIN files, but SYS files always load at $2000 so AuxType is not used.

Once you find the code above, and determine the address of the file type value (the 06 in the LDA #$06 instruction), you can change it with a simple monitor command. Or, you could get fancy and write a program to do the searching for you and make the patch.

And I wrote just such a program! The following code will search for the patch location, and if it finds $06 in the file type address it will change it to $FF. If it finds anything else there, it will change it back to $06. And it will print out a message telling which one it did.

      1000 *SAVE S.PATCH.TF
      1010 *--------------------------------
      1020 PNTR   .EQ $00,01
      1030 *--------------------------------
      1040 MON.COUT   .EQ $FDED
      1050 MON.CROUT  .EQ $FD8E
      1060 *--------------------------------
      1070 T
      1080        LDA #$8E00   START LOOKING AT $8E00
      1090        STA PNTR
      1100        LDA /$8E00
      1110        STA PNTR+1
      1120 *--------------------------------
      1130 .1     LDY #0
      1140 .2     LDA (PNTR),Y
      1150        CMP KEY,Y
      1160        BEQ .3       ...MATCHES SO FAR
      1170        INC PNTR     SLIDE WINDOW DOWN ONE
      1180        BNE .1
      1190        INC PNTR+1
      1200        LDA PNTR+1   STOP BEFORE IT GOES TOO FAR
      1210        CMP /$9100   $90FF IS PLENTY FAR ENOUGH
      1220        BCC .1       ...NOT THERE YET
      1230        LDY #Q.NOT.FOUND
      1240        JMP MSG.OUT
      1250 *--------------------------------
      1260 .3     INY          NEXT BYTE IN KEY AND WINDOW
      1270        CPY #KEY.LEN
      1280        BCC .2       ...MORE IN THE KEY
      1290 *--------------------------------
      1300        LDY #PLOC-KEY
      1310        LDA (PNTR),Y    GET CURRENT FILETYPE
      1320        CMP #$06
      1330        BEQ .4       ...NOW "BIN", MAKE IT "SYS"
      1340        LDA #$06     ...NOT OTHER, MAKE IT "BIN"
      1350        STA (PNTR),Y
      1360        LDY #Q.MADE.BIN
      1370        JMP MSG.OUT
      1380 *--------------------------------
      1390 .4     LDA #$FF     MAKE IT TYPE "SYS"
      1400        STA (PNTR),Y
      1410        LDY #Q.MADE.SYS
      1420        JMP MSG.OUT
      1430 *--------------------------------
      1440 KEY    LDA #$07
      1450        STA $BEB4
      1460        .HS A9     "LDA" OF "LDA #$06"
      1470 PLOC   .EQ *
      1480 KEY.LEN    .EQ *-KEY
      1490 *--------------------------------
      1500 MSG1   ORA #$80
      1510        JSR MON.COUT
      1520        INY
      1530 MSG.OUT
      1540        LDA QTS,Y
      1550        BPL MSG1
      1560        JSR MON.COUT
      1570        JMP MON.CROUT
      1580 *--------------------------------
      1590 QTS
      1600 Q.NOT.FOUND  .EQ *-QTS
      1610        .AT /KEY NOT FOUND/
      1620 Q.MADE.BIN   .EQ *-QTS
      1630        .AT /PATCHED TO FILETYPE "BIN"/
      1640 Q.MADE.SYS   .EQ *-QTS
      1650        .AT /PATCHED TO FILETYPE "SYS"/
      1660 *--------------------------------

Viewing Appleworks Word Processor Files | Bob Sander-Cederlof

I don't happen to be an owner of Appleworks. This may be the tiniest minority of which I am a member. However I do see a lot of type AWP files on disks you send me. Problem, how do I really "see" them? I need a way to list them to the screen so that they look the way you wrote them: a WISIWYS lister (What I See Is What You Saw). It strikes me now that people who are trying to "hear" the files using a Speech Synthesizer might also need some special lister.

I have been doing it by BLOADing the file I need to see, specifying a starting address and file type, like this:

           :BLOAD MSG.TO.SC,A$1000,TAWP

That leading colon is the prompt from the S-C Macro Assembler, which is usually where I am when I do the BLOAD. Then a hex-dump with ASCII on the right, lets me read it. But it is an uncomfortable way to read.

I looked up the format of the Appleworks Word Processor files, and decided to write a quick-and-dirty lister. The first 300 bytes of a type AWP file are of no interest to my lister. This header contains the tab stops and a few flags, and I don't know what else. My documentation is from February 1984, and most of the bytes were undefined at that time. I presume they are better defined now that over three years of improvements have been made to Appleworks, but I don't have that documentation.

If I BLOAD at address $1000, then 300 bytes in starts at $112C. From there to the end of file there are basically two types of data records: commands and text. Commands are two-byte records, and text lines consist of a four-byte header and a variable amount of text.

You distinguish whether a record is a command or text line by looking at the second byte. If it is 00, you have a text line. If it is $D0-$FF, you have a command. If it is anything else, I don't know what you have. Maybe garbage?

If the second byte of a record is $D0 or larger, indicating a command, then the first byte of the record may contain more information for the command. The most frequent command is $D0, which means a carriage return command. In my lister I simply output a carriage return when I see this command. Another popular command is $FF, which means end-of-file. Obviously, when my lister finds this code it is time to end the listing! All the other codes I chose to simply list in hexadecimal. To distinguish them from text they list on separate lines of the form ".$xx.$yy", where xx is the command code (second byte) and yy is the operand value (first byte).

Text lines have the second byte = $00. The first byte of these records contains a byte count, but it is somewhat redundant: it is always two larger than another byte count found in bits 6-0 of the fourth byte of the record. I chose to use this second byte count, which is the actual number of bytes of ASCII and control codes which follow the four-byte header. The third byte of the text line header is supposed to be a leading blank count for the line, but I haven't found anything but zero there in the files I have viewed. Anyway, I chose to ignore this byte in my lister. The sign bit of the fourth byte indicates whether or not a carriage return should be tacked onto the end of this line.

In a paragraph, separate screen lines will be separate text line records. All except the last line will have bit 7 of the fourth header byte = zero, while the last line of the paragraph will have that bit = one. In my lister I decided to allow two ways of viewing the file. If you assemble with WRAP in line 1020 = 0, a file will list with carriage returns at the end of every text line record. This makes it look like it did inside the Appleworks Word Processor. If you change line 1020 to WRAP .EQ 1, no carriage return will be listed until the end of the paragraph.

Within text line records you have both ASCII characters ($20-$7F) and control codes ($00-$1F). The control codes are NOT the same as ASCII control characters. They have special meaning within Appleworks. The documentation I have defines codes $01 through $0B as follows:

          Begin   End

           01      02      Boldface
           03      04      Superscript
           05      06      Subscript
           07      08      Underline

           09 -- Print Page Number
           0A -- Enter Keyboard
           0B -- Sticky Space

I converted $0B (sticky space) to $20 (ASCII space). A sticky space is supposed to print as a space, but not allow splitting at that point at the end of a line. All of the other codes I chose to simply ignore. You could obviously do something special with them. Either act intelligently and do on the screen or printer what they are supposed to do, or simply print them out as "<xx>" or the like.

You could add code to this lister to make it into a super-duper program. I thought about adding the front end to my ProDOS QUIT code program (from July, 1986 AAL). I would change the file type selection from that front end from SYS and BIN to AWP. Then I could easily find any AWP file on any mounted volume using the arrow keys, and BLOAD it with the RETURN key.

The next nice addition would be an output option section that would allow sending the output to a printer, speech synthesizer, or a TXT file.

If you do add either or both of these, or some other nice features, how about sending them here?

      1000 *SAVE LIST.AWP
      1010        .LIST CON    SEE CONDITIONAL CODE TOO
      1020 *--------------------------------
      1030 WRAP   .EQ 0 for CR at end of each line
      1040 *          1 for CR only at end of paragraph
      1050 *--------------------------------
      1060 P      .EQ $00,01
      1070 *--------------------------------
      1080 CROUT  .EQ $FD8E
      1090 PRBYTE .EQ $FDDA
      1100 COUT   .EQ $FDED
      1110 *--------------------------------
      1120 T
      1130        LDA #$1000+300
      1140        STA P
      1150        LDA /$1000+300
      1160        STA P+1
      1170 *--------------------------------
      1180 .1     LDY #1
      1190        LDA (P),Y
      1200        BEQ .2       TEXT LINE
      1210        CMP #$FF
      1220        BEQ .5       END OF FILE
      1230        CMP #$D0
      1240        BEQ .15
      1250        JSR PRB
      1260        DEY
      1270        LDA (P),Y
      1280        JSR PRB
      1290 .15    JSR CROUT
      1300        LDY #1
      1310        JSR BUMP
      1320        JMP .1
      1330 *--------------------------------
      1340 .2     LDY #3
      1350        LDA (P),Y    LENGTH OF TEXT BYTE
      1360   .DO WRAP
      1370        PHA          SAVE FOR CR FLAG
      1380   .FIN
      1390        AND #$7F
      1400        TAX          # CHARS IN LINE
      1410 .3     INY
      1420        LDA (P),Y
      1430        CMP #$20     CONTROL CHAR?
      1440        BCS .35      ...NO
      1450        CMP #$0B     STICKY SPACE?
      1460        BCC .36      ...NO
      1470        LDA #$20     ...YES, JUST PRINT A SPACE
      1480 .35    ORA #$80
      1490        JSR COUT
      1500 .36    DEX
      1510        BNE .3
      1520   .DO WRAP
      1530        PLA
      1540        BPL .4
      1550   .FIN
      1560        JSR CROUT
      1570 .4     JSR BUMP
      1580        JMP .1
      1590 *--------------------------------
      1600 .5     RTS
      1610 *--------------------------------
      1620 PRB    PHA
      1630        LDA #"."
      1640        JSR COUT
      1650        LDA #"$"
      1660        JSR COUT
      1670        PLA
      1680        JMP PRBYTE
      1690 *--------------------------------
      1700 BUMP   TYA
      1710        SEC
      1720        ADC P
      1730        STA P
      1740        BCC .1
      1750        INC P+1
      1760 .1     RTS
      1770 *--------------------------------

Modify S-C Macro For Overlay Assembly | Bob Sander-Cederlof

From time to time we have been asked about adding some features to the S-C Macro Assembler that would make it easy to assemble overlay segments. We have finally done it.

Some of you are developing programs that are so large that there is not room in the main 64K memory for everything at once. I have heard of one system that has over 200K bytes of machine language object code! Overlaying code segments is one method of making such a large program fit. A main program always resides in memory, and loads different sections of code when they are needed. These might be read from disk, from RAMdisk, or just copied from another section of extended memory. The main program calls program sections in one overlay, loads in another overlay and calls routines in it, and so on. The main program section also contains common subroutines that are used by more than one overlay. This is a rather standard technique on mainframe computers.

The normal method for building overlay programs on larger computers involves assembling all the parts as separate relocatable object modules, and then building the overlay "tree" of files with a program called a "Link Editor". You can do something like this with the Apple Programmer's Workshop on the IIgs. However, if you are trying to use the S-C Macro Assembler (and we hope you are!) there is no Link Editor and no way of generating relocatable object modules. The S-C Assembler does all its linking during assembly, and does not make it easy to build overlays. Of course, we are about to change that within this very article.

Since code in the main section calls subroutines in the overlays, we need a way of knowing addresses within the overlays. Likewise the overlays need to know the addresses of programs and data within the main section. If everything is assembled at the same time, all this cross linking can be done automatically. However, it requires making sure that there are no duplications of label names between overlays. The result can be huge assembly-time symbol tables that overflow the space available. By using the .INB directive (available in the ProDOS version of the S-C Macro Assembler) you can maximize the space available for symbols, but it still can run out of space.

If you are using Apple's APW on the IIgs you can do it because of the Link Editor. In fact there are Tools included in the System Loader for handling overlays. Of course you are probably trying to write programs that will run on //e's or //c's, since that is still what most people own. And you are most likely not comfortable with APW and the System Loader and all the other IIgs tools yet. So what can you do?

It is already possible to do it with the S-C Assembler, except that with such large programs the symbol table during assembly can get so large that you run out of memory. With the patching program I am about to describe you can use our assembler to build overlays, even with extremely large amounts of code that would normally overflow the space available for the symbol table during assembly. However, the following patches do not provide a COMPLETE overlaying capability. For one thing, you will have to handle loading each overlay yourself when your program executes. For another, there is not capability for using symbolic reference from the main section into the overlay sections. For another, I have only shown here patches for the ProDOS version of the S-C Macro Assembler. A similar program would work with the DOS version, but the addresses for patching would be different.

I do provide the ability for symbolic reference from overlays back to the main, which is the more important direction. For entry points within the overlay sections, you will need to use some sort of jump tables or other fixed address entry point method. Here is an outline of what I am talking about:

    Main:  load overlay 1
           call routines in overlay 1 thru JMP table
           load overlay 2
           call routines in overlay 2 thru JMP table
           et cetera
           end

           Common Subroutine A
           Common Subroutine B
           ...
           Common Subroutine Z

           Common Data

    Overlay n:  Jump Table:  JMP ENTRY1
                             JMP ENTRY2
                             ---
                             JMP ENTRYN

                ENTRY1:  do stuff, call main routines as
                           necessary, end
                ENTRY2:  et cetera

One way of assembling such a program now without any modifications to the S-C Assembler would be to simply use .IN or .INB to include the main section withing every overlay source file. When you are assembling such a program the main section would be assembled over and over again, needlessly. The new approach lets the main section be assembled once; its object code is written on a file and its symbol table is saved in memory; then each overlay is assembled, beginning with the symbol table of the main program.

The general approach is to assemble the main section; mark the symbol table; assemble the first overlay; prune the symbol table back to the mark; assemble the next overlay; prune, assemble, and so on till the last overlay is assembled. To make it as automatic as possible, the entire assembly is controlled by an EXEC file. The EXEC file loads and executes a file named B.USR.ASM which creates the new overlay assembly capability, loads the main source code or the assembly control file for the main section, assembles the main, and then successive assembles the various overlays.

The key, of course, is B.USR.ASM. This program, when loaded and executed, stores an address into the vector for the USR command into the S-C Macro Assembler. It also searches memory for a particular entry point inside the code which process the "ASM" command, and patches itself with that entry point. The USR command is then enabled.

The USR command works in two ways, depending on whether it is the first use since loading and executing B.USR.ASM, or a subsequent use. The first time it saves the current address for the end of the assembler's symbol table. On subsequent uses it prunes back the assembler's symbol table to that saved address. In both cases, after diddling with the symbol table, it jumps into the ASM command just after the point at which the symbol table would normally be cleared.

Perhaps it is best now to look at the listing of my program. The first section, called INSTALL.USR, is the code that is executed if you "BRUN B.USR.ASM" or "-B.USR.ASM". Lines 1180-1340 search for the spot inside the ASM command code which we need to JMP to. The code at the beginning of the ASM command looks like this:

           STX $60
           STX $EE
           STX $EF
           STX $53
           STX $54
           JSR INIT.SYM.TABLE
           JSR ...

The place where we want to enter the ASM command is that second JSR. We still have to STX in those five locations, and the value in X needs to be zero. You can see the USR version of this at lines 2250-2340. My search loop uses the five STX lines here as a search key. When it finds the same ten bytes inside the assembler somewhere between $9000 and $92FF, it assumes it has found the ASM command code. By adding $000D it computes the address of that second JSR, and then plugs that address into the JMP instruction at line 2340. Lines 1350-1420 do that computation and plugging. Lines 1430-1480 then install the USR address into the USR vector and return to the assembler.

When you type or EXEC the USR command, the assembler will jump to USR at line 1500. Lines 1510-1520 check a flag to see if this is the first time you did it or not. If it is the first time, lines 1540-1600 save the address of the end of the symbol table, and then line 1610 starts up the ASM command without clearing out the symbol table. This is how you would assemble the first overlay, after assembling the main section.

On subsequent uses of USR, lines 1630-2230 will prune back the symbol table to the same state it was the first time, and start up the assembly with that symbol table. This "pruning back" is not easy. The structure of the symbol tables is a set of 28 linked chains. There is one chain for each letter of the alphabet, and a symbol is placed in a chain based on the first letter of its name. There are two more chains, one used for macro definitions and another for target file information. Within each chain, symbol entries are kept in alphabetical order. This means that when we assemble the first overlay, some new symbols will be linked into existing chains in arbitrary positions. The "pruning back" operations cannot simply store the original "end of symbol table" address; it must also remove any and all symbol entries from each chain which are beyond that address.

Lines 1630-2140 do just that. There are 28 pointers kept at $0130-$0167 which point to the first entry in each of the 28 chains. Then the first two bytes of each symbol entry contain a pointer to the next entry in alphabetical order. The last symbol entry in a chain contains 0000 in these two bytes. If there are no symbols in a particular chain, its pointer in the $0130 area will be 0000. This code loops from 0 to 27, eliminating all entries beyond the marked end of table address for each of the 28 chains.

Finally, lines 2190-2230 restore the saved end of table address, and we fall into the code to enter the assembler without clearing the symbol table.

Using my program with an EXEC file makes all this easy. Here is an example of such an EXEC file:

           -B.USR.ASM
           LOAD MAIN
           ASM
           LOAD OVERLAY.1
           USR
           LOAD OVERLAY.2
           USR
           LOAD OVERLAY.3
           USR

Each of the files loaded above could either be complete sections, or merely small assembly control files with a lot of .IN or .INB directives specifying source files to include. I recommend the latter, using the .INB directive. For example, MAIN might look like this:

           1000    .TI 55,TITLE OF MAIN PROGRAM
           1010    .INB MAIN.EQUATES
           1020    .INB MAIN.PART1
           1030    .INB MAIN.PART2
           1040    .INB MAIN.LAST.PART

An OVERLAY.n file would be similar, but would name the particular files involved in that overlay. Since each overlay is separately assembled, there may be identical symbol names in different overlays with no conflict. Since the MAIN symbol table is present during the assembly of each overlay, the code in the overlays may freely reference any symbol names in the MAIN section.

      1000 *SAVE S.USR.ASM
      1010 *--------------------------------
      1020        .OR $300
      1030        .TF B.USR.ASM
      1040 *--------------------------------
      1050 PAGE.NUM   .EQ $53,54    Page number, starts at 0000
      1060 PASS.NUM   .EQ $60       Pass number, starts at 00
      1070 EOT        .EQ $CC,CD    Address of End of Symbol Table
      1080 ERROR.CNT  .EQ $EE,EF    Error count, starts at 0000
      1090 *--------------------------------
      1100 HASH.TBL   .EQ $0130 ... $0167   (28 pointers)
      1110 USER.HOOK  .EQ $8006     Address USR command jumps to
      1120 *--------------------------------
      1130 CHAIN      .EQ $53,54    Use PAGE.NUM temporarily
      1140 FWD        .EQ $EE,EF    Use ERROR.CNT temporarily
      1150 PASM       .EQ $EE,EF    Use ERROR.CNT temporarily
      1160 *--------------------------------
      1170 INSTALL.USR
      1180        LDA #$9000   FIND ENTRY INTO "ASM" COMMAND
      1190        STA PASM
      1200        LDA /$9000
      1210        STA PASM+1
      1220        LDX #3
      1230 .1     LDY #9
      1240 .2     LDA (PASM),Y
      1250        CMP KEY,Y
      1260        BEQ .3
      1270        INC PASM
      1280        BNE .1
      1290        INC PASM+1
      1300        DEX
      1310        BNE .1
      1320        BRK          TRAP IF KEY NOT FOUND
      1330 .3     DEY
      1340        BPL .2
      1350 *---Install ASM link address-----
      1360        CLC
      1370        LDA PASM
      1380        ADC #$0D
      1390        STA JMPASM+1
      1400        LDA PASM+1
      1410        ADC #0
      1420        STA JMPASM+2
      1430 *---Install USR now--------------
      1440        LDA #USR
      1450        STA USER.HOOK+1
      1460        LDA /USR
      1470        STA USER.HOOK+2
      1480        RTS
      1490 *--------------------------------
      1500 USR
      1510        BIT FIRST.TIME.FLAG
      1520        BMI SUBSEQUENT.TIME
      1530 *--------------------------------
      1540 FIRST.TIME
      1550        SEC          Set Flag Negative for Subsequent
      1560        ROR FIRST.TIME.FLAG
      1570        LDA EOT      Save Address of End of
      1580        STA PNTR          SYMBOL TABLE
      1590        LDA EOT+1
      1600        STA PNTR+1
      1610        JMP JUMP.INTO.ASSEMBLER
      1620 *--------------------------------
      1630 SUBSEQUENT.TIME
      1640 ***    Rebuild pointers by starting at beginning of
      1650 ***    each chain in HASH TABLE and traveling to end
      1660 ***    of each chain, lopping off any symbols beyond
      1670 ***    (PNTR).
      1680        LDX #0       FOR X = 0 TO 27
      1690 *--------------------------------
      1700 .1     TXA
      1710        ASL          INDEX * 2, CLC
      1720        ADC #HASH.TBL
      1730        STA CHAIN
      1740        LDA /HASH.TBL
      1750        STA CHAIN+1
      1760 *--------------------------------
      1770 .2     LDY #0       Get address of next symbol
      1780        LDA (CHAIN),Y     in this chain
      1790        STA FWD
      1800        CMP PNTR     See if beyond truncation point
      1810        INY
      1820        LDA (CHAIN),Y
      1830        BEQ .5       ...end of this chain
      1840        STA FWD+1
      1850        SBC PNTR+1
      1860        BCS .3       ...beyond truncation point
      1870        LDA FWD      Step forward to next link
      1880        STA CHAIN
      1890        LDA FWD+1
      1900        STA CHAIN+1
      1910        BCC .2       ...ALWAYS
      1920 *---Found link beyond truncation point---
      1930 .3     LDY #0       Look forward through chain
      1940        LDA (FWD),Y       until find one below
      1950        STA TCHAIN        the truncation point.
      1960        CMP PNTR
      1970        INY
      1980        LDA (FWD),Y
      1990        STA TCHAIN+1
      2000        SBC PNTR+1
      2010        BCC .4       ...below truncation point
      2020        LDA TCHAIN   Step forward to next link
      2030        STA FWD           in this chain
      2040        LDA TCHAIN+1
      2050        STA FWD+1
      2060        BCS .3       ...ALWAYS
      2070 *---Patch out truncated links of chain---
      2080 .4     LDY #0
      2090        LDA TCHAIN
      2100        STA (CHAIN),Y
      2110        INY
      2120        LDA TCHAIN+1
      2130        STA (CHAIN),Y
      2140        BCC .2       ...ALWAYS
      2150 *--------------------------------
      2160 .5     INX          NEXT X   (0...27)
      2170        CPX #28
      2180        BCC .1
      2190 *---Put EOT back too-------------
      2200        LDA PNTR
      2210        STA EOT
      2220        LDA PNTR+1
      2230        STA EOT+1
      2240 *--------------------------------
      2250 JUMP.INTO.ASSEMBLER
      2260        LDX #0
      2270 KEY
      2280        STX PASS.NUM
      2290        STX ERROR.CNT
      2300        STX ERROR.CNT+1
      2310        STX PAGE.NUM
      2320        STX PAGE.NUM+1
      2330 JMPASM
      2340        JMP *-*  *** ADDRESS FILLED IN BY PROGRAM ***
      2350 *--------------------------------
      2360 FIRST.TIME.FLAG .HS 00
      2370 PNTR   .HS 00.00
      2380 TCHAIN .HS 00.00
      2390 *--------------------------------

Apple Assembly Line (ISSN 0889-4302) is published monthly by S-C SOFTWARE CORPORATION, P.O. Box 280300, Dallas, Texas 75228. Phone (214) 324-2050. Subscription rate is $18 per year in the USA, sent Bulk Mail; add $3 for First Class postage in USA, Canada, and Mexico; add $14 postage for other countries. Back issues are available for $1.80 each (other countries add $1 per back issue for postage).

All material herein is copyrighted by S-C SOFTWARE CORPORATION, all rights reserved. (Apple is a registered trademark of Apple Computer, Inc.)