Apple Assembly Line
Volume 7 -- Issue 5 February 1987

In This Issue...

Apple IIgs Technical Reference, by Michael Fischer

Hot off the press, over two pounds and nearly 700 pages of useful reference material for your IIgs. As far as I know, this is the first book to reveal the NUMBERS you need to use the tools in the toolbox. Published by Osborne/McGraw-Hill at $19.95, our price will be an even $19.

You know Michael Fischer as the author of 65816/65802 Assembly Language Programming, same publishers. That book is apparently doing well, because McGraw-Hill just raised the price from $18.95 to $21.95. Our price went up a little too....

Product Information

Look inside for more detail on two more exciting new products: ProVIEW, by Doug McComsey is a professional tool for zapping your ProDOS disks; the new ProDOS-based S-C Disassembler fills the need for a fast, powerful, and flexible intelligent disassembler operating under ProDOS together with the S-C Macro Assembler.

For years we have been selling the little book by Caxton Foster, "Real Time Programming". It was a little gem, but we cannot get them anymore. The publisher told us they have no schedule for reprinting it. We also miss Foster's excellent book on cryptograms & computers. Both books used the Apple for examples.


Pre-Allocating DOS Files Bill Morgan

Lately I've been working on a project that requires saving large files to disk as fast as possible. The information arrives in 256-byte chunks, with brief pauses between the chunks, so it's not too tough to write to a hard disk during the pauses. I found that things worked fine while saving to an existing file, but when I had to create a new file I got into trouble with the time DOS wastes allocating sectors one by one and constantly reading and re-writing the VTOC. Time for some speedup tricks!

I remembered that the S-C Word Processor uses a special technique to save its files, calling DOS routines to allocate enough sectors and create a track/sector list before actually writing the file. This approach requires that we know how big the file is before opening it, but that's OK because I do have that information. Another problem quickly showed up: the Word Processor knows that its files cannot be bigger than 120 sectors, and therefore will need only one sector's worth of t/s list. My files can be several hundred sectors long, so I have to be ready for anything.

The following machine language routines, along with the Applesoft driver, will create the track/sector lists for a file of any length.

This approach to creating a file does require some non-standard use of DOS routines, so a little patching is necessary. We will be using the File Manager's ALLOCATE.SECTOR routine, which does all that extra VTOC handling. Since we're going to repeatedly call that routine until we're done, it's a waste of time to shuffle the VTOC back and forth. Patching an RTS in at $AFFD stops the extra VTOC action. Since we're sort of sneaking into the File Manager's territory, we can't rely on the normal error handling. The patch at $B392 grabs control in the event of an error, so we can remove our patches and handle the error our way.

These programs will almost certainly fail under a modified DOS, so compare the code at the patch locations to standard DOS before trying it out.

Note: Some of the techniques here look odd even to me, but the original program had only a limited space for machine language routines and lots of room for a compiled Applesoft program. That's why, for example, the catalog is updated and rewritten from BASIC in lines 1150-1190, rather than from the machine language.

