Apple Assembly Line
Volume 5 -- Issue 8May 1985

S-C Macro Assembler for ProDOS

At long last, the news you've all been waiting for: the ProDOS version of the S-C Macro Assembler is almost ready. We have a working assembler in Beta testing, and it's doing just fine. We need to spend another month or two shaking on it and developing documentation, so it will be just a little longer 'til we start shipping, but it's on the way! Watch the front page of AAL for the announcement.

News from Don Lancaster

After nearly a year of delay at the publisher, Enhancing Your Apple II and //e, volume 2 is here! This followup to his very popular collection of Apple tricks, gimmicks, and techniques contains still more high-quality information on how to get the most out of our favorite computer. Here Don provides the tricks of microjustification and proportional spacing for Applewriter //e, an absolute "Old Monitor" style RESET for the //e, a software-only video synchronization technique for all Apple II's and //e's, and a just-for-fun guide to mapping and playing Castle Wolfenstein.

I've been saving the best for last: Tearing Into Applewriter //e. Here is 86 pages of priceless data on the internal workings of the most popular Apple Word Processor, including how to capture source code and customize it to your own taste. See our ad on page three for price and shipping.

As you will notice from his ad in this issue, Lancaster has been hard at work tearing into Appleworks, and has a set of disks available on that program. We haven't seen those yet, but I'll bet they're more of the same great inside info we've come to expect from Don.


A New Catalog for DOS 3.3Robert F. O'Brien
Dublin, Ireland

In AAL March '85 Bob S-C presented re-writes of some loosely coded DOS sections to make space for patches - the Catalog Function Handler is another such loose bit of code, but rather than just free up some bytes I decided to add some useful features which Apple omitted and correct an annoying error at the same time. This new routine adds the following features to the CATALOG command:

  1. Displays the free space remaining on the disk.
  2. Allows you to terminate the Catalog during the normal pause after a screenful of files have been displayed by pressing the <ESC>-key (or other designated key).
  3. Displays the correct number of sectors for each file in the Catalog for even the very large files - where the number of sectors exceeds 255 (which was the limit of the old PRINT.DECIMAL subroutine at $AE42 in DOS 3.3).
  4. Optionally displays two filenames on each line of the Catalog - this is an 80-Column card option, also great for double-barrelled CATALOG printouts (for labels etc.).

In addition, the new Catalog retains the principal features of the old routine such as displaying the Volume number, the locked file indicator (*) and the file type abbreviation so that the user is not deprived of any essential information.

All the foregoing was achieved without using any additional DOS RAM space or zero-page locations other than that space already used by the Catalog Function Handler itself. Of course, something of the old routine had to be sacrificed in order to add the new features - it was necessary to omit the message "DISK VOLUME " from the beginning of the display. The 12-byte space where this message resided is now used to house a subroutine to check for locked files.

Even with all these enhancements, there are 17 free bytes left over! You could use some of them to print out an abbreviated form of the "DISK VOLUME " message, like "V=".

An additional constraint I saddled myself with in doing the re-write was that PRINT.DECIMAL (the DOS subroutine used to convert the hexadecimal numbers in locations $44,45 to decimal and print them) should retain its normal entry point ($AE42) so that the new code would be compatible with other programs which might use it.

For those who wish to get double-barrelled Catalog listings on an 80-column card or on a printer just change the "SEC" at line 2010 to "LSR". In other words, $AE12:4A will enable the wide printout, and $AE12:38 will put it back to normal.

To install the new patches just BLOAD the two binary files: NEW CATALOG PART 1, and NEW CATALOG PART 2. You can put the modified DOS onto any normal disk using Bob Perkins' technique (in AAL Aug 1982 p.24) without disturbing any other files present, or INIT a blank disk and the modified DOS will be incorporated on it. If you prefer to terminate long Catalog's with the <RETURN> key as you do for listings with the S-C Macro Assemblers just change byte $AE21:8D.

Also, if you are prepared to restrict yourself to 11 character file-names you can have a double-barrelled Catalog on the 40-Column screen by changing byte $ADF7:0B (POKE 44535,11), but I feel it would be of little value overall.

Now for a more detailed look at the program internals. Due to the requirement to save as many bytes as possible to squeeze in the desired features it was not possible to write the code in as straight-forward a manner as one would like. Even so, the routine was written with 17 byte to spare - after many re-writes to fit in all the features.

Lines 1020-1240 define various subroutines, variables, and data tables inside the rest of DOS.

Lines 1320-1360 use the same code as the original Catalog routine to initialize the File Manager and read the disk Volume Table Of Contents (VTOC).

In lines 1370-1410 we clear LINE.SKIP.FLAG which is used by SKIP.LINE subroutine to determine whether to tab to a second column or print a carriage return. Then we call PRINT.DECIMAL.YA to print the volume number. The volume number itself is passed in the A-register, and a zero high-byte in Y. Since we stripped out the code for printing "DISK VOLUME ", the volume number will be printed immediately to the right of the CATALOG command, on the same line. You will see "CATALOG 254 395", or the like, where the first number is the volume number and the second is the count of free sectors.

By making a special entry above the PRINT.DECIMAL subroutine which is used both here and at line 1830 below, we save several bytes. Of course we have already save a couple dozen bytes by not printing "DISK VOLUME ".

Calculation of the free disk space is made in lines 1420-1530. We make use of a new feature in the corrected PRINT.DECIMAL routine whereby $44 and $45 are reduced to 0 during the conversion - resulting in a saving of 4 bytes by not having to re-zero $45. (In the old routine only $44 was reduced to 0.)

In the VTOC 4 bytes are set aside for each track to indicate sector usage although only 2 are needed for a standard Apple disk. (The extra space allows up to 50 tracks and up to 32 sectors per track to be initialized.) A bit set=1 means that the corresponding sector on the track is available for use. If a bit is set=0 then the sector is already allocated. So it was simply a matter of counting every bit set from offset byte $38 (track 0) to Byte $C3 (for Trk $22) of the VTOC buffer to get a count of the free space. If you want to count all the way to the 50th track, in case the program is working with a hard disk like the Sider or Corvus, or a RANA 320K floppy, change lines 1430-1440:

       1430       LDX #$38
       1440       LDA VTOC.BUFFER,X

In line 1550 we have another departure from the original code - 2 bytes were saved by entering the tail end of the SKIP.LINE subroutine in order to set the number of lines to place on the screen before pausing during a Catalog. This has the added advantage that you can customize your Catalog more easily in that the line count can be adjusted by modifying a single byte ($AE25).

