Volume 7 -- Issue 5 | February 1987

In This Issue...

- Pre-Allocating DOS Files
- A Smarter Hexadecimal Memory Search
- ProVIEW Announcement
- Display //gs Tool Tables
- Display the 65802 Registers
- ProDOS-based Intelligent Disassembler

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:

- Input is from one or more binary object files, including file types BIN and SYS.
- Output is to one or more "S-C" type (compressed source code) files.
- Generates comment lines before each label listing all references to that label.
- Disassembly is "script" driven, allowing incremental enhancement.
- Input files may be positioned to specific starting addresses.
- Decodes ProDOS "MLI" calls as such.
- Allows pre-named symbols up to 32-characters long.

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