The Applesoft code is really pretty simple. We issue a DOS UNLOCK command to get the catalog sector containing our file into memory. (If the file doesn't already exist, then the ONERR code at line 10000 creates it.) Once we have the catalog information we can call the machine language routines to 'open' the file by locating the first track/sector list and then allocate the file. Then it's just a matter of looping along collecting data and writing it out to the next sector in the list. 'Closing' the file just means updating the catalog sector with the final length and the correct file type. The routine beginning at line 20000 is just a dummy to come up with something to put in the file.

Lines 1420-1460 provide fixed addresses for BASIC to call the various routines. At line 1520, OPEN.USER.FILE uses the DOS CATALOG.INDEX and the catalog sector in memory to look up the location of the first track/sector list, falling into READ.TS.LIST to get that into memory.

ALLOCATE.FILE, beginning at line 1790, is the heart of this program. We start out by remembering the location of the current t/s list and reading the VTOC. We then install our quick patches into DOS. After initializing the offset, program length (1 because of the first t/s list sector) and index, we're ready to start allocating.

We call NEXT.TS.ENTRY to see if there is already another sector allocated. If so it's on to lines 2060-2140 to count that sector and see if we're done. If NEXT.TS.ENTRY returns with .EQ. status then we need another sector and must call ALLOCATE.SECTOR to get it. That routine returns the sector number in the A-register, so we store that and get the track number from the FM's variable at $B5F1. We need to decrement the Y-register because NEXT.TS.ENTRY has already moved TS.INDEX to the next position. Once we have the new sector we count it and, if we're not done, go back for more.

Once we have all the sectors we need, we just call the DOS routine to free up the reserved but unused sectors in the current track, clean up our patches, and finally write out the revised VTOC. Then we write out the current t/s list and we're finished.

READ and WRITE.NEXT.SECTOR just set up the IOB, call NEXT.TS.ENTRY to get a sector location, and call RWTS. If an error occurs it is passed back to BASIC for handling. If NEXT.TS.ENTRY return .EQ. status here, then we have hit the end of the file. This feature isn't used in this demo, but is necessary when we use the same techniques to read a file.

NEXT.TS.ENTRY, at line 2550, starts out by getting the current index into the list. Normally that index is greater than zero, so the routine bumps the index for the next call and returns the next sector and track in Y and A. If the index was zero, then we're finished with that sector of the list and go on to line 2640 to see if there is another sector. If there is another sector then we pick up its location, remember whether we're in the middle of reading or writing, and go get the next piece of the list. Then it's back to the top to get the next entry. Note that READ.TS.LIST resets TS.INDEX to the beginning.

If there isn't another sector we drop down to line 2760. If we're not in the middle of allocating then this means we're at the end of the file, so we return with a zero in the A-register. When we are allocating it means we need to make another t/s list sector. To do this we first call ALLOCATE.SECTOR to find a place for it, then set the pointer bytes in the current sector, recover the current sector's location, and write it out. Then we remember where this sector is going to go, zero the buffer, update the offset, and count the new sector.

FILE.MANAGER.ERROR just cleans out the DOS patches and passes the error code back to the Applesoft program. REMOVE and INSTALL.DOS.PATCHES very straightforwardly do just that.

Much of this code is adapted from the S-C Word Processor's method of reading and writing files. My thanks to Bob S-C and Bobby Deen for the inspiration.

     10  HIMEM: 37888
     20 D$ =  CHR$ (4)
     30  PRINT D$"BLOAD B.PREALLOCATE,A$9400"
     40 PA = 37888
     100 TYPE = 0: REM  Change for Non-Text File
     110 EC = 3: POKE EC,0: REM Error Code
     120 LN = 3: REM File Length, in Sectors
     130  POKE 0,LN - LN / 256: POKE 1,LN / 256
     140 F$ = "FILENAME"
     1000  ONERR  GOTO 10000
     1010  PRINT D$"UNLOCK"F$
     1020  ONERR  GOTO 11010
     1030  CALL PA: REM Locate File
     1040  IF  PEEK (EC) THEN 11000
     1050  CALL PA + 3: REM Allocate File
     1060  IF  PEEK (EC) THEN 11000
     1070  PRINT D$"UNLOCK"F$
     1080  CALL PA: REM Locate File
     1090  IF  PEEK (EC) THEN 11000
     1100  FOR I = 1 TO LN
     1110  GOSUB 20000: REM Get a Sector
     1120  CALL PA + 9: REM Write a Sector
     1130  IF  PEEK (EC) THEN 11000
     1140  NEXT 
     1150  PRINT D$"UNLOCK"F$
     1160 CL =  PEEK (45980) + 46311
     1170  POKE CL, PEEK (4): POKE CL + 1, PEEK (5): REM Catalog File Length
     1180  POKE  PEEK (45980) + 46280,TYPE: REM If New, Non-Text File
     1190  CALL PA + 12: REM Rewrite Catalog Sector
     1200  IF  PEEK (EC) THEN 11000
     1210  END 
     10000  POKE 216,0
     10010 ER =  PEEK (222): IF ER <  > 6 THEN 11020
     10020  PRINT D$"OPEN"F$: PRINT D$"CLOSE"
     10030  GOTO 1020
     11000 ER =  PEEK (EC): GOTO 11020
     11010 ER =  PEEK (222)
     11020  PRINT "ERROR #"ER
     11030  POKE 216,0
     12000  END 
     20000  FOR Z = 0 TO 255
     20010  POKE 39590,A + 193
     20020  NEXT 
     20030 A = A + 1
     20040  RETURN 
  1000 *SAVE S.PREALLOCATE
  1010 *--------------------------------
  1020 COUNT          .EQ 0,1
  1030 EOF.FLAG       .EQ 2
  1040 ERROR.FLAG     .EQ 3
  1050 PGM.LENGTH     .EQ 4,5
  1060 TS.INDEX       .EQ 6
  1070 TS.OFFSET      .EQ 7,8
  1080 THIS.TS.TRACK  .EQ 9
  1090 THIS.TS.SECTOR .EQ $A
  1100 ALLOCATE.FLAG  .EQ $B
  1110  
  1120 CALL.RWTS  .EQ $3D9
  1130 GET.IOB    .EQ $3E3
  1140  
  1150 DATA.BUFFER .EQ $9AA6
  1160 TS.BUFFER   .EQ $9BA6
  1170  
  1180 DOS.READ.VTOC   .EQ $AFF7
  1190 DOS.WRITE.VTOC  .EQ $AFFB
  1200 ALLOCATE.SECTOR .EQ $B244
  1210 RELEASE.SECTORS .EQ $B2C3
  1220 CATALOG.INDEX   .EQ $B39C
  1230 CAT.TRACK       .EQ $B4C6 (+ Index)
  1240 CAT.SECTOR      .EQ $B4C7 (   "   )
  1250 FM.ERROR        .EQ $B5C5
  1260 CURRENT.TRACK   .EQ $B5F1
  1270  
  1280 IOB        .EQ $B7E8
  1290 IOB.VOLUME .EQ IOB+3
  1300 IOB.TRACK  .EQ IOB+4
  1310 IOB.SECTOR .EQ IOB+5
  1320 IOB.BUFFER .EQ IOB+8,9
  1330 IOB.OPCODE .EQ IOB+12
  1340 *--------------------------------
  1350        .OP 65C02
  1360        .OR $9400
  1370        .TF B.PREALLOCATE
  1380 *--------------------------------
  1390 *
  1400 *      CALLS FROM BASIC
  1410 *
  1420 CALL.PA    JMP OPEN.USER.FILE
  1430 CALL.PA.3  JMP ALLOCATE.FILE
  1440 CALL.PA.6  JMP READ.NEXT.SECTOR
  1450 CALL.PA.9  JMP WRITE.NEXT.SECTOR
  1460 CALL.PA.12 JMP RWTS.CALLER
  1470 *--------------------------------
  1480 *
  1490 *      GET A FILE READY
  1500 *
  1510 OPEN.USER.FILE
  1520        LDX CATALOG.INDEX
  1530        LDA CAT.TRACK,X   t/s list track
  1540        STA IOB.TRACK
  1550        LDA CAT.SECTOR,X   & sector
  1560        STA IOB.SECTOR
  1570  
  1580 READ.TS.LIST
  1590        LDA #1            read opcode
  1600        .HS 2C            skip 2 bytes
  1610 WRITE.TS.LIST
  1620        LDA #2            write opcode
  1630        STA IOB.OPCODE
  1640        LDA #TS.BUFFER    set buffer
  1650        STA IOB.BUFFER
  1660        LDA /TS.BUFFER
  1670        STA IOB.BUFFER+1
  1680        JSR RWTS.CALLER
  1690        LDA #$C           point to first t/s pair
  1700        STA TS.INDEX
  1710        LDA /DATA.BUFFER  restore buffer
  1720        STA IOB.BUFFER+1
  1730        STZ EOF.FLAG
  1740        RTS
  1750 *--------------------------------
  1760 *
  1770 *      PRE-ALLOCATE SECTORS
  1780 *
  1790 ALLOCATE.FILE
  1800        LDX CATALOG.INDEX
  1810        LDA CAT.TRACK,X   remember where
  1820        STA THIS.TS.TRACK we're starting
  1830        LDA CAT.SECTOR,X
  1840        STA THIS.TS.SECTOR
  1850        JSR DOS.READ.VTOC
  1860        JSR INSTALL.DOS.PATCHES
  1870        STZ TS.OFFSET
  1880        STZ TS.OFFSET+1
  1890        LDA #1
  1900        STA PGM.LENGTH    count first t/s list
  1910        STZ PGM.LENGTH+1
  1920        LDA #$C           start of t/s pairs
  1930        STA TS.INDEX
  1940        STA ALLOCATE.FLAG make non-zero
  1950  
  1960 .1     JSR NEXT.TS.ENTRY (bumps TS.INDEX)
  1970        BNE .2            .NE. if one already present
  1980        JSR ALLOCATE.SECTOR
  1990        LDY TS.INDEX
  2000        DEY
  2010        STA TS.BUFFER,Y   set sector
  2020        LDA CURRENT.TRACK         
  2030        DEY
  2040        STA TS.BUFFER,Y   and track
  2050  
  2060 .2     INC PGM.LENGTH    count it
  2070        BNE .3
  2080        INC PGM.LENGTH+1
  2090 .3     DEC COUNT
  2100        BNE .1            back for more
  2110        LDA COUNT+1       are we done?
  2120        BEQ .4
  2130        DEC COUNT+1       no, keep going
  2140        BRA .1
  2150  
  2160 .4     JSR RELEASE.SECTORS    rest of track
  2170        JSR REMOVE.DOS.PATCHES clean house
  2180        JSR DOS.WRITE.VTOC     update VTOC
  2190        STZ ALLOCATE.FLAG      done allocating
  2200        LDA THIS.TS.TRACK
  2210        STA IOB.TRACK
  2220        LDA THIS.TS.SECTOR
  2230        STA IOB.SECTOR
  2240        JMP WRITE.TS.LIST      write this list
  2250 *--------------------------------
  2260 *
  2270 *      READ OR WRITE A SECTOR
  2280 *
  2290 READ.NEXT.SECTOR
  2300        LDA #1            read 
  2310        .HS 2C            skip 2
  2320 WRITE.NEXT.SECTOR
  2330        LDA #2            write
  2340        STA IOB.OPCODE
  2350        CLC
  2360        JSR NEXT.TS.ENTRY get track/sector
  2370        BNE .1            .EQ. means EOF
  2380        INC EOF.FLAG      raise the flag
  2390        RTS               and quit
  2400  
  2410 .1     STA IOB.TRACK
  2420        STY IOB.SECTOR
  2430  
  2440 RWTS.CALLER
  2450        STZ IOB.VOLUME
  2460        JSR GET.IOB
  2470        JSR CALL.RWTS
  2480        BCC .1            .CS. if error
  2490        STA ERROR.FLAG    pass code back
  2500 .1     RTS
  2510 *--------------------------------
  2520 *
  2530 *      RETURN NEXT TRACK/SECTOR
  2540 *
  2550 NEXT.TS.ENTRY
  2560        LDX TS.INDEX
  2570        BEQ .2            done with this list
  2580        INC TS.INDEX      bump index
  2590        INC TS.INDEX      for next time
  2600        LDY TS.BUFFER+1,X
  2610        LDA TS.BUFFER,X   return .EQ. if eof
  2620 .1     RTS
  2630  
  2640 .2     LDA TS.BUFFER+1   track of next t/s list
  2650        BEQ .3            no next list
  2660        STA IOB.TRACK
  2670        LDA TS.BUFFER+2   sector
  2680        STA IOB.SECTOR
  2690        LDA IOB.OPCODE    read or write
  2700        PHA               stash it
  2710        JSR READ.TS.LIST  read next one
  2720        PLA
  2730        STA IOB.OPCODE    restore
  2740        BRA NEXT.TS.ENTRY and go on
  2750  
  2760 .3     LDA ALLOCATE.FLAG are we allocating?
  2770        BEQ .1            no, it's eof time
  2780        JSR ALLOCATE.SECTOR for next list
  2790        STA TS.BUFFER+2   sector
  2800        LDA CURRENT.TRACK
  2810        STA TS.BUFFER+1   and track
  2820        LDA THIS.TS.TRACK
  2830        STA IOB.TRACK
  2840        LDA THIS.TS.SECTOR
  2850        STA IOB.SECTOR
  2860        JSR WRITE.TS.LIST write full sector
  2870  
  2880        LDA TS.BUFFER+1
  2890        STA THIS.TS.TRACK keep track
  2900        LDA TS.BUFFER+2
  2910        STA THIS.TS.SECTOR
  2920        LDX #0
  2930 .4     STZ TS.BUFFER,X   clear the buffer
  2940        INX
  2950        BNE .4
  2960        CLC
  2970        LDA TS.OFFSET
  2980        ADC #122          update offset
  2990        STA TS.OFFSET
  3000        STA TS.BUFFER+5
  3010        LDA TS.OFFSET+1
  3020        ADC #0
  3030        STA TS.OFFSET+1
  3040        STA TS.BUFFER+6
  3050        INC PGM.LENGTH    count new list sector
  3060        BNE .5
  3070        INC PGM.LENGTH+1
  3080 .5     BRA NEXT.TS.ENTRY and go on
  3090 *--------------------------------
  3100 FILE.MANAGER.ERROR
  3110        JSR REMOVE.DOS.PATCHES clean house
  3120        LDA FM.ERROR
  3130        STA ERROR.FLAG         and report error
  3140        RTS
  3150 *--------------------------------
  3160 *
  3170 *   HANDLE TWO PATCHES TO FILE MANAGER
  3180 *
  3190 REMOVE.DOS.PATCHES
  3200        LDX #0
  3210        .HS 2C            skip two bytes
  3220 INSTALL.DOS.PATCHES
  3230        LDX #4
  3240        LDA DOS.PATCHES,X
  3250        STA $AFFD
  3260        LDA DOS.PATCHES+1,X
  3270        STA $B392
  3280        LDA DOS.PATCHES+2,X
  3290        STA $B393
  3300        LDA DOS.PATCHES+3,X
  3310        STA $B394
  3320        RTS
  3330 *--------------------------------
  3340 DOS.PATCHES
  3350 *   Original Code
  3360        .HS AC       LDY opcode
  3370        LDX $B39B
  3380 *   Patching Code
  3390        RTS          don't allow sector allocate
  3400 *                   routine to read/write/VTOC
  3410        JMP FILE.MANAGER.ERROR  trap error exits,
  3420 *                   because we didn't really call
  3430 *                   File Manager legitimately.
  3440 *--------------------------------
  3450        .LIF

A Smarter Hexadecimal Memory Search Bob Sander-Cederlof

Today Rick Hayner called, wanting some help in locating places inside Rak-Ware's DISASM where it calls SETVID and various other subroutines in the monitor. Rick is blind, so he uses the ECHO speech synthesizer to operate his Apple. Programs like DISASM keep un-hooking the speech synthesizer, so he has to patch it until it keeps talking. It takes a long time to search through a whole program for all the possible kinds of code which could un-hook the output device.

We used the monitor "S" command, which can search for a single byte or a two-byte value. This command is not in every Apple monitor, but it was in the one in my computer. We found most of the places, but not all. The trouble was we didn't know exactly what addresses we were looking for, because there are so many possibilities.

Anyway, I kept thinking we needed some kind of searching program which could look through DISASM or whatever more intelligently. What an idea! Let the computer work for us! I came up with SMART.SEARCH, which fills the bill.

SMART.SEARCH can search through a range of memory for any string of bytes up to 255 bytes long. (The listing is prettier if the string length is less than 80, though.) Three strings supplied by the caller define the content of matching strings. The first string is a "mask". Each byte of memory will be ANDed with the corresponding byte in the MASK string before being compared with the key. This lets you ignore certain bits in certain bytes during the search (such as the high bit in ASCII bytes). The key is defined by two strings, providing a lower and an upper limit for values in each byte.

For example, to search for JSR's to subroutines in the monitor, I use a mask string of "FF 00 FF", a lower limit of "20 00 F8", and an upper limit of "20 00 FF". Notice the middle byte always matches, so we trigger on any JSR's to any address between $F800 and $FFFF.

In the listing which follows, Lines 1180-1420 are a demonstration driver for the SMART.SEARCH subroutine. Each call to SMART.SEARCH searches the range from $0800 to $14FF, which happens to be where DISASM loads. I first printed out all the monitor calls, then all the JMPs into the monitor, and then all STA, STX, and STY instructions which store into any address with the low-order byte equal to $36 through $39 (the I/O hooks are at $0036-0039). We might want to also search for JMPs and JSRs to $03EA, which is the DOS I/O hook subroutine, and for stores into the DOS hook area starting at $AA53, but that is "left as an exercise for the reader".

When SMART.SEARCH finds a matching string, it prints out the address of the first byte, and all the bytes of the matching string. SMART.SEARCH displays as many columns of addresses and strings as in can on an 80-column line. If you want a different line length, change the value in line 2050.

SMART.SEARCH has to be driven by a series of calls, which are assembled together with the SMART.SEARCH subroutine. Before you assemble, change line 1170 to set the origin to a place that does not conflict with the memory you are going to examine. Calls to SMART.SEARCH require a lot of information. I decided to use the technique of including all that information immediately after the JSR SMART.SEARCH. You can see how to code the information by studying lines 1180-1420.

Inside SMART.SEARCH, there are three main sections. The first gets information from the caller and sets up a lot of addresses in page-zero vectors. The second searches for a string that matches. The third prints a matching address and string.

Lines 1450-1740, along with several subroutines in lines 2350-2560, pull in all the caller's information. After the information has all been set up, RETURN contains the address SMART.SEARCH to which should go when it is finished.

Lines 1750-1860 compare for a matching string. Lines 1870-2090 print the matching address and string if one is found. Lines 2110-2230 increment the search address for the next comparison. If not past the end address, this code will branch back to do another comparison.

Lines 2110-2120 and 2250-2330 allow the output to be paused and restarted, or aborted by pressing any key. The RETURN key will terminate the particular SMART.SEARCH in progress.

While writing SMART.SEARCH, I thought of many other possibilities. You might like to try some of them. You could adapt it to search through all the sectors of a floppy disk. You could modify the comparison algorithm to include more options. You could search through all the bytes on an extended memory card (such as RAMWORKS or RAMFACTOR). You could modify the output format. I thought of including a FORMAT string, that could specify both byte order and whether to print the bytes found in hex or in ASCII. Or, you could just use the "shell" which handles the parameter passage, and code an entirely different function. If you have an Apple IIgs, you might want to modify SMART.SEARCH so that it looks like a "Tool", and have it installed at boot-up time.

If you come up with some great ones, let us have a look. We might like to pass them on to the rest of you!

  1000 *SAVE SMART.SEARCH
  1010 *--------------------------------
  1020 PNTR   .EQ $00,01
  1030 PEND   .EQ $02,03
  1040 PMASK  .EQ $04,05
  1050 PLOWER .EQ $06,07
  1060 PUPPER .EQ $08,09
  1070 LENGTH .EQ $0A
  1080 FWIDTH .EQ $0B
  1090 COLUMN .EQ $0C
  1100 RETURN .EQ $0D,0E
  1110 *--------------------------------
  1120 PRNTAX .EQ $F941
  1130 COUT   .EQ $FDED
  1140 CROUT  .EQ $FD8E
  1150 PRBYTE .EQ $FDDA
  1160 *--------------------------------
  1170        .OR $3000
  1180 T
  1190        JSR SMART.SEARCH
  1200        .DA $0800    Starting Address
  1210        .DA $1500    Ending address + 1
  1220        .HS 03       Length of strings
  1230        .HS FF.00.FF Mask string
  1240        .HS 20.00.F8 Lower limit string
  1250        .HS 20.00.FF Upper limit string
  1260 *--------------------------------
  1270        JSR SMART.SEARCH
  1280        .DA $0800    Starting Address
  1290        .DA $1500    Ending address + 1
  1300        .HS 03       Length of strings
  1310        .HS FF.00.FF Mask string
  1320        .HS 4C.00.F8 Lower limit string
  1330        .HS 4C.00.FF Upper limit string
  1340 *--------------------------------
  1350        JSR SMART.SEARCH
  1360        .DA $0800    Starting Address
  1370        .DA $1500    Ending address + 1
  1380        .HS 02       Length of strings
  1390        .HS FF.FF    Mask string
  1400        .HS 81.36    Lower limit string
  1410        .HS 9D.39    Upper limit string
  1420        RTS
  1430 *--------------------------------
  1440 SMART.SEARCH
  1450        JSR CROUT
  1460        PLA          GET RETURN ADDRESS
  1470        STA RETURN
  1480        PLA
  1490        STA RETURN+1
  1500 *---Get Parameters after JSR-----
  1510        JSR GET.WORD      Get Starting Address
  1520        STX PNTR
  1530        STA PNTR+1
  1540        JSR GET.WORD      Get Ending Address + 1
  1550        STX PEND
  1560        STA PEND+1
  1570        JSR GET.BYTE      Get Search String Length
  1580        STA LENGTH
  1590        ASL               Calc Field Width
  1600        ADC #7
  1610        STA FWIDTH
  1620        STA COLUMN        Start column counter
  1630        JSR GET.BYTE      Point at Mask String
  1640        LDA RETURN
  1650        STA PMASK
  1660        LDA RETURN+1
  1670        STA PMASK+1
  1680        JSR ADD.LENGTH.TO.RETURN
  1690        STX PLOWER        Point at Lower Limit String
  1700        STA PLOWER+1
  1710        JSR ADD.LENGTH.TO.RETURN
  1720        STX PUPPER        Point at Upper Limit String
  1730        STA PUPPER+1
  1740        JSR ADD.LENGTH.TO.RETURN
  1750 *---Compare strings--------------
  1760 .1     LDY #0
  1770 .2     LDA (PNTR),Y
  1780        AND (PMASK),Y
  1790        CMP (PLOWER),Y
  1800        BCC .6
  1810        CMP (PUPPER),Y
  1820        BCC .3       ...YES
  1830        BNE .6
  1840 .3     INY
  1850        CPY LENGTH
  1860        BCC .2       ...MORE TO COMPARE
  1870 *---Found a matching string------
  1880        LDA PNTR+1   Print the Address
  1890        LDX PNTR
  1900        JSR PRNTAX
  1910        LDA #"-"
  1920        JSR COUT
  1930        LDY #0       Print the String
  1940 .4     LDA (PNTR),Y
  1950        JSR PRBYTE
  1960        INY
  1970        CPY LENGTH
  1980        BCC .4
  1990        LDA #" "     Tab to next column
  2000        JSR COUT
  2010        JSR COUT
  2020 *---Check if end of line---------
  2030        LDA FWIDTH
  2040        ADC COLUMN
  2050        CMP #80
  2060        BCC .5
  2070        JSR CROUT
  2080        LDA FWIDTH
  2090 .5     STA COLUMN
  2100 *---Check for pause/abort--------
  2110        LDA $C000
  2120        BMI .7       PAUSE OR ABORT
  2130 *---Advance Pointer--------------
  2140 .6     INC PNTR
  2150        BNE .1
  2160        INC PNTR+1
  2170        LDA PNTR
  2180        CMP PEND
  2190        LDA PNTR+1
  2200        SBC PEND+1
  2210        BCC .1
  2220        JSR CROUT
  2230        JMP (RETURN)
  2240 *--------------------------------
  2250 .7     STA $C010
  2260        CMP #$8D
  2270        BEQ .9       ...ABORT
  2280 .8     LDA $C000
  2290        BPL .8       ...PAUSE
  2300        STA $C010
  2310        CMP #$8D
  2320        BNE .6       ...END OF PAUSE
  2330 .9     JMP (RETURN)
  2340 *--------------------------------
  2350 GET.WORD
  2360        JSR GET.BYTE
  2370        TAX
  2380 GET.BYTE
  2390        INC RETURN
  2400        BNE .1
  2410        INC RETURN+1
  2420 .1     LDY #0
  2430        LDA (RETURN),Y
  2440        RTS
  2450 *--------------------------------
  2460 ADD.LENGTH.TO.RETURN
  2470        CLC
  2480        LDA RETURN
  2490        ADC LENGTH
  2500        STA RETURN
  2510        TAX
  2520        LDA RETURN+1
  2530        ADC #0
  2540        STA RETURN+1
  2550        RTS
  2560 *--------------------------------

ProVIEW Bob Sander-Cederlof

For the last year or so Doug McComsey has been fine tuning this program, and the result is a tool which I like, use, and recommend.

ProVIEW is essentially a slick ZAP program for ProDOS disks and memory. On an Apple //c, //e, or //gs in 80-column mode ProVIEW gives you a window into RAM, ROM, ProDOS files, and ProDOS disk blocks. You can examine, modify, and update any portion of a file or disk block. ProVIEW does not recognize DOS disks, and will not work in a II, IIPlus, IIclone, or anything in 40-column mode. Nevertheless, these restrictions are more than overcome by its features and ease-of-use.

The various control and display screens of ProVIEW look and function a lot like Appleworks. As a result, you very quickly outgrow the need for a reference manual. All of the keystrokes you need to use are natural and easy to remember; help is only a keystroke away at all times, using the Apple-? combination.

ProVIEW executes without disturbing the S-C Macro Assembler or BASIC.SYSTEM. When you want to use it, you simply insert the disk and type "-PV". Or, if you have it on your hard disk or RAM-disk you can get it even quicker. When you are through, a simple ESCAPE keystroke gets you back to the assembler or to Applesoft.

Doug has asked us to handle sales of ProVIEW. Our introductory price is only $20. You will get documentation and a copyable disk, with one of the handiest tools you've ever purchased.


Display the IIgs Tool Tables Bob Sander-Cederlof

While exploring in my IIgs, a map is handy. There is a lot of RAM and a lot of ROM, and it is practically impossible to keep everything in my head. It is also ridiculous to try drawing such a map with pencil and paper. Why not use the computer to document itself?

The tools in the IIgs are all accessed through a hierarchy of tables. All you need to know is the address of the first (top) table. The top table starts with a 4-byte value which is the number of secondary tables. Following this count are a list of 4-byte addresses which point to each of the secondary tables. Each secondary table begins with a 4-byte count of the number of tools in that toolset, and continues with a list of 4-byte addresses pointing to the code for each tool.

These tables may be either in RAM or ROM. When you turn on your IIgs certain values are loaded into bank $E1. Among these is a pointer at $E1/03C0 which contains a 3-byte address for the top tool table. In my IIgs this is at $FE/013F. All of the secondary tables are also in bank $FE of ROM. However, after booting the system disk most of the tables have been rebuilt in RAM. About half of the toolsets are not resident in ROM anyway, so those tables are meaningless until the system disk has been booted and the RAM-based toolsets loaded. Many of the ROM-based toolsets are already somewhat obselete, because the system disk builds RAM-based tables and loads modified tool code into RAM.

The following program will start at $E1/03C0 and follow the hierarchy of tables. The addresses for all of the tools in every toolset will be displayed. If your printer is on and connected, the tables will be listed on the printer as well.

Although my program runs entirely in Emulation Mode, it still uses some of the new addressing modes of the 65816. Line 1170 shows an example of long indexed mode, and is part of a loop copying the address of the top table into a page-zero pointer. Line 1390 is one of many examples using the long-indirect- indexed mode. In this mode a 3-byte address in page zero is added to the current value in the Y-register to get the effective address.

In the S-C Macro Assembler this long form is distinguished from the normal 2-byte indirect-indexed form by using a ">" before the operand: ">(zp),Y" for long-indirect-indexed, and "(zp),Y" for regular indirect-indexed. The "greater than" sign indicates mnemonically that the address at "zp" is three bytes long rather than two. The assemblers which followed the lead of ORCA/M use a different syntax: "[zp],Y" for long-indirect- indexed. I chose not to use the square brackets because the earlier Apple keyboards could not generate a left bracket ([), and because the right bracket was already in use for macro parameters. Future versions of S-C Macro may allow the ORCA syntax as well as the current form.

If you have a //gs, you will benefit by entering this program and running it. Once you know where the tools are located, you can dig in with the disassembler and find out what makes them tick!

  1000 *SAVE S.TOOL.TABLES
  1010 *--------------------------------
  1020        .OP 65816
  1030 *--------------------------------
  1040 *   This program runs in Emulation Mode
  1050 *--------------------------------
  1060 CROUT  .EQ $FD8E
  1070 PRBYTE .EQ $FDDA
  1080 COUT   .EQ $FDED
  1090 *--------------------------------
  1100 PTRA        .EQ $00,01,02
  1110 PTRB        .EQ $03,04,05
  1120 TOOL.NUMBER .EQ $06
  1130 TOOLSET.NO  .EQ $07
  1140 *--------------------------------
  1150 LIST.TOOL.TABLES
  1160        LDX #2
  1170 .1     LDA $E103C0,X     Start with main table
  1180        STA PTRA,X        Address of table of toolset addresses
  1190        STA PTRB,X
  1200        DEX               3 bytes per address
  1210        BPL .1
  1220 *---Display main table-----------
  1230        LDY #Q.TOOLSET         Title for main table
  1240        JSR QTO
  1250        JSR DISPLAY.A.TABLE    Addresses of toolset tables
  1260 *---Loop for each toolset--------
  1270        LDA #1            Start with toolset #1
  1280        STA TOOLSET.NO
  1290 .2     LDY #Q.TOOL       Toolset title
  1300        JSR QTO
  1310        LDA TOOLSET.NO    Print the # too
  1320        JSR PRBYTE
  1330        LDY #Q.LINE       Underline it
  1340        JSR QTO
  1350        LDA TOOLSET.NO    Make toolset# into index
  1360        ASL               by multiplying by 4
  1370        ASL
  1380        TAY
  1390        LDA >(PTRB),Y     Get address of toolset table
  1400        STA PTRA          lo-byte
  1410        INY
  1420        LDA >(PTRB),Y
  1430        STA PTRA+1        mid-byte
  1440        INY
  1450        LDA >(PTRB),Y
  1460        STA PTRA+2        hi-byte
  1470        JSR DISPLAY.A.TABLE
  1480        INC TOOLSET.NO    Next toolset...
  1490        LDA TOOLSET.NO
  1500        CMP >(PTRB)       Compare to number of existing sets
  1510        BCC .2            ...more toolsets
  1520        RTS
  1530 *--------------------------------
  1540 DISPLAY.A.TABLE
  1550        LDY #Q.TITLE      Title for the table
  1560        JSR QTO
  1570        LDA #1            Start with tool #1 in set
  1580        STA TOOL.NUMBER
  1590 .1     LDY #Q.CRD        Newline and a dollar sign
  1600        JSR QTO
  1610        LDA TOOL.NUMBER   Print the tool (function) # in hex
  1620        JSR PRBYTE
  1630        LDY #Q.DOTS       Connect with dots to address column
  1640        JSR QTO
  1650        LDA TOOL.NUMBER   Make tool# into an index by
  1660        ASL                  multiplying by four
  1670        ASL
  1680        TAY
  1690        INY               Point to the hi-byte
  1700        INY
  1710        LDA >(PTRA),Y
  1720        JSR PRBYTE        Print hi-byte
  1730        LDA #"/"          Using bb/hhll format
  1740        JSR COUT
  1750        DEY               Print mid-byte
  1760        LDA >(PTRA),Y
  1770        JSR PRBYTE
  1780        DEY               Print lo-byte
  1790        LDA >(PTRA),Y
  1800        JSR PRBYTE
  1810        INC TOOL.NUMBER   Next tool # in set
  1820        LDA TOOL.NUMBER
  1830        CMP >(PTRA)       Out of the set yet?
  1840        BCC .1            ...No, still more tools
  1850        RTS
  1860 *--------------------------------
  1870 QTO1   JSR COUT          Print a string
  1880        INY
  1890 QTO    LDA QTS,Y         Enter here with Y indexing message
  1900        BNE QTO1
  1910        RTS
  1920 *--------------------------------
  1930 QTS
  1940 Q.TITLE    .EQ *-QTS
  1950        .HS 8D
  1960        .AS -/Tool   Address/
  1970        .HS 00
  1980 Q.CRD  .EQ *-QTS
  1990        .HS 8D.A4.00
  2000 Q.DOTS .EQ *-QTS
  2010        .AS -/...$/
  2020        .HS 00
  2030 Q.TOOLSET  .EQ *-QTS
  2040        .HS 8D
  2050        .AS -/ Toolset Tables/
  2060 Q.LINE .EQ *-QTS
  2070        .HS 8D
  2080        .AS -/----------------/
  2090        .HS 00
  2100 Q.TOOL     .EQ *-QTS
  2110        .HS 8D.8D.8D
  2120        .AS -/Tools in Set $/
  2130        .HS 00
  2140 *--------------------------------

Display the 65802 Registers Bob Boughner

Shortly after I installed the 65802 chip in my Apple IIPlus, I inadvertently tried to execute a BRK instruction while in the 16-bit Native Mode. Needless to say, things did not work too well. After digging into the problem, I uncovered two areas where difficulties occur. The first is that the monitor ROMs implicitly assume 8-bit word lengths; going into them while in the 16-bit mode will definitly not work.

The second area, and even more serious, is that the BRK vector is located at a different place when you are in Native Mode. In Emulation Mode, or in a normal 6502 or 65C02, the BRK vector is combined with the IRQ vector and is at $FFFE and $FFFF. When you are in Native Mode in a 65802 or 65816, the BRK vector is separated from the IRQ vector. The Native Mode IRQ vector is at $FFEE and $FFEF, while the Native Mode BRK vector is at $FFE6 and $FFE7. A separate vector is required in Native Mode because there is no B-bit in the Native-mode status register.

After this experience, I decided to write a subroutine that would display all the registers, regardless of what mode I was in. The program which follows is the result of that effort as modified by Bob S-C. The program will only work in a 65802 or 65816; if your Apple has a 65C02 or 6502, you are out of luck because I used some instructions and addressing modes not in those processors. As you will notice, I decided not to bother with displaying the program bank and data bank registers, because in a 65802 these have no effect on anything. I also assume the program will be running in an Apple II machine below the IIgs level, so these registers will probably always be zero.

I also assumed in my code that the stack always stays in page one. This may not be a good assumption, but it simplifies a lot. Whenever a program switches to emulation mode, the high byte of the S-register gets changed to 01. Since I am using monitor subroutines, I have to use emulation mode. I further have assumed that the stack-relative addressing mode will work. This means that the stack cannot wrap around during the call and execution of DISPLAY.REGISTERS. If it does, the values printed out may not be correct.

I decided to split the display so that it will fit on a 40-column screen. The PC address and the A-, X-, and Y-registers display on the first line. The D- and S-registers display on the second line, followed by the nine status bits. I break out the status bits and display the bit name in normal video if the bit is 0. If a status bit is 1, its name will display in INVERSE on a //e or //c 80-column screen, and in FLASHING on a 40-column screen. By changing line 1650 to "AND #$DF", and line 1990 to "FLAGS .AS -/eczidxmvn/", you can make 0 status bits display in lower case and 1 status bits display in upper case (assuming your Apple has lower-case display).

Bob S-C made one change which saved a lot of code but will greatly offend some programmers. He added some self-modifying code! Line 1480 stores a value into the instruction in line 1490, building a "LDA ...,S" instruction with the proper offset to pick up a byte out of the stack.

Lines 1190-1220 are the standard way of entering a program which can be called from both Native or Emulation Mode. The first PHP saves the actual status register, and the second one saves the value that was in the E-bit. The CLC-XCE gets us into Native Mode, so that we can save the registers. Since we might have been in full 16-bit native mode, we have to be in that mode when we save the A-, X-, and Y-registers.

Lines 1230-1270 save the registers by pushing them on the stack. Lines 1290 set the D-register to 0000, so that we can call monitor subroutines later. Lines 1320-1350 form a modified return address, so that we can print out the actual address of the JSR DISPLAY.REGISTERS instruction. This really is not that necessary, but I liked it better this way. Lines 1370-1400 push the address on the stack of the stack pointer before the JSR DISPLAY.REGISTERS was executed. Now there are quite a few bytes on the stack, so I will draw a map. The numbers indicate the value that can be used in a "offset,S" addressing mode to access the bytes.

       $10,S  return-hi
       $0F,S    "   -lo
       $0E,S  P-register (status)
       $0D,S      "      (E-bit)
       $0C,S  D-register-hi
       $0B,S      "     -lo
       $0A,S  Y-register-hi
       $09,S      "     -lo
       $08,S  X-register-hi
       $07,S      "     -lo
       $06,S  A-register-hi
       $05,S      "     -lo
       $04,S  call addrs-hi
       $03,S      "     -lo
       $02,S  stack addr-hi
       $01,S      "     -lo

Lines 1420-1550 display the register contents of the 16-bit registers. A format string controls the loop. The bytes in the string which are ASCII characters with the high bit set are printed just as they are. Bytes in the format string which are hex values below $20 are offsets into the stack. These are used to pick up bytes out of the stack for printing in hexadecimal.

Lines 1560-1690 display the nine status bits. I first shove the E-bit value into CARRY, then pick up the rest of the bits in the A-register. The loop then shifts A and CARRY, so that I can effectively squeeze nine bits into the eight-bit A-register.

Lines 1710-1830 restore all the registers and return to the caller in the same mode and with the same status as at the time of call.

I added a little test routine to the end, lines 2010-2100. This loads a few test values into registers in native mode and displays them. The result looked like this:

       0898-  A=1234  X=5678  Y=ABCD
              D=0000  S=01E2  NVMXDIZCE

with N and C in inverse or flashing mode.

When I called DISPLAY.REGISTERS directly by typing $800G from within the ProDOS version of the S-C Macro Assembler, the display looked like this:

       8B52-  A-00DC  X=0002  Y=0000
              D=0000  S=01E2  NVMXDIZCE

with M, X, and E in inverse or flashing mode.

I use my DISPLAY.REGISTERS subroutine when I am debugging a new program, by assembling it in and calling it at strategic points. I am working on a more complete DEBUG program which will allow STEP and TRACE for all of the 65802 opcodes. I am using Bob S-C's DISASM.816 (published in AAL in March 1985 and March 1986), and Steve Wozniak's 6502 TRACE from the old monitor ROM.

  1000 *SAVE S.REG.DISP
  1010 *--------------------------------
  1020        .OP 65802
  1030 *--------------------------------
  1040 MON.PRBYTE .EQ $FDDA
  1050 MON.COUT   .EQ $FDED
  1060 *--------------------------------
  1070 *   SUBROUTINE TO DISPLAY 65802 REGISTERS
  1080 *   By Bob Boughner and Bob S-C
  1090 *
  1100 *   I assume this routine will be used in
  1110 *   an Apple with only 64K address space,
  1120 *   so PBR and DBR are always 00.
  1130 *
  1140 *   I also assume that the S-register is
  1150 *   always in page 1, because it must be
  1160 *   there for monitor subroutines to work.
  1170 *--------------------------------
  1180 DISPLAY.REGISTERS
  1190        PHP          SAVE CALLER'S STATUS
  1200        CLC          ENTER NATIVE MODE
  1210        XCE
  1220        PHP          SAVE CALLER'S E-BIT
  1230        REP #$38     CLR M,X,D (16-BIT, NOT DECIMAL MODE)
  1240        PHD          SAVE D-REGISTER
  1250        PHY               Y-REGISTER
  1260        PHX               X-REGISTER
  1270        PHA               A-REGISTER
  1280 *--------------------------------
  1290        LDA ##0000   D=0000, so no problems with use of monitor
  1300        TCD          subroutines.
  1310 *--------------------------------
  1320        LDA 11,S     Get and adjust Return Address to point to
  1330        DEC          the JSR that got us in here.
  1340        DEC
  1350        PHA
  1360 *--------------------------------
  1370        TSC          Get and adjust Stack Pointer
  1380        SEC
  1390        SBC ##0013
  1400        PHA
  1410 *--------------------------------
  1420        SEC          Get into emulation mode for display
  1430        XCE
  1440 *---Display 16-bit registers-----
  1450        LDY #0
  1460 .1     LDA FORMAT,Y
  1470        BMI .3
  1480        STA .2+1     *** SELF-MODIFYING CODE ***
  1490 .2     LDA *-*,S
  1500        JSR MON.PRBYTE
  1510        BRA .4
  1520 .3     JSR MON.COUT
  1530 .4     INY
  1540        CPY #FORMAT.LEN
  1550        BCC .1
  1560 *---Display status bits----------
  1570        LDY #8       9 bits altogether
  1580        LDA 13,S     Get E-bit into CARRY
  1590        LSR
  1600        LDA 14,S     Get the rest of the bits
  1610 .5     ROL          First time this puts E into bit0
  1620        PHA          Save status byte
  1630        LDA FLAGS,Y  Get bit name
  1640        BCC .6       Print NORMAL if bit=0
  1650        AND #$7F     Print INVERSE or FLASH if bit=1
  1660 .6     JSR MON.COUT
  1670        PLA          Get status byte again
  1680        DEY          Next bit
  1690        BPL .5
  1700 *---Restore everything & return--
  1710        CLC          NATIVE MODE
  1720        XCE
  1730        REP #$30
  1740        PLA          POP OFF S-REG VALUE
  1750        PLA          POP OFF PC VALUE
  1760        PLA          RESTORE A-REG
  1770        PLX                  X-REG
  1780        PLY                  Y-REG
  1790        PLD                  D-REG
  1800        PLP          E-BIT
  1810        XCE
  1820        PLP          STATUS
  1830        RTS
  1840 *--------------------------------
  1850 FORMAT .HS 04.03         PC of JSR DISPLAY.REGISTERS
  1860        .AS -/-  A=/
  1870        .HS 06.05         A-register
  1880        .AS -/  X=/
  1890        .HS 08.07         X-register
  1900        .AS -/  Y=/
  1910        .HS 0A.09.8D      Y-register, RETURN
  1920        .AS -/       D=/
  1930        .HS 0C.0B         D-register
  1940        .AS -/  S=/
  1950        .HS 02.01         S-register
  1960        .AS -/  /
  1970 FORMAT.LEN .EQ *-FORMAT
  1980 *--------------------------------
  1990 FLAGS  .AS -/ECZIDXMVN/
  2000 *--------------------------------
  2010 T      CLC
  2020        XCE
  2030        REP #$30
  2040        LDA ##$1234
  2050        LDX ##$5678
  2060        LDY ##$ABCD
  2070        JSR DISPLAY.REGISTERS
  2080        SEC
  2090        XCE
  2100        RTS
  2110 *--------------------------------

ProDOS-based Intelligent Disassembler Bob Sander-Cederlof

In response to the requests of many of you, I have at long-last developed a disassembler which runs under ProDOS. (This new product is distinctly different from the Rak-Ware DISASM, which runs under DOS 3.3. The Rak-Ware product is still the best one to use if you are using DOS.) Here are some of the features of the new S-C ProDOS DISASM:

Of all the features, the most important may be the "script". This is essentially a "program", written in "disassembly language". The script allows you to define which input files to include and which output files to generate, to name symbols such as monitor entry points and major subroutines in your program being disassembled, to define table areas, and even to insert comments.

The script itself is written using the standard S-C Macro Assembler, and may be saved on a source file just as an assembly-language program would. As you gain knowledge about the program you are disassembling, you can add lines to the script.

Version 1.0 handles all of the 65C02 instruction set. Future enhancements which which are definitely planned include expanding to include the entire 65816 instruction set. Version 1.0 is for sale now for $50 including the commented source code.


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.)