At lines 1570-1610 we start by clearing the Carry flag so that the first sector of the directory will be read (track $11, sector $0F). Also we set the index (X) to the first filename entry in the sector.

Lines 1620-1660 examine the track number of the Track/Sector list for the current filename entry. Should this number be 0 it indicates that we are at the end of the directory, at which point we would terminate the Catalog by exiting the File Manager routine by a jump to $B37F.

Fortunately, there was a JMP $B37F instruction within relative branching distance of the Catalog Function Handler. We could therefore dispense with the JMP to $B37F in the original code saving a further 3 bytes by branching to FM.EXIT at $AD86 instead. This is an address in the DELETE Function Handler ($AD2B-AD97) which precedes the Catalog routine in RAM. There are three ways we can terminate the Catalog, which all result in a branch to FM.EXIT: here at line 1600 when we find there are no more catalog sectors, at line 1650 when we find there are no more catalog entries, and at line 2090 when the ESC-key is typed during a screen-pause.

At line 1660 if the track number value is negative (bit 7 set) then we have found a deleted file. Deleted files don't show up on the Catalog, so we call on the subroutine at $B230 which sets the X-Register to the value of the entry point offset for the next entry in the sector, if any.

If on return from this subroutine the Carry flag bit is set (=1) then we have reached the end of the current catalog sector and we branch back to READ.SECTOR at line 1580 to read the next directory sector, if any. (Each directory sector accommodates 7 entries.)

At line 1680 we call the SKIP.LINE subroutine, which normally merely prints a carriage return. This routine was called from five different places in the original catalog code, so we have saved a dozen bytes by only calling it from this one place. (Putting it in-line would save four more!)

At line 1700 we call the new subroutine at the site of the DISK VOLUME message space to check for locked files and print the space or asterisk. This routine also leaves the file type code in the Y-register. This code could be placed in-line, rather than making it a subroutine, but then the final two lines could not be used as a short PRINT.SPACE subroutine.

Lines 1710-1790 convert the file type code to a file type character. The file type code is in bits 6-0, and is either zero (meaning type T), or a single bit. The hex values 40, 20, 10, 08, 04, 02, and 01 stand for file types B, A, R, S, B, A, and I. A string at $B4C8 holds "TIABSRAB", so we need to convert the bit position to an index value, and pick up the character out of that string. The ASL at line 1740 elminates the "lock/unlock" bit. The loop in line 1750-1770 shifts bits out until the value is zero, counting up in the Y-register. If the value was already zero, we exit immediately with Y=0, and type is "T". A type value of 1 gives an index of 1, up through $40 giving an index of 7.

By the way, types 40 and 20 are not Binary and Applesoft. They are hardly ever used, except in protection schemes. Types 04 and 02 are Binary and Applesoft.

The original catalog code had a significantly longer loop for converting the file type number to an index. You might want to compare the two.

The number of sectors in the file is picked up and converted in lines 1800-1830 and the decimal value is printed, surrounded by spaces. Lines 1840-1900 print out the file name.

Lines 1920-1950 advance to the next filename entry, and branch either to process it or to read in another catalog sector.

Lines 1970-2130 usually print a carriage return. If you have changed line 2010 to "LSR", to get double column catalogs, the least significant bit of LINE.SKIP.FLAG will determine whether to print a carriage return or not. When line 2010 is "SEC" we will always get a carriage return. If a carriage return is printed, we also count the line. When the line count is complete, we pause and wait for a keystroke. If that key is an ESC-key, the catalog will terminate. If not, the line count is re-initialized and we go back for more file names.

Line 2150 simply reserves 17 bytes, shoving the PRINT.DECIMAL routine down so that it still starts at $AE42 like it used to. These 17 bytes could be used for other code or data, whatever you like.

Lines 2160-2230 store a value to be converted and printed, print a blank, and then fall into the PRINT.DECIMAL subroutine.

The new corrected PRINT.DECIMAL subroutine is actually a little shorter than th buggy original. It left room for a JMP PRINT.SPACE at the end, which saved calling PRINT.SPACE from several other places. It also left room for the LINE.SKIP.FLAG variable.

The PRINT.DECIMAL subroutine (lines 2240-2490) effectively divides the number in $44,45 by subtracting in turn the values 100, 10 and 1 from it - a 16-bit subtraction. The count of the number of subtractions and the low order byte remainder are temporarily stored on the stack to conserve memory usage. We start with 100 and keep subtracting it and incrementing the subtraction-counter until we get borrow, at which point we print the counter value.

Now $44,45 will contain the remainder and so we continue using 10 and then 1 until three decimal digits are printed. This subroutine can accurately convert numbers having values up to 999 decimal.

CHALLENGE. Even though we have already squeezed out 17 bytes, while adding new features, we did lose the "DISK VOLUME " message. Can someone out there squeeze enough more out, without losing any features, to slip the message back in?

