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