CAVEAT. If you decide to put this new CATALOG program on your disks, please be careful. There are some programs which temporarily patch the catalog routine themselves. In particular, ES-CAPE and other commercial programs patch the SKIP.LINES subroutine so that the pause is eliminated. Since SKIP.LINES has been moved and is different, no telling what might happen.

  1000 *SAVE S.NEW CATALOG
  1010 *--------------------------------
  1020 DOS.ARITH.REG          .EQ $44,45
  1030 *--------------------------------
  1040 ADV.NEXT.DIR.ENTRY     .EQ $B230
  1050 DOS.INIT.FM            .EQ $ABDC
  1060 EXIT.FM                .EQ $AD86
  1070 MON.COUT               .EQ $FDED
  1080 MON.CROUT              .EQ $FD8E
  1090 MON.RDKEY              .EQ $FD0C
  1100 READ.DIRECTORY.SECTOR  .EQ $B011
  1110 READ.VTOC              .EQ $AFF7
  1120 *--------------------------------
  1130 DEC.CONVERSION.TABLE   .EQ $B3A4
  1140 FILE.TYPE.NAME.TABLE   .EQ $B3A7
  1150 *--------------------------------
  1160 CATALOG.LINE.COUNT     .EQ $B39D
  1170 DIRECTORY.ENTRY        .EQ $B4C6
  1180 DIRECTORY.INDEX        .EQ $B39C
  1190 DISK.VOL.NUMBER        .EQ $B7F6
  1200 FILE.NAME              .EQ $B4C9
  1210 FILE.SIZE              .EQ $B4E7
  1220 FILE.TYPE              .EQ $B4C8
  1230 FM.VOL.NUMBER          .EQ $B5F9
  1240 VTOC.BUFFER            .EQ $B3BB
  1250 *--------------------------------
  1260 *    New Catalog for DOS 3.3
  1270 *    by  Robert F.O'Brien
  1280 *--------------------------------
  1290        .OR $AD98
  1300        .TF NEW CATALOG PART 1
  1310 *--------------------------------
  1320 CATALOG
  1330        JSR DOS.INIT.FM   Init file manager.
  1340        LDA #$FF          Set Volume = 0
  1350        STA FM.VOL.NUMBER   (matches any volume)
  1360        JSR READ.VTOC     Load in VTOC into buffer.
  1370 *---Print Volume Number----------
  1380        LDY #0            High byte = 0
  1390        STY LINE.SKIP.FLAG    (signal to skip)
  1400        LDA DISK.VOL.NUMBER   low byte
  1410        JSR PRINT.DECIMAL.YA
  1420 *---Calculate Free Space---------
  1430        LDX #$74          Trk 0 VTOC offset
  1440 .1     LDA VTOC.BUFFER+$38-$74,X     Bit Map Byte
  1450 .2     BPL .3            This sector in use
  1460        INC DOS.ARITH.REG   Count a free sector.
  1470        BNE .3
  1480        INC DOS.ARITH.REG+1
  1490 .3     ASL               Check next bit
  1500        BNE .2            Still more in this byte
  1510 .4     INX               Next byte of bit map
  1520        BNE .1            ...still more
  1530        JSR PRINT.DECIMAL    print number free
  1540 *---Start Line count-------------
  1550        JSR SET.LINE.COUNT        lines to print.
  1560 *---Start reading directory------
  1570        CLC               Get first sector.
  1580 READ.SECTOR
  1590        JSR READ.DIRECTORY.SECTOR 
  1600        BCS EXIT.FM       No more sectors
  1610        LDX #0            Index to 1st file in sector
  1620 SET.ENTRY.INDEX
  1630        STX DIRECTORY.INDEX   Set entry offset
  1640        LDA DIRECTORY.ENTRY,X See if valid filename
  1650 EXIT   BEQ EXIT.FM       ...end of directory
  1660        BMI NEXT.ENTRY    ...ignore deleted file.
  1670 *---Start next file display------
  1680        JSR SKIP.LINE     Next line or Tab
  1690 *---Locked or Unlocked-----------
  1700        JSR LOCKED.FILE.CHECK     "*" if locked file.
  1710 *---File Type--------------------
  1720        TYA               Get file type byte
  1730        LDY #-1           Index to type table
  1740        ASL               Ignore Bit 7
  1750 .1     INY               Next file type code
  1760        LSR               Check bit of type byte
  1770        BNE .1            ...not yet
  1780 .2     LDA FILE.TYPE.NAME.TABLE,Y Get file type
  1790        JSR MON.COUT      ...and print it
  1800 *---File Size--------------------
  1810        LDY FILE.SIZE+1,X         high order byte
  1820        LDA FILE.SIZE,X           low order byte
  1830        JSR PRINT.DECIMAL.YA      print total sect.
  1840 *---File Name--------------------
  1850        LDY #30
  1860 .3     LDA FILE.NAME,X           char. no. in Y.
  1870        JSR MON.COUT          print file name.
  1880        INX                       next char.
  1890        DEY                       
  1900        BNE .3                    not done yet!
  1910 *---Next File in Directory-------
  1920 NEXT.ENTRY
  1930        JSR ADV.NEXT.DIR.ENTRY  Set X-Reg for next file
  1940        BCC SET.ENTRY.INDEX     more in sector
  1950        BCS READ.SECTOR         get next sector
  1960 *--------------------------------
  1970 SKIP.LINE
  1980        JSR PRINT.SPACE   Separate 2nd line entry
  1990        INC LINE.SKIP.FLAG  Toggle lsbit
  2000        LDA LINE.SKIP.FLAG  Check Odd or Even
  2010        SEC        <<<Change to "LSR" for double
  2020 *                    column CATALOG >>>
  2030        BCC RETURN
  2040        JSR MON.CROUT     Start a new line
  2050        DEC CATALOG.LINE.COUNT    continue countdown
  2060        BNE RETURN                not full screen yet
  2070        JSR MON.RDKEY     Pause for keypress!
  2080        CMP #$9B          Is it  ESC-key?
  2090        BEQ EXIT          ...yes, exit file manager
  2100 SET.LINE.COUNT
  2110        LDA #21           lines per screenful
  2120        STA CATALOG.LINE.COUNT
  2130 RETURN RTS               Continue catalog
  2140 *--------------------------------
  2150        .BS 17            17 FREE BYTES!
  2160 *--------------------------------
  2170 *   Print (YA) with leading and
  2180 *      trailing blanks.
  2190 *--------------------------------
  2200 PRINT.DECIMAL.YA
  2210        STY DOS.ARITH.REG+1
  2220        STA DOS.ARITH.REG
  2230        JSR PRINT.SPACE
  2240 *--------------------------------
  2250 *   Print ($44,45) with trailing blank
  2260 *--------------------------------
  2270 PRINT.DECIMAL
  2280        LDY #2            Set for 3 divisors
  2290 .1     LDA #$B0          ASCII zero
  2300 .2     PHA               save digit on stack
  2310        SEC               Subtract 100, 10, or 1
  2320        LDA DOS.ARITH.REG    from remainder
  2330        SBC DEC.CONVERSION.TABLE,Y    
  2340        PHA               save remainder on stack
  2350        LDA DOS.ARITH.REG+1  
  2360        SBC #0            (divisor high byte = 0)
  2370        BCC .3            ...far enough
  2380        STA DOS.ARITH.REG+1   Update remainder
  2390        PLA
  2400        STA DOS.ARITH.REG
  2410        PLA               get current digit
  2420        ADC #0            and count the subtraction
  2430        BNE .2            ...continue subtracting
  2440 .3     PLA               Discard stacked remainder byte
  2450        PLA               Get quotient digit
  2460        JSR MON.COUT      and print it!
  2470        DEY               Next divisor
  2480        BPL .1            ...not finished yet
  2490        JMP PRINT.SPACE   Trailing space
  2500 *--------------------------------
  2510 LINE.SKIP.FLAG .DA #0    LEAST SIGNIFICANT BIT IS FLAG
  2520 *--------------------------------
  2530        .OR $B3AF
  2540        .TF NEW CATALOG PART 2
  2550 *--------------------------------
  2560 *   OVERLAYS "DISK VOLUME " MESSAGE
  2570 *--------------------------------
  2580 LOCKED.FILE.CHECK
  2590        LDA #"*"
  2600        LDY FILE.TYPE,X   file type code.
  2610        BMI CAT.COUT      ...the file is locked
  2620 PRINT.SPACE
  2630        LDA #" "
  2640 CAT.COUT
  2650        JMP MON.COUT
  2660 *--------------------------------

80-Column Window Utility for //e and //cBill Reed
New Orleans, LA

I throughly enjoyed "Fast Text Windows" by Michael Ching. However, I prefer not to use the area at $800-BFF as a text buffer; I much prefer to use the first bank of the language card, which is not normally used by Applesoft programs running under DOS 3.3.

I modified Mike's program by changing the immediate values in lines 1560 and 1580 from #$0C to #$D4 and adding lines 1644, 1646 and 1905. The first two lines enable the bank of RAM to be read or written to. The last re-enables the Applesoft ROMS.

     1644       LDA $C08B
     1646       LDA $C08B
     1905       LDA $C082

I further modified the program to function in 80 columns on a //e or //c. The big problem was to mimic the text card, which uses bank switching to store adjacent characters in the same address, but different locations (main RAM and aux RAM). This was solved by using one buffer for the "even" characters and another for the "odd".

Additional code was required to determine the even/odd condition, so I (being lazy) removed the border portion of the program to conserve room. The border routines could certainly be retained if part of the program was also moved to bank one of the language card area. (Be careful if you try this, because you must avoid calling the monitor or Applesoft ROMs when the ROMs are switched off. You can possibly get away with calling the monitor with the ROMs switched off, but only if you first make a copy of the monitor in the F800-FFFF area of RAM.)

I moved the data storage to the zero page, mostly because it was available and slightly faster.

  1000 ; SAVES.WINDOWS.80
  1010 *-AAL APRIL 85 P 16-------------
  1020       .OR $2F5
  1030       .TF B.WINDOWS.80
  1040 *-------------------------------
  1050 TOP       .EQ $00  $0-$6 DATA
  1060 BOTTOM    .EQ $01  
  1070 LEFT      .EQ $02
  1080 RIGHT     .EQ $03
  1090 WIDTH     .EQ $04
  1100 LINE      .EQ $05
  1110 DIREC     .EQ $06
  1120 *
  1130 B1        .EQ $18,19   TEXT PNTR
  1140 B2        .EQ $1A,1B   BUFR PNTR
  1150 B3        .EQ $1C,1D   BUFR PNTR
  1160 WNDLFT    .EQ $20
  1170 WNDWDTH   .EQ $21
  1180 WNDTOP    .EQ $22
  1190 WNDBTM    .EQ $23
  1200 BASL      .EQ $28
  1210 BASH      .EQ $29
  1220 *-------------------------------
  1230 AMPERV    .EQ $3F5
  1240 PAG2OFF   .EQ $C054  READ MRBRD
  1250 PAG2ONN   .EQ $C055  READ AUXBRD
  1260 LCROM     .EQ $C082
  1270 LCRAM1    .EQ $C08B
  1280 GETBYTE   .EQ $E6F8
  1290 COMBYTE   .EQ $E74C
  1300 BASCALC   .EQ $FBC1
  1310 HOME      .EQ $FC58
  1320 *-------------------------------
  1330 SETUP
  1340       LDA #MOVE.WINDOW
  1350       STA AMPERV+1
  1360       LDA /MOVE.WINDOW
  1370       STA AMPERV+2
  1380       RTS
  1390 *-------------------------------
  1400 MOVE.WINDOW
  1410       JSR GETBYTE
  1420       STX TOP
  1430       STX LINE
  1440       JSR COMBYTE
  1450       STX BOTTOM
  1460       JSR COMBYTE
  1470       STX LEFT
  1480       JSR COMBYTE
  1490       STX RIGHT
  1500       INX
  1510       SEC
  1520       TXA
  1530       SBC LEFT
  1540       STA WIDTH
  1550       JSR COMBYTE
  1560       DEX
  1570       STX DIREC
  1580 *-------------------------------
  1590 *-------------------------------
  1600 MOVE.LINE
  1610       LDA LINE
  1620       JSR BASCALC
  1630       LDA BASH
  1640       STA B1+1
  1650       EOR #$D4
  1660       STA B2+1
  1670       CLC
  1680       ADC #$04      2ND BUFR
  1690       STA B3+1
  1700       LDA BASL
  1710       STA B1
  1720       STA B2
  1730       STA B3
  1740       LDA LCRAM1    ENABLE LANG
  1750       LDA LCRAM1    CARD R/W
  1760 *--MOVE THE LINE SEGMENT--------
  1770       LDA RIGHT
  1780       LSR           A/2 + EVN/ODD
  1790       TAY           TXT SCRN PNTR
  1800       LDX DIREC
  1810       BNE .3
  1820 *--MOVE IT UP-------------------
  1830       LDX WIDTH     DOWN COUNTER
  1840       BCC .2
  1850 .1    LDA (B1),Y    DO ODD COLS
  1860       STA (B2),Y
  1870       DEX
  1880       BMI .6 
  1890 .2    LDA PAG2ONN   DO EVN COLS
  1900       LDA (B1),Y
  1910       STA (B3),Y
  1920       LDA PAG2OFF
  1930       DEY
  1940       DEX
  1950       BPL .1
  1960       BMI .6 
  1970 *--MOVE IT DOWN-----------------
  1980 .3    LDX WIDTH
  1990       BCC .5
  2000 .4    LDA (B2),Y    DO ODD COLS
  2010       STA (B1),Y
  2020       DEX
  2030       BMI .6 
  2040 .5    LDA PAG2ONN   DO EVN COLS
  2050       LDA (B3),Y
  2060       STA (B1),Y
  2070       LDA PAG2OFF
  2080       DEY
  2090       DEX
  2100       BPL .4
  2110 *--NEXT LINE--------------------
  2120 .6    INC LINE
  2130       LDA LCROM     RESTORE ROM
  2140       LDA BOTTOM
  2150       CMP LINE
  2160       BCS MOVE.LINE
  2170 *--IF CLEARING, SET WINDOW------
  2180       LDA DIREC
  2190       BNE .7
  2200       LDX LEFT
  2210       STX WNDLFT
  2220       LDX WIDTH
  2230       DEX
  2240       STX WNDWDTH
  2250       LDX TOP
  2260       INX
  2270       STX WNDTOP
  2280       LDX BOTTOM
  2290       STX WNDBTM
  2300       JSR HOME
  2310 .7    RTS
  2320 *-------------------------------
  2330 ZZEND .EQ *

AUTO/MANUAL Toggle Update for
S-C Macro Assembler Version 2.0
Robert F. O'Brien
Dublin, Ireland

Here is a short routine (23 bytes) which makes use of the ESC-U command option to toggle the Auto-linenumbering mode on and off readily. The routine is relocatable so you can put it anywhere you have sufficient free space - just set the ESC-U vector to point to it, in this case :$C083 C083 D00C:4C 00 03 N C080.

When the cursor is waiting for input at the beginning of the command line, typing ESC-U will generate the command AUTO and then you have the option of entering a line number and/or RETURN. To cancel the AUTO mode just type ESC-U while the cursor is at the beginning of the line (just after the linenumber - 4 or 5 digit line numbers are catered for).

Extended AUTO command:

The second routine, starting at $317, is just 17 bytes long and extends the AUTO command so that you can specify the increment after the starting linenumber. For example, AUTO 3000,1 sets a starting line number of 3000 and an increment of 1. This code is also relocatable but you must patch the first instruction in the main AUTO command so that it uses the new code as a sub- routine. In this case it's :$C083 C083 D392:20 17 03 N C080.

The addresses specified for these new features are for the corrected version of the Assembler - i.e. serial nos. greater than 1251; see note in AAL March '85. Here is a table of what to expect at each of the addresses used, so you can find the equivalent spots in other copies of the assembler:

$D198 -- 20 xx D2      (JSR GNC)
         B0 17         (BCS to RTS)
         49 30         (EOR #$30)

$D392 -- 20 xx D1      (JSR GET.VALUE)
         CA            (DEX)
         30 0E         (BMI to SEC)

$D40B -- 41 55 54 CF   (.AT /AUTO/)
         xx D3         (.DA AUTO-1)

$DB9A -- 09 80         (ORA #$80)
         9D 00 02      (STA $0200,X)
         C9 A0         (CMP #$A0)

Note that with the Auto/Manual Toggle function installed you won't need the MANUAL command any more, so you have a spare command if you need it!

  1000 *SAVE S.AUTO/MAN
  1010 *--------------------------------
  1020        .OR $300
  1030 *      .TF AUTO/MAN TOGGLE
  1040 *--------------------------------
  1050 INCREMENT    .EQ $5A,5B
  1060 SYM.VALUE    .EQ $B8,B9
  1070 AUTO.FLAG    .EQ $E3
  1080  
  1090 WARM.START   .EQ $D028
  1100 GET.VALUE    .EQ $D198
  1110 AUTO.CMD     .EQ $D40B
  1120 INSTALL.CHAR .EQ $DB9A
  1130 *--------------------------------
  1140 AUTO.MAN.CODE
  1150        TXA               check cursor posn.
  1160        BEQ .1            OK to output cmd.
  1170        CPX #7            line start?
  1180        BGE .2            ignore ESC-U.
  1190        LSR AUTO.FLAG     cancel auto-mode.
  1200        JMP WARM.START
  1210  
  1220 .1     LDA AUTO.CMD,X    output cmd. name
  1230        JSR INSTALL.CHAR  put in buffer+scrn.
  1240        CPX #4            4 chars. output?
  1250        BNE .1            no.
  1260 .2     RTS               exit ESC-U routine
  1270 *-----------------------------
  1280 *  Point start of AUTO cmd. handler
  1290 *  to here for extended function.
  1300 *--------------------------------
  1310 NEW.AUTO.EXT
  1320        JSR GET.VALUE     get linenum if any.
  1330        JSR GET.VALUE     get inc. if any.
  1340        CPX #3            increment?
  1350        BLT .1            no
  1360        DEX               adjust for inc.
  1370        DEX                   do.
  1380        LDA SYM.VALUE     set inc. low byte
  1390        STA INCREMENT
  1400 *  (following 2 lines only needed
  1410 *   if you use increments of 255+!)
  1420 *      LDA SYM.VALUE+1   set inc. high byte
  1430 *      STA INCREMENT+1
  1440 .1     RTS               finish AUTO cmd.

Apple ProDOS:
Advanced Features for Programmers
a Review
Bill Morgan

Gary Little, the prolific author of Inside the Apple //e and Inside the Apple //c, has yet another new book out. This one is called Apple ProDOS: Advanced Features for Programmers. In this volume Little covers just about all you need to know to write assembly language programs under ProDOS, from simply passing commands to BASIC.SYSTEM, through great detail on all the MLI calls, to writing your own interrupt handlers and device drivers.

Here's a quick summary of the book's contents:

  1. An Introduction to ProDOS -- Little starts out with the history of Apple's DOS's, a comparison of ProDOS and DOS 3.3, and a summary of important features of ProDOS.
  2. Files and File Management -- Here he covers the directory structures, file structures, disk formatting, and gives us a READ.BLOCK program.
  3. Loading and Installing ProDOS -- This chapter goes into the boot process, ProDOS' memory usage, and the Global Page.
  4. The Machine Language Interface -- This is the information on using the MLI, its error codes, and complete details of all MLI calls.
  5. System Programming Featuring BASIC.SYSTEM -- Here we have a discussion of system programs, the structure and commands of BASIC.SYSTEM, and assembly language programming under BASIC.SYSTEM.
  6. Interrupts -- In this chapter Little covers interrupts in general, ProDOS interrupt handling, and programming the Apple mouse.
  7. Disk Drivers -- Nearing the end, we go into identifying and handling foreign disk drivers, driver commands, the /RAM driver, and adding your own driver.
  8. ProDOS Clock Drivers -- And finally we find out about using the built-in clock support, adding a clock driver, and reading the date and time from Applesoft.

An important strength of this book is the wealth of examples. In the chapter on the Machine Language Interface there is an example of the correct use of EVERY MLI call. The BASIC.SYSTEM chapter includes an ONLINE command, to identify all disk volumes currently on line. The chapter on interrupts contains a couple of examples of mouse programming. The Disk driver section has a listing of a simple /RAM driver for main memory. And this is just a sample of the useful code provided in Little's new book. A companion disk containing all of the book's programs and more is available for $25.00 from the author.

I hear some of you asking: How does Apple ProDOS: Advanced Features (APAF) compare to Beneath Apple ProDOS (BAP)? Well, the two books complement each other quite nicely. With all its examples, treatment of interrupt handlers and device drivers, and overall clarity, I'd say that APAF is the better book on programming under ProDOS. BAP has useful examples as well, and better detail about the internals of diskette formatting and how ProDOS works, especially with its 120+ page supplement describing the code on a line-by-line basis. So if you're concerned with understanding the inner workings of the operating system, or with modifying its behavior, BAP is the book to have. Otherwise, get APAF for the best information on programming using ProDOS. Personally, I'm glad to have both books on the shelf here, along with Apple's ProDOS Technical Reference Manual.

Apple ProDOS: Advanced Features for Programmers, by Gary B. Little. Brady Communications Co., 1985. 266+iv pp., Reference Card. $17.95. Available from S-C Software for $17 + shipping.


Adapting the Output Format of Rak-Ware DISASMBob Kovacs

This technical note describes the format table used within DISASM 2.2e, which can be modified to adapt the output text file format to other assemblers. Even if you never plan to modify DISASM, or even if you don't own a copy of DISASM, you can learn a lot about the use of configuration tables by studying what follows.

The current version of the disassembler provides three different output formats to support the DOS ToolKit, S-C, and LISA assemblers. The format table contains various attributes which are unique to each assembler. The table begins at location $1331 and is $3F bytes long. Let's first examine the table and then determine how to adapt it to other assembler formats.

 Item      ToolKit      S-C          LISA
---------  -----------  -----------  -----------

comment AA * AA * BB ; firstchr 00 none 89 ^I 00 none tabchr1 A0 spc 89 ^I A0 spc tabchr2 A0 spc A0 spc A0 spc opchr C1 A 00 none 00 none pgzchr C5D1D5 EQU AEC5D1 .EQ C5D0DA EPZ extchr C5D1D5 EQU AEC5D1 .EQ C5D1D5 EQU hexchr C4C6C2 DFB AEC8D3 .HS C8C5D8 HEX orgchr CFD2C7 ORG AECFD2 .OR CFD2C7 ORG prechr AA0000 * 000000 none C9CED3 INS postchr 00 none 98 ^X 85 ^E

comment: the character used at the beginning of a line to signify a comment line.

firstchr: the character ouput at the beginning of each line.

tabchr1: the character used to tab to the opcode field.

tabchr2: the character used to tab to the operand field.

opchr: operand for impled accumulator instructions (ASL, LSR, ROR, ROL).

pgzchr: directive for page zero declarations.

extchr: directive for absolute declarations.

hexchr: directive for data tables.

orgchr: directive for setting the program origin.

prechr: preamble sequence for initialization of the assembler.

postchr: postamble character for termination of the assembler's loading operation.

You will find that it is relatively simple to modify the format table for other assemblers. First, determine which of the three existing formats is to be overwritten (just pick the one you think you'll need the least). Then determine the format data which is appropriate to your assembler. BLOAD DISASM, enter the monitor, and stuff the new values into the table. Finally BSAVE DISASM,A$800,L$D00.

Or, if you have purchased the source code of DISASM 2.2e (or created your own using DISASM!), you can merely edit the table with your assembler and re-assemble the program.

You might also need or want to change some other paramteters, which are not in the format table:

Label Prefix: located at $132E, the current value is C9DAD8 (the letters "IZX"). These letters are used to indicate internal, pagezero, and external labels in the generated text file.

Menu Table: located at $1300, this table contains the names of the three assemblers listed in the first menu. Each name is stored in ASCII, followed by a return ($0D) and a terminator ($00).

Label Name Separator: A period ($AE) is output as the second character in every generated label name. This can be changed to any other character by editing the LDA #$AE instruction at location $0EA4.

I would be interested in hearing from any of you who have already modified DISASM. This kind of feedback can lead to new versions with even more powerful features.


DATE Command for ProDOSBill Morgan

One of the nice new features in ProDOS is the way the diskette catalog shows the date of creation and last modification for each file, IF you have a clock/calendar card installed in your Apple. Well I don't have such a card in either of the Apples I use regularly, at work or at home. And no //c has a clock! (Yet, at least. I'll bet someone will come up with a way...)

Anyway, I got tired of always seeing <NO DATE> and started figuring out how to set a date without a clock to do it for me. A look at Beneath Apple ProDOS informed me that the current date is transformed into the format YYYYYYYMMMMDDDDD and stored (in the usual 6502 low byte/high byte sequence) at $BF90-BF91 in the ProDOS Global Pages (the fixed locations of all of the accessible system variables). The first thing I did was manually convert the current date into that format and poke it in from the Monitor. That went like this:

                         $BF90      $BF91
May =  $5 = 0101       MMM DDDDD  YYYYYYY M
 10 =  $A = 01010      101 01010  1010101 0
'85 = $55 = 1010101       $AA        $AA

So, the values to poke into $BF90-91 were $AA and $AA. What better time than a four-A day to start such a project!

That experiment worked just fine: the next file I saved on the disk showed creation and modification dates of 10-MAY-85, just as I had hoped. With that success under my belt the next step had to be to come up with a program to read and/or set those date bytes. And, while I'm at it, why not take advantage of ProDOS' built-in hooks for installing new commands and add a DATE command to the operating system?

How do I go about adding a command? The ProDOS Technical Reference Manual is pretty sketchy on the subject, but two other books, Beneath Apple ProDOS and the new Apple ProDOS: Advanced Features for Programmers, have good descriptions and examples of the procedure. If you're going to do much assembly language programming under ProDOS you should have one or both of those books.

When ProDOS fails to recognize a command it does a JSR EXTRNCMD ($BE06) to find out if an external command processor will claim this one. What I have to do is install the address of DATE in $BE07-08, after moving the address that was already there into a JMP instruction. This way, if DATE doesn't recognize the command it can pass it along to any other processor that might have been there before.

Processing of an external command is normally divided into two phases, a parser and a handler. The parser section will scan the command name at the beginning of the line. If the command is not recognized, the parser should set the carry bit and JMP on to the address found in EXTRNCMD to see if another external processor will claim it.

If the command is recognized, the parser can set certain bits in PBITS ($BE54-55) to signify which parameters are permitted or required on the command line, and store the address of the handler in EXTRNADDR ($BE50-51). After storing the command length minus one in XLEN ($BE52) and a zero in XCNUM ($BE53), to signify that an external processor did claim the command, the parser then returns control to ProDOS to scan the rest of the line. If the line was syntactically correct, ProDOS will return the values of the parameters in a set of standard locations ($BE58-6F) and pass control back to the handler address specified.

Since DATE is a simple processor that uses a nonstandard parameter, I just set PBITS to zero, to indicate no parsing necessary, and store the address of an RTS instruction in EXTRNADDR. I then proceed to do all my processing before returning to ProDOS.

There is one additional wrinkle to using an external command with ProDOS: where do I put my code so ProDOS, Applesoft, and others don't stomp all over it? In the interest of simplicity I have ignored that problem here. The best procedure, as shown in the books mentioned above, is to call ProDOS to assign me a buffer and then relocate my code into that buffer. The examples in the books provide details of this process.

Now, let's take a look at the code:

Lines 1310-1400 install DATE by moving the current External Command address to my exit JMP instruction and storing DATE's address in the vector.

Lines 1440-1540 check the input buffer to see if this is a DATE command. If not we branch on down to that JMP instruction where we earlier put the address found in the External Command vector. This passes control either on to the next external command in the chain, or back to ProDOS for a SYNTAX ERROR.

If the command matched we go on to lines 1560-1650 to do the necessary housekeeping. This involves storing the command length-1 in the Global Page, setting a couple of flags to tell ProDOS not to parse the rest of the command line, and that an external command has taken over. Then we supply a handler address for the second half of ProDOS' processing, which in this case is just an RTS instruction. Finally we reach lines 1670-1690, where we check to see if the character following DATE is a Carriage Return. If so we branch forward to RETURN.DATE to display the existing date.

If there is more than just DATE on the command line, we must want to set a new date, so we fall into SET.DATE at line 1710. This routine makes heavy use of ACCUMULATE.DIGITS at line 2400, so we'll examine that code first. The first step is to zero the byte where we'll be accumulating the value typed in. Then we scan forward in the input buffer, looking for a nonblank character. When we find one we first check to see if it is a slash, which marks the end of a number, or a Carriage Return, which marks the end of the line. If it was either of those we exit, setting the Carry bit to indicate which one we found.

If the character found was not a delimiter we next check to see if it is a number. If not, we have a SYNTAX ERROR. When we do get a number, we strip off the high bits to convert the ASCII code to a binary value, and save that value. We then multiply the previous value in ACCUM by 10 and add in the new value. Then it's back to line 2440 to get another character. Lines 2710-2730 load the A-register with the value found and branch to the error exit if that value was zero.

Now, back to SET.DATE. That routine begins at line 1720 with a DEY to get ready for the INY at the beginning of ACCUMULATE.DIGITS. We then get the month, check for a legal value, and store it. Next we get the day, save the status, and check and save that value. Then it's time to check the status to see if the day was followed by a slash, or by a Carriage Return. If it was a slash then a year was specified, so we go get that value. If it was a Return no year was present, so we use 1985. (I guess that means we'll have to reassemble or patch this program every year. I think I can handle that.)

The last step in SET.DATE is to fold the year, month, and day together as described above and store the results in the Global Page. The comments in the listing illustrate how the bits are shuffled around to the correct format. After setting the date we fall into RETURN.DATE to display the result.

RETURN.DATE, at lines 2080-2290, is quite straightforward. It just gets the bytes from the Global Page, unfolds them, and calls DEC.OUT to translate them to decimal numbers and display those numbers. Again, the comments illustrate the bit manipulations involved in the unfolding process.

The final section of code is DEC.OUT, at lines 2750-2910. In lines 2760-2810 we use the Y-register to count how many times we can subtract 10 from the number passed in the A-register. Then lines 2830-2910 restore and save the A-register, make sure the tens count is non-zero, convert it to a character and print it. We then recover the units value and print that out.

  1000 *SAVE S.DATE
  1010 *--------------------------------
  1020 *
  1030 *      Program to read or set the
  1040 *      date bytes in the Global Page
  1050 *
  1060 *            by Bill Morgan
  1070 *
  1080 *--------------------------------
  1090 POINTER    .EQ $40,41
  1100 ACCUM      .EQ $42 
  1110 MONTH      .EQ $43    
  1120 DAY        .EQ $44
  1130 TEMP       .EQ $45
  1140  
  1150 WBUF       .EQ $200
  1160  
  1170 EXTRNCMD   .EQ $BE07
  1180 EXTRNADDR  .EQ $BE50,51
  1190 XLEN       .EQ $BE52
  1200 XCNUM      .EQ $BE53
  1210 PBITS      .EQ $BE54
  1220 GP.DATE    .EQ $BF90
  1230  
  1240 PRAX       .EQ $F941
  1250 CROUT      .EQ $FD8E
  1260 COUT       .EQ $FDED
  1270 *--------------------------------
  1280        .OR $803
  1290 *      .TF B.DATE
  1300 *--------------------------------
  1310 INSTALL
  1320        LDA EXTRNCMD+1    exit to old
  1330        STA EXIT+2        user command
  1340        LDA EXTRNCMD
  1350        STA EXIT+1
  1360        LDA /DATE         become new
  1370        STA EXTRNCMD+1    user command
  1380        LDA #DATE
  1390        STA EXTRNCMD
  1400        RTS
  1410 *--------------------------------
  1420 COMMAND .AS /DATE/
  1430 *--------------------------------
  1440 DATE   LDY #0
  1450        STY POINTER       point to input buffer
  1460        LDA /WBUF
  1470        STA POINTER+1
  1480 .1     LDA (POINTER),Y   scan command
  1490        AND #%01111111
  1500        CMP COMMAND,Y
  1510        BNE ERR.BRIDGE    not mine
  1520        INY
  1530        CPY #4
  1540        BCC .1
  1550 *--- ProDOS bookkeeping ---------
  1560        DEY
  1570        STY XLEN          command length - 1
  1580        INY
  1590        LDA #0
  1600        STA PBITS         don't parse parms
  1610        STA XCNUM         external command
  1620        LDA #RTS1
  1630        STA EXTRNADDR     no execution after
  1640        LDA /RTS1         command parsing
  1650        STA EXTRNADDR+1
  1660 *--- set or display date? -------
  1670        LDA (POINTER),Y
  1680        CMP #$8D          DATE only?
  1690        BEQ RETURN.DATE   yes, return old date
  1700 *--------------------------------
  1710 SET.DATE
  1720        DEY
  1730        JSR ACCUMULATE.DIGITS  get month
  1740        CMP #13
  1750        BCS ERROR         >12 no good
  1760        STA MONTH
  1770        JSR ACCUMULATE.DIGITS  get day
  1780        PHP               save status
  1790        CMP #32
  1800        BCC GO.ON         <=31 ok
  1810  
  1820        PLP
  1830 ERR.BRIDGE
  1840        BNE ERROR      ...always
  1850  
  1860 GO.ON  STA DAY
  1870        PLP               recover status
  1880        BCC .1            .CC. if "/"
  1890        LDA #85           year defaults to '85
  1900        BNE .2         ...always
  1910 .1     JSR ACCUMULATE.DIGITS  get year
  1920        CMP #100
  1930        BCS ERROR         >99 no good
  1940 .2     PHA               save year
  1950        LDA MONTH         X 0000MMMM
  1960        LSR               M 00000MMM
  1970        ROR               M M00000MM
  1980        ROR               M MM00000M
  1990        ROR               M MMM00000
  2000        STA MONTH
  2010        PLA               M 0YYYYYYY
  2020        ROL               0 YYYYYYYM
  2030        STA GP.DATE+1
  2040        LDA MONTH           MMM00000
  2050        ORA DAY             MMMDDDDD
  2060        STA GP.DATE
  2070 *--------------------------------
  2080 RETURN.DATE
  2090        JSR CROUT
  2100        LDA GP.DATE+1     X YYYYYYYM
  2110        LSR               M 0YYYYYYY
  2120        PHA
  2130        LDA GP.DATE       M MMMDDDDD
  2140        PHA
  2150        ROR               X MMMMDDDD
  2160        LSR               X 0MMMMDDD
  2170        LSR               X 00MMMMDD
  2180        LSR               X 000MMMMD
  2190        LSR               X 0000MMMM
  2200        JSR DEC.OUT       display month
  2210        LDA #"/"          /
  2220        JSR COUT
  2230        PLA               X MMMDDDDD
  2240        AND #%00011111    X 000DDDDD
  2250        JSR DEC.OUT       display day
  2260        LDA #"/"          /
  2270        JSR COUT
  2280        PLA               X 0YYYYYYY
  2290        JSR DEC.OUT       display year
  2300 *--------------------------------
  2310 GOOD.EXIT
  2320        CLC               signal no error
  2330 RTS1   RTS
  2340 *--------------------------------
  2350 ERROR1 PLA               clean up
  2360        PLA               return addresses
  2370 ERROR  SEC               signal error
  2380 EXIT   JMP RTS1          INSTALL makes address
  2390 *--------------------------------
  2400 ACCUMULATE.DIGITS
  2410        LDA #0
  2420        STA ACCUM         zero accumulator
  2430  
  2440 .1     INY               next character
  2450        LDA (POINTER),Y
  2460        AND #%01111111    hi-bit off
  2470        CMP #' '          space?
  2480        BEQ .1            back for another
  2490        CMP #'/'          slash?
  2500        BEQ .2            yes, exit .CC.
  2510        CMP #$0D          <CR>?
  2520        BEQ .3            yes, exit .CS.
  2530        CMP #'0'          too small?
  2540        BCC ERROR1        not digit
  2550        CMP #'9'+1        too big?
  2560        BCS ERROR1        not digit
  2570  
  2580        AND #%00001111    isolate value
  2590        STA TEMP          stash it
  2600        LDA ACCUM
  2610        ASL               X 2
  2620        ASL               X 4
  2630        ADC ACCUM         X 5
  2640        ASL               X 10
  2650        ADC TEMP          add new digit
  2660        BCS ERROR1        too big
  2670        STA ACCUM
  2680        BCC .1         ...always
  2690  
  2700 .2     CLC               .CC. if /
  2710 .3     LDA ACCUM         return value
  2720        BEQ ERROR1        0 no good
  2730        RTS
  2740 *--------------------------------
  2750 DEC.OUT
  2760        LDY #0            zero counter
  2770        SEC               get ready
  2780 .1     SBC #10           subtract 10
  2790        BCC .2            borrow?
  2800        INY               count a 10
  2810        BPL .1         ...always
  2820  
  2830 .2     ADC #10           restore borrow
  2840        PHA               save units
  2850        TYA               print 10's count
  2860        BEQ .3            no leading zero
  2870        ORA #$B0          make character
  2880        JSR COUT          print it
  2890 .3     PLA               recover units
  2900        ORA #$B0          make character
  2910        JMP COUT          return through COUT

32-bit Values in Version 2.0
-- A Mixed Blessing
Bob Sander-Cederlof

In previous versions of the S-C assemblers, expressions were evaluated in 16 bits, and symbol values were kept in the table in 16-bit form. Version 2.0 works with 32-bit expressions and symbol values. We added this feature for your benefit, but it may sometimes be a mixed blessing.

For example, Bob Bernard had a problem with a program which assembled perfectly under Version 1.0, but gave countless BAD ADDRESS errors in version 2.0. We traced the problem to his origin statement, which was ".OR -31488". In older versions, -31488 is the same as $8500, but in version 2.0 it is $FFFF8500. The following code will not assemble:

       .OR -31488
  SSS  JMP SSS

Why? Because the value of SSS is also $FFFF8500, and it will not fit in a JMP instruction. In 65816 mode, using a JML instruction, it would be legal.

Two ways to fix come to mind. You normally work in hexadecimal when you are in assembly language, rather than decimal. Therefore, change the origin statement to ".OR $8500". Or, if you really want to use decimal, write ".OR 65536-31488".

Another owner of version 2.0 had a problem with a program that used many macros, and lots of private labels. Private labels are the ones used inside macro definitions, which are written with a colon and a one or two digit number. The private label table normally begins at $FFF and grows downward toward $800. His program assembled with no problems before, but under version 2.0 it got a MEM FULL error. Reason, again, the 32-bit symbol values. Each entry in the private label table now takes two more bytes, so he ran out of space sooner. His solution was to move the beginning of the label table higher.


Apple Assembly Line 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.)