S-C Macro Assembler for ProDOS
At long last, the news you've all been waiting for: the ProDOS version of the S-C Macro Assembler is almost ready. We have a working assembler in Beta testing, and it's doing just fine. We need to spend another month or two shaking on it and developing documentation, so it will be just a little longer 'til we start shipping, but it's on the way! Watch the front page of AAL for the announcement.
News from Don Lancaster
After nearly a year of delay at the publisher, Enhancing Your Apple II and //e, volume 2 is here! This followup to his very popular collection of Apple tricks, gimmicks, and techniques contains still more high-quality information on how to get the most out of our favorite computer. Here Don provides the tricks of microjustification and proportional spacing for Applewriter //e, an absolute "Old Monitor" style RESET for the //e, a software-only video synchronization technique for all Apple II's and //e's, and a just-for-fun guide to mapping and playing Castle Wolfenstein.
I've been saving the best for last: Tearing Into Applewriter //e. Here is 86 pages of priceless data on the internal workings of the most popular Apple Word Processor, including how to capture source code and customize it to your own taste. See our ad on page three for price and shipping.
As you will notice from his ad in this issue, Lancaster has been hard at work tearing into Appleworks, and has a set of disks available on that program. We haven't seen those yet, but I'll bet they're more of the same great inside info we've come to expect from Don.
In AAL March '85 Bob S-C presented re-writes of some loosely coded DOS sections to make space for patches - the Catalog Function Handler is another such loose bit of code, but rather than just free up some bytes I decided to add some useful features which Apple omitted and correct an annoying error at the same time. This new routine adds the following features to the CATALOG command:
In addition, the new Catalog retains the principal features of the old routine such as displaying the Volume number, the locked file indicator (*) and the file type abbreviation so that the user is not deprived of any essential information.
All the foregoing was achieved without using any additional DOS RAM space or zero-page locations other than that space already used by the Catalog Function Handler itself. Of course, something of the old routine had to be sacrificed in order to add the new features - it was necessary to omit the message "DISK VOLUME " from the beginning of the display. The 12-byte space where this message resided is now used to house a subroutine to check for locked files.
Even with all these enhancements, there are 17 free bytes left over! You could use some of them to print out an abbreviated form of the "DISK VOLUME " message, like "V=".
An additional constraint I saddled myself with in doing the re-write was that PRINT.DECIMAL (the DOS subroutine used to convert the hexadecimal numbers in locations $44,45 to decimal and print them) should retain its normal entry point ($AE42) so that the new code would be compatible with other programs which might use it.
For those who wish to get double-barrelled Catalog listings on an 80-column card or on a printer just change the "SEC" at line 2010 to "LSR". In other words, $AE12:4A will enable the wide printout, and $AE12:38 will put it back to normal.
To install the new patches just BLOAD the two binary files: NEW CATALOG PART 1, and NEW CATALOG PART 2. You can put the modified DOS onto any normal disk using Bob Perkins' technique (in AAL Aug 1982 p.24) without disturbing any other files present, or INIT a blank disk and the modified DOS will be incorporated on it. If you prefer to terminate long Catalog's with the <RETURN> key as you do for listings with the S-C Macro Assemblers just change byte $AE21:8D.
Also, if you are prepared to restrict yourself to 11 character file-names you can have a double-barrelled Catalog on the 40-Column screen by changing byte $ADF7:0B (POKE 44535,11), but I feel it would be of little value overall.
Now for a more detailed look at the program internals. Due to the requirement to save as many bytes as possible to squeeze in the desired features it was not possible to write the code in as straight-forward a manner as one would like. Even so, the routine was written with 17 byte to spare - after many re-writes to fit in all the features.
Lines 1020-1240 define various subroutines, variables, and data tables inside the rest of DOS.
Lines 1320-1360 use the same code as the original Catalog routine to initialize the File Manager and read the disk Volume Table Of Contents (VTOC).
In lines 1370-1410 we clear LINE.SKIP.FLAG which is used by SKIP.LINE subroutine to determine whether to tab to a second column or print a carriage return. Then we call PRINT.DECIMAL.YA to print the volume number. The volume number itself is passed in the A-register, and a zero high-byte in Y. Since we stripped out the code for printing "DISK VOLUME ", the volume number will be printed immediately to the right of the CATALOG command, on the same line. You will see "CATALOG 254 395", or the like, where the first number is the volume number and the second is the count of free sectors.
By making a special entry above the PRINT.DECIMAL subroutine which is used both here and at line 1830 below, we save several bytes. Of course we have already save a couple dozen bytes by not printing "DISK VOLUME ".
Calculation of the free disk space is made in lines 1420-1530. We make use of a new feature in the corrected PRINT.DECIMAL routine whereby $44 and $45 are reduced to 0 during the conversion - resulting in a saving of 4 bytes by not having to re-zero $45. (In the old routine only $44 was reduced to 0.)
In the VTOC 4 bytes are set aside for each track to indicate sector usage although only 2 are needed for a standard Apple disk. (The extra space allows up to 50 tracks and up to 32 sectors per track to be initialized.) A bit set=1 means that the corresponding sector on the track is available for use. If a bit is set=0 then the sector is already allocated. So it was simply a matter of counting every bit set from offset byte $38 (track 0) to Byte $C3 (for Trk $22) of the VTOC buffer to get a count of the free space. If you want to count all the way to the 50th track, in case the program is working with a hard disk like the Sider or Corvus, or a RANA 320K floppy, change lines 1430-1440:
1430 LDX #$38 1440 LDA VTOC.BUFFER,X
In line 1550 we have another departure from the original code - 2 bytes were saved by entering the tail end of the SKIP.LINE subroutine in order to set the number of lines to place on the screen before pausing during a Catalog. This has the added advantage that you can customize your Catalog more easily in that the line count can be adjusted by modifying a single byte ($AE25).
At lines 1570-1610 we start by clearing the Carry flag so that the first sector of the directory will be read (track $11, sector $0F). Also we set the index (X) to the first filename entry in the sector.
Lines 1620-1660 examine the track number of the Track/Sector list for the current filename entry. Should this number be 0 it indicates that we are at the end of the directory, at which point we would terminate the Catalog by exiting the File Manager routine by a jump to $B37F.
Fortunately, there was a JMP $B37F instruction within relative branching distance of the Catalog Function Handler. We could therefore dispense with the JMP to $B37F in the original code saving a further 3 bytes by branching to FM.EXIT at $AD86 instead. This is an address in the DELETE Function Handler ($AD2B-AD97) which precedes the Catalog routine in RAM. There are three ways we can terminate the Catalog, which all result in a branch to FM.EXIT: here at line 1600 when we find there are no more catalog sectors, at line 1650 when we find there are no more catalog entries, and at line 2090 when the ESC-key is typed during a screen-pause.
At line 1660 if the track number value is negative (bit 7 set) then we have found a deleted file. Deleted files don't show up on the Catalog, so we call on the subroutine at $B230 which sets the X-Register to the value of the entry point offset for the next entry in the sector, if any.
If on return from this subroutine the Carry flag bit is set (=1) then we have reached the end of the current catalog sector and we branch back to READ.SECTOR at line 1580 to read the next directory sector, if any. (Each directory sector accommodates 7 entries.)
At line 1680 we call the SKIP.LINE subroutine, which normally merely prints a carriage return. This routine was called from five different places in the original catalog code, so we have saved a dozen bytes by only calling it from this one place. (Putting it in-line would save four more!)
At line 1700 we call the new subroutine at the site of the DISK VOLUME message space to check for locked files and print the space or asterisk. This routine also leaves the file type code in the Y-register. This code could be placed in-line, rather than making it a subroutine, but then the final two lines could not be used as a short PRINT.SPACE subroutine.
Lines 1710-1790 convert the file type code to a file type character. The file type code is in bits 6-0, and is either zero (meaning type T), or a single bit. The hex values 40, 20, 10, 08, 04, 02, and 01 stand for file types B, A, R, S, B, A, and I. A string at $B4C8 holds "TIABSRAB", so we need to convert the bit position to an index value, and pick up the character out of that string. The ASL at line 1740 elminates the "lock/unlock" bit. The loop in line 1750-1770 shifts bits out until the value is zero, counting up in the Y-register. If the value was already zero, we exit immediately with Y=0, and type is "T". A type value of 1 gives an index of 1, up through $40 giving an index of 7.
By the way, types 40 and 20 are not Binary and Applesoft. They are hardly ever used, except in protection schemes. Types 04 and 02 are Binary and Applesoft.
The original catalog code had a significantly longer loop for converting the file type number to an index. You might want to compare the two.
The number of sectors in the file is picked up and converted in lines 1800-1830 and the decimal value is printed, surrounded by spaces. Lines 1840-1900 print out the file name.
Lines 1920-1950 advance to the next filename entry, and branch either to process it or to read in another catalog sector.
Lines 1970-2130 usually print a carriage return. If you have changed line 2010 to "LSR", to get double column catalogs, the least significant bit of LINE.SKIP.FLAG will determine whether to print a carriage return or not. When line 2010 is "SEC" we will always get a carriage return. If a carriage return is printed, we also count the line. When the line count is complete, we pause and wait for a keystroke. If that key is an ESC-key, the catalog will terminate. If not, the line count is re-initialized and we go back for more file names.
Line 2150 simply reserves 17 bytes, shoving the PRINT.DECIMAL routine down so that it still starts at $AE42 like it used to. These 17 bytes could be used for other code or data, whatever you like.
Lines 2160-2230 store a value to be converted and printed, print a blank, and then fall into the PRINT.DECIMAL subroutine.
The new corrected PRINT.DECIMAL subroutine is actually a little shorter than th buggy original. It left room for a JMP PRINT.SPACE at the end, which saved calling PRINT.SPACE from several other places. It also left room for the LINE.SKIP.FLAG variable.
The PRINT.DECIMAL subroutine (lines 2240-2490) effectively divides the number in $44,45 by subtracting in turn the values 100, 10 and 1 from it - a 16-bit subtraction. The count of the number of subtractions and the low order byte remainder are temporarily stored on the stack to conserve memory usage. We start with 100 and keep subtracting it and incrementing the subtraction-counter until we get borrow, at which point we print the counter value.
Now $44,45 will contain the remainder and so we continue using 10 and then 1 until three decimal digits are printed. This subroutine can accurately convert numbers having values up to 999 decimal.
CHALLENGE. Even though we have already squeezed out 17 bytes, while adding new features, we did lose the "DISK VOLUME " message. Can someone out there squeeze enough more out, without losing any features, to slip the message back in?
CAVEAT. If you decide to put this new CATALOG program on your disks, please be careful. There are some programs which temporarily patch the catalog routine themselves. In particular, ES-CAPE and other commercial programs patch the SKIP.LINES subroutine so that the pause is eliminated. Since SKIP.LINES has been moved and is different, no telling what might happen.
1000 *SAVE S.NEW CATALOG 1010 *-------------------------------- 1020 DOS.ARITH.REG .EQ $44,45 1030 *-------------------------------- 1040 ADV.NEXT.DIR.ENTRY .EQ $B230 1050 DOS.INIT.FM .EQ $ABDC 1060 EXIT.FM .EQ $AD86 1070 MON.COUT .EQ $FDED 1080 MON.CROUT .EQ $FD8E 1090 MON.RDKEY .EQ $FD0C 1100 READ.DIRECTORY.SECTOR .EQ $B011 1110 READ.VTOC .EQ $AFF7 1120 *-------------------------------- 1130 DEC.CONVERSION.TABLE .EQ $B3A4 1140 FILE.TYPE.NAME.TABLE .EQ $B3A7 1150 *-------------------------------- 1160 CATALOG.LINE.COUNT .EQ $B39D 1170 DIRECTORY.ENTRY .EQ $B4C6 1180 DIRECTORY.INDEX .EQ $B39C 1190 DISK.VOL.NUMBER .EQ $B7F6 1200 FILE.NAME .EQ $B4C9 1210 FILE.SIZE .EQ $B4E7 1220 FILE.TYPE .EQ $B4C8 1230 FM.VOL.NUMBER .EQ $B5F9 1240 VTOC.BUFFER .EQ $B3BB 1250 *-------------------------------- 1260 * New Catalog for DOS 3.3 1270 * by Robert F.O'Brien 1280 *-------------------------------- 1290 .OR $AD98 1300 .TF NEW CATALOG PART 1 1310 *-------------------------------- 1320 CATALOG 1330 JSR DOS.INIT.FM Init file manager. 1340 LDA #$FF Set Volume = 0 1350 STA FM.VOL.NUMBER (matches any volume) 1360 JSR READ.VTOC Load in VTOC into buffer. 1370 *---Print Volume Number---------- 1380 LDY #0 High byte = 0 1390 STY LINE.SKIP.FLAG (signal to skip) 1400 LDA DISK.VOL.NUMBER low byte 1410 JSR PRINT.DECIMAL.YA 1420 *---Calculate Free Space--------- 1430 LDX #$74 Trk 0 VTOC offset 1440 .1 LDA VTOC.BUFFER+$38-$74,X Bit Map Byte 1450 .2 BPL .3 This sector in use 1460 INC DOS.ARITH.REG Count a free sector. 1470 BNE .3 1480 INC DOS.ARITH.REG+1 1490 .3 ASL Check next bit 1500 BNE .2 Still more in this byte 1510 .4 INX Next byte of bit map 1520 BNE .1 ...still more 1530 JSR PRINT.DECIMAL print number free 1540 *---Start Line count------------- 1550 JSR SET.LINE.COUNT lines to print. 1560 *---Start reading directory------ 1570 CLC Get first sector. 1580 READ.SECTOR 1590 JSR READ.DIRECTORY.SECTOR 1600 BCS EXIT.FM No more sectors 1610 LDX #0 Index to 1st file in sector 1620 SET.ENTRY.INDEX 1630 STX DIRECTORY.INDEX Set entry offset 1640 LDA DIRECTORY.ENTRY,X See if valid filename 1650 EXIT BEQ EXIT.FM ...end of directory 1660 BMI NEXT.ENTRY ...ignore deleted file. 1670 *---Start next file display------ 1680 JSR SKIP.LINE Next line or Tab 1690 *---Locked or Unlocked----------- 1700 JSR LOCKED.FILE.CHECK "*" if locked file. 1710 *---File Type-------------------- 1720 TYA Get file type byte 1730 LDY #-1 Index to type table 1740 ASL Ignore Bit 7 1750 .1 INY Next file type code 1760 LSR Check bit of type byte 1770 BNE .1 ...not yet 1780 .2 LDA FILE.TYPE.NAME.TABLE,Y Get file type 1790 JSR MON.COUT ...and print it 1800 *---File Size-------------------- 1810 LDY FILE.SIZE+1,X high order byte 1820 LDA FILE.SIZE,X low order byte 1830 JSR PRINT.DECIMAL.YA print total sect. 1840 *---File Name-------------------- 1850 LDY #30 1860 .3 LDA FILE.NAME,X char. no. in Y. 1870 JSR MON.COUT print file name. 1880 INX next char. 1890 DEY 1900 BNE .3 not done yet! 1910 *---Next File in Directory------- 1920 NEXT.ENTRY 1930 JSR ADV.NEXT.DIR.ENTRY Set X-Reg for next file 1940 BCC SET.ENTRY.INDEX more in sector 1950 BCS READ.SECTOR get next sector 1960 *-------------------------------- 1970 SKIP.LINE 1980 JSR PRINT.SPACE Separate 2nd line entry 1990 INC LINE.SKIP.FLAG Toggle lsbit 2000 LDA LINE.SKIP.FLAG Check Odd or Even 2010 SEC <<<Change to "LSR" for double 2020 * column CATALOG >>> 2030 BCC RETURN 2040 JSR MON.CROUT Start a new line 2050 DEC CATALOG.LINE.COUNT continue countdown 2060 BNE RETURN not full screen yet 2070 JSR MON.RDKEY Pause for keypress! 2080 CMP #$9B Is it ESC-key? 2090 BEQ EXIT ...yes, exit file manager 2100 SET.LINE.COUNT 2110 LDA #21 lines per screenful 2120 STA CATALOG.LINE.COUNT 2130 RETURN RTS Continue catalog 2140 *-------------------------------- 2150 .BS 17 17 FREE BYTES! 2160 *-------------------------------- 2170 * Print (YA) with leading and 2180 * trailing blanks. 2190 *-------------------------------- 2200 PRINT.DECIMAL.YA 2210 STY DOS.ARITH.REG+1 2220 STA DOS.ARITH.REG 2230 JSR PRINT.SPACE 2240 *-------------------------------- 2250 * Print ($44,45) with trailing blank 2260 *-------------------------------- 2270 PRINT.DECIMAL 2280 LDY #2 Set for 3 divisors 2290 .1 LDA #$B0 ASCII zero 2300 .2 PHA save digit on stack 2310 SEC Subtract 100, 10, or 1 2320 LDA DOS.ARITH.REG from remainder 2330 SBC DEC.CONVERSION.TABLE,Y 2340 PHA save remainder on stack 2350 LDA DOS.ARITH.REG+1 2360 SBC #0 (divisor high byte = 0) 2370 BCC .3 ...far enough 2380 STA DOS.ARITH.REG+1 Update remainder 2390 PLA 2400 STA DOS.ARITH.REG 2410 PLA get current digit 2420 ADC #0 and count the subtraction 2430 BNE .2 ...continue subtracting 2440 .3 PLA Discard stacked remainder byte 2450 PLA Get quotient digit 2460 JSR MON.COUT and print it! 2470 DEY Next divisor 2480 BPL .1 ...not finished yet 2490 JMP PRINT.SPACE Trailing space 2500 *-------------------------------- 2510 LINE.SKIP.FLAG .DA #0 LEAST SIGNIFICANT BIT IS FLAG 2520 *-------------------------------- 2530 .OR $B3AF 2540 .TF NEW CATALOG PART 2 2550 *-------------------------------- 2560 * OVERLAYS "DISK VOLUME " MESSAGE 2570 *-------------------------------- 2580 LOCKED.FILE.CHECK 2590 LDA #"*" 2600 LDY FILE.TYPE,X file type code. 2610 BMI CAT.COUT ...the file is locked 2620 PRINT.SPACE 2630 LDA #" " 2640 CAT.COUT 2650 JMP MON.COUT 2660 *-------------------------------- |
I throughly enjoyed "Fast Text Windows" by Michael Ching. However, I prefer not to use the area at $800-BFF as a text buffer; I much prefer to use the first bank of the language card, which is not normally used by Applesoft programs running under DOS 3.3.
I modified Mike's program by changing the immediate values in lines 1560 and 1580 from #$0C to #$D4 and adding lines 1644, 1646 and 1905. The first two lines enable the bank of RAM to be read or written to. The last re-enables the Applesoft ROMS.
1644 LDA $C08B 1646 LDA $C08B 1905 LDA $C082
I further modified the program to function in 80 columns on a //e or //c. The big problem was to mimic the text card, which uses bank switching to store adjacent characters in the same address, but different locations (main RAM and aux RAM). This was solved by using one buffer for the "even" characters and another for the "odd".
Additional code was required to determine the even/odd condition, so I (being lazy) removed the border portion of the program to conserve room. The border routines could certainly be retained if part of the program was also moved to bank one of the language card area. (Be careful if you try this, because you must avoid calling the monitor or Applesoft ROMs when the ROMs are switched off. You can possibly get away with calling the monitor with the ROMs switched off, but only if you first make a copy of the monitor in the F800-FFFF area of RAM.)
I moved the data storage to the zero page, mostly because it was available and slightly faster.
1000 ; SAVES.WINDOWS.80 1010 *-AAL APRIL 85 P 16------------- 1020 .OR $2F5 1030 .TF B.WINDOWS.80 1040 *------------------------------- 1050 TOP .EQ $00 $0-$6 DATA 1060 BOTTOM .EQ $01 1070 LEFT .EQ $02 1080 RIGHT .EQ $03 1090 WIDTH .EQ $04 1100 LINE .EQ $05 1110 DIREC .EQ $06 1120 * 1130 B1 .EQ $18,19 TEXT PNTR 1140 B2 .EQ $1A,1B BUFR PNTR 1150 B3 .EQ $1C,1D BUFR PNTR 1160 WNDLFT .EQ $20 1170 WNDWDTH .EQ $21 1180 WNDTOP .EQ $22 1190 WNDBTM .EQ $23 1200 BASL .EQ $28 1210 BASH .EQ $29 1220 *------------------------------- 1230 AMPERV .EQ $3F5 1240 PAG2OFF .EQ $C054 READ MRBRD 1250 PAG2ONN .EQ $C055 READ AUXBRD 1260 LCROM .EQ $C082 1270 LCRAM1 .EQ $C08B 1280 GETBYTE .EQ $E6F8 1290 COMBYTE .EQ $E74C 1300 BASCALC .EQ $FBC1 1310 HOME .EQ $FC58 1320 *------------------------------- 1330 SETUP 1340 LDA #MOVE.WINDOW 1350 STA AMPERV+1 1360 LDA /MOVE.WINDOW 1370 STA AMPERV+2 1380 RTS 1390 *------------------------------- 1400 MOVE.WINDOW 1410 JSR GETBYTE 1420 STX TOP 1430 STX LINE 1440 JSR COMBYTE 1450 STX BOTTOM 1460 JSR COMBYTE 1470 STX LEFT 1480 JSR COMBYTE 1490 STX RIGHT 1500 INX 1510 SEC 1520 TXA 1530 SBC LEFT 1540 STA WIDTH 1550 JSR COMBYTE 1560 DEX 1570 STX DIREC 1580 *------------------------------- 1590 *------------------------------- 1600 MOVE.LINE 1610 LDA LINE 1620 JSR BASCALC 1630 LDA BASH 1640 STA B1+1 1650 EOR #$D4 1660 STA B2+1 1670 CLC 1680 ADC #$04 2ND BUFR 1690 STA B3+1 1700 LDA BASL 1710 STA B1 1720 STA B2 1730 STA B3 1740 LDA LCRAM1 ENABLE LANG 1750 LDA LCRAM1 CARD R/W 1760 *--MOVE THE LINE SEGMENT-------- 1770 LDA RIGHT 1780 LSR A/2 + EVN/ODD 1790 TAY TXT SCRN PNTR 1800 LDX DIREC 1810 BNE .3 1820 *--MOVE IT UP------------------- 1830 LDX WIDTH DOWN COUNTER 1840 BCC .2 1850 .1 LDA (B1),Y DO ODD COLS 1860 STA (B2),Y 1870 DEX 1880 BMI .6 1890 .2 LDA PAG2ONN DO EVN COLS 1900 LDA (B1),Y 1910 STA (B3),Y 1920 LDA PAG2OFF 1930 DEY 1940 DEX 1950 BPL .1 1960 BMI .6 1970 *--MOVE IT DOWN----------------- 1980 .3 LDX WIDTH 1990 BCC .5 2000 .4 LDA (B2),Y DO ODD COLS 2010 STA (B1),Y 2020 DEX 2030 BMI .6 2040 .5 LDA PAG2ONN DO EVN COLS 2050 LDA (B3),Y 2060 STA (B1),Y 2070 LDA PAG2OFF 2080 DEY 2090 DEX 2100 BPL .4 2110 *--NEXT LINE-------------------- 2120 .6 INC LINE 2130 LDA LCROM RESTORE ROM 2140 LDA BOTTOM 2150 CMP LINE 2160 BCS MOVE.LINE 2170 *--IF CLEARING, SET WINDOW------ 2180 LDA DIREC 2190 BNE .7 2200 LDX LEFT 2210 STX WNDLFT 2220 LDX WIDTH 2230 DEX 2240 STX WNDWDTH 2250 LDX TOP 2260 INX 2270 STX WNDTOP 2280 LDX BOTTOM 2290 STX WNDBTM 2300 JSR HOME 2310 .7 RTS 2320 *------------------------------- 2330 ZZEND .EQ * |
Here is a short routine (23 bytes) which makes use of the ESC-U command option to toggle the Auto-linenumbering mode on and off readily. The routine is relocatable so you can put it anywhere you have sufficient free space - just set the ESC-U vector to point to it, in this case :$C083 C083 D00C:4C 00 03 N C080.
When the cursor is waiting for input at the beginning of the command line, typing ESC-U will generate the command AUTO and then you have the option of entering a line number and/or RETURN. To cancel the AUTO mode just type ESC-U while the cursor is at the beginning of the line (just after the linenumber - 4 or 5 digit line numbers are catered for).
Extended AUTO command:
The second routine, starting at $317, is just 17 bytes long and extends the AUTO command so that you can specify the increment after the starting linenumber. For example, AUTO 3000,1 sets a starting line number of 3000 and an increment of 1. This code is also relocatable but you must patch the first instruction in the main AUTO command so that it uses the new code as a sub- routine. In this case it's :$C083 C083 D392:20 17 03 N C080.
The addresses specified for these new features are for the corrected version of the Assembler - i.e. serial nos. greater than 1251; see note in AAL March '85. Here is a table of what to expect at each of the addresses used, so you can find the equivalent spots in other copies of the assembler:
$D198 -- 20 xx D2 (JSR GNC) B0 17 (BCS to RTS) 49 30 (EOR #$30) $D392 -- 20 xx D1 (JSR GET.VALUE) CA (DEX) 30 0E (BMI to SEC) $D40B -- 41 55 54 CF (.AT /AUTO/) xx D3 (.DA AUTO-1) $DB9A -- 09 80 (ORA #$80) 9D 00 02 (STA $0200,X) C9 A0 (CMP #$A0)
Note that with the Auto/Manual Toggle function installed you won't need the MANUAL command any more, so you have a spare command if you need it!
1000 *SAVE S.AUTO/MAN 1010 *-------------------------------- 1020 .OR $300 1030 * .TF AUTO/MAN TOGGLE 1040 *-------------------------------- 1050 INCREMENT .EQ $5A,5B 1060 SYM.VALUE .EQ $B8,B9 1070 AUTO.FLAG .EQ $E3 1080 1090 WARM.START .EQ $D028 1100 GET.VALUE .EQ $D198 1110 AUTO.CMD .EQ $D40B 1120 INSTALL.CHAR .EQ $DB9A 1130 *-------------------------------- 1140 AUTO.MAN.CODE 1150 TXA check cursor posn. 1160 BEQ .1 OK to output cmd. 1170 CPX #7 line start? 1180 BGE .2 ignore ESC-U. 1190 LSR AUTO.FLAG cancel auto-mode. 1200 JMP WARM.START 1210 1220 .1 LDA AUTO.CMD,X output cmd. name 1230 JSR INSTALL.CHAR put in buffer+scrn. 1240 CPX #4 4 chars. output? 1250 BNE .1 no. 1260 .2 RTS exit ESC-U routine 1270 *----------------------------- 1280 * Point start of AUTO cmd. handler 1290 * to here for extended function. 1300 *-------------------------------- 1310 NEW.AUTO.EXT 1320 JSR GET.VALUE get linenum if any. 1330 JSR GET.VALUE get inc. if any. 1340 CPX #3 increment? 1350 BLT .1 no 1360 DEX adjust for inc. 1370 DEX do. 1380 LDA SYM.VALUE set inc. low byte 1390 STA INCREMENT 1400 * (following 2 lines only needed 1410 * if you use increments of 255+!) 1420 * LDA SYM.VALUE+1 set inc. high byte 1430 * STA INCREMENT+1 1440 .1 RTS finish AUTO cmd. |
Gary Little, the prolific author of Inside the Apple //e and Inside the Apple //c, has yet another new book out. This one is called Apple ProDOS: Advanced Features for Programmers. In this volume Little covers just about all you need to know to write assembly language programs under ProDOS, from simply passing commands to BASIC.SYSTEM, through great detail on all the MLI calls, to writing your own interrupt handlers and device drivers.
Here's a quick summary of the book's contents:
An important strength of this book is the wealth of examples. In the chapter on the Machine Language Interface there is an example of the correct use of EVERY MLI call. The BASIC.SYSTEM chapter includes an ONLINE command, to identify all disk volumes currently on line. The chapter on interrupts contains a couple of examples of mouse programming. The Disk driver section has a listing of a simple /RAM driver for main memory. And this is just a sample of the useful code provided in Little's new book. A companion disk containing all of the book's programs and more is available for $25.00 from the author.
I hear some of you asking: How does Apple ProDOS: Advanced Features (APAF) compare to Beneath Apple ProDOS (BAP)? Well, the two books complement each other quite nicely. With all its examples, treatment of interrupt handlers and device drivers, and overall clarity, I'd say that APAF is the better book on programming under ProDOS. BAP has useful examples as well, and better detail about the internals of diskette formatting and how ProDOS works, especially with its 120+ page supplement describing the code on a line-by-line basis. So if you're concerned with understanding the inner workings of the operating system, or with modifying its behavior, BAP is the book to have. Otherwise, get APAF for the best information on programming using ProDOS. Personally, I'm glad to have both books on the shelf here, along with Apple's ProDOS Technical Reference Manual.
Apple ProDOS: Advanced Features for Programmers, by Gary B. Little. Brady Communications Co., 1985. 266+iv pp., Reference Card. $17.95. Available from S-C Software for $17 + shipping.
This technical note describes the format table used within DISASM 2.2e, which can be modified to adapt the output text file format to other assemblers. Even if you never plan to modify DISASM, or even if you don't own a copy of DISASM, you can learn a lot about the use of configuration tables by studying what follows.
The current version of the disassembler provides three different output formats to support the DOS ToolKit, S-C, and LISA assemblers. The format table contains various attributes which are unique to each assembler. The table begins at location $1331 and is $3F bytes long. Let's first examine the table and then determine how to adapt it to other assembler formats.
Item ToolKit S-C LISA --------- ----------- ----------- -----------comment AA * AA * BB ; firstchr 00 none 89 ^I 00 none tabchr1 A0 spc 89 ^I A0 spc tabchr2 A0 spc A0 spc A0 spc opchr C1 A 00 none 00 none pgzchr C5D1D5 EQU AEC5D1 .EQ C5D0DA EPZ extchr C5D1D5 EQU AEC5D1 .EQ C5D1D5 EQU hexchr C4C6C2 DFB AEC8D3 .HS C8C5D8 HEX orgchr CFD2C7 ORG AECFD2 .OR CFD2C7 ORG prechr AA0000 * 000000 none C9CED3 INS postchr 00 none 98 ^X 85 ^E
comment: the character used at the beginning of a line to signify a comment line.
firstchr: the character ouput at the beginning of each line.
tabchr1: the character used to tab to the opcode field.
tabchr2: the character used to tab to the operand field.
opchr: operand for impled accumulator instructions (ASL, LSR, ROR, ROL).
pgzchr: directive for page zero declarations.
extchr: directive for absolute declarations.
hexchr: directive for data tables.
orgchr: directive for setting the program origin.
prechr: preamble sequence for initialization of the assembler.
postchr: postamble character for termination of the assembler's loading operation.
You will find that it is relatively simple to modify the format table for other assemblers. First, determine which of the three existing formats is to be overwritten (just pick the one you think you'll need the least). Then determine the format data which is appropriate to your assembler. BLOAD DISASM, enter the monitor, and stuff the new values into the table. Finally BSAVE DISASM,A$800,L$D00.
Or, if you have purchased the source code of DISASM 2.2e (or created your own using DISASM!), you can merely edit the table with your assembler and re-assemble the program.
You might also need or want to change some other paramteters, which are not in the format table:
Label Prefix: located at $132E, the current value is C9DAD8 (the letters "IZX"). These letters are used to indicate internal, pagezero, and external labels in the generated text file.
Menu Table: located at $1300, this table contains the names of the three assemblers listed in the first menu. Each name is stored in ASCII, followed by a return ($0D) and a terminator ($00).
Label Name Separator: A period ($AE) is output as the second character in every generated label name. This can be changed to any other character by editing the LDA #$AE instruction at location $0EA4.
I would be interested in hearing from any of you who have already modified DISASM. This kind of feedback can lead to new versions with even more powerful features.
One of the nice new features in ProDOS is the way the diskette catalog shows the date of creation and last modification for each file, IF you have a clock/calendar card installed in your Apple. Well I don't have such a card in either of the Apples I use regularly, at work or at home. And no //c has a clock! (Yet, at least. I'll bet someone will come up with a way...)
Anyway, I got tired of always seeing <NO DATE> and started figuring out how to set a date without a clock to do it for me. A look at Beneath Apple ProDOS informed me that the current date is transformed into the format YYYYYYYMMMMDDDDD and stored (in the usual 6502 low byte/high byte sequence) at $BF90-BF91 in the ProDOS Global Pages (the fixed locations of all of the accessible system variables). The first thing I did was manually convert the current date into that format and poke it in from the Monitor. That went like this:
$BF90 $BF91 May = $5 = 0101 MMM DDDDD YYYYYYY M 10 = $A = 01010 101 01010 1010101 0 '85 = $55 = 1010101 $AA $AA
So, the values to poke into $BF90-91 were $AA and $AA. What better time than a four-A day to start such a project!
That experiment worked just fine: the next file I saved on the disk showed creation and modification dates of 10-MAY-85, just as I had hoped. With that success under my belt the next step had to be to come up with a program to read and/or set those date bytes. And, while I'm at it, why not take advantage of ProDOS' built-in hooks for installing new commands and add a DATE command to the operating system?
How do I go about adding a command? The ProDOS Technical Reference Manual is pretty sketchy on the subject, but two other books, Beneath Apple ProDOS and the new Apple ProDOS: Advanced Features for Programmers, have good descriptions and examples of the procedure. If you're going to do much assembly language programming under ProDOS you should have one or both of those books.
When ProDOS fails to recognize a command it does a JSR EXTRNCMD ($BE06) to find out if an external command processor will claim this one. What I have to do is install the address of DATE in $BE07-08, after moving the address that was already there into a JMP instruction. This way, if DATE doesn't recognize the command it can pass it along to any other processor that might have been there before.
Processing of an external command is normally divided into two phases, a parser and a handler. The parser section will scan the command name at the beginning of the line. If the command is not recognized, the parser should set the carry bit and JMP on to the address found in EXTRNCMD to see if another external processor will claim it.
If the command is recognized, the parser can set certain bits in PBITS ($BE54-55) to signify which parameters are permitted or required on the command line, and store the address of the handler in EXTRNADDR ($BE50-51). After storing the command length minus one in XLEN ($BE52) and a zero in XCNUM ($BE53), to signify that an external processor did claim the command, the parser then returns control to ProDOS to scan the rest of the line. If the line was syntactically correct, ProDOS will return the values of the parameters in a set of standard locations ($BE58-6F) and pass control back to the handler address specified.
Since DATE is a simple processor that uses a nonstandard parameter, I just set PBITS to zero, to indicate no parsing necessary, and store the address of an RTS instruction in EXTRNADDR. I then proceed to do all my processing before returning to ProDOS.
There is one additional wrinkle to using an external command with ProDOS: where do I put my code so ProDOS, Applesoft, and others don't stomp all over it? In the interest of simplicity I have ignored that problem here. The best procedure, as shown in the books mentioned above, is to call ProDOS to assign me a buffer and then relocate my code into that buffer. The examples in the books provide details of this process.
Now, let's take a look at the code:
Lines 1310-1400 install DATE by moving the current External Command address to my exit JMP instruction and storing DATE's address in the vector.
Lines 1440-1540 check the input buffer to see if this is a DATE command. If not we branch on down to that JMP instruction where we earlier put the address found in the External Command vector. This passes control either on to the next external command in the chain, or back to ProDOS for a SYNTAX ERROR.
If the command matched we go on to lines 1560-1650 to do the necessary housekeeping. This involves storing the command length-1 in the Global Page, setting a couple of flags to tell ProDOS not to parse the rest of the command line, and that an external command has taken over. Then we supply a handler address for the second half of ProDOS' processing, which in this case is just an RTS instruction. Finally we reach lines 1670-1690, where we check to see if the character following DATE is a Carriage Return. If so we branch forward to RETURN.DATE to display the existing date.
If there is more than just DATE on the command line, we must want to set a new date, so we fall into SET.DATE at line 1710. This routine makes heavy use of ACCUMULATE.DIGITS at line 2400, so we'll examine that code first. The first step is to zero the byte where we'll be accumulating the value typed in. Then we scan forward in the input buffer, looking for a nonblank character. When we find one we first check to see if it is a slash, which marks the end of a number, or a Carriage Return, which marks the end of the line. If it was either of those we exit, setting the Carry bit to indicate which one we found.
If the character found was not a delimiter we next check to see if it is a number. If not, we have a SYNTAX ERROR. When we do get a number, we strip off the high bits to convert the ASCII code to a binary value, and save that value. We then multiply the previous value in ACCUM by 10 and add in the new value. Then it's back to line 2440 to get another character. Lines 2710-2730 load the A-register with the value found and branch to the error exit if that value was zero.
Now, back to SET.DATE. That routine begins at line 1720 with a DEY to get ready for the INY at the beginning of ACCUMULATE.DIGITS. We then get the month, check for a legal value, and store it. Next we get the day, save the status, and check and save that value. Then it's time to check the status to see if the day was followed by a slash, or by a Carriage Return. If it was a slash then a year was specified, so we go get that value. If it was a Return no year was present, so we use 1985. (I guess that means we'll have to reassemble or patch this program every year. I think I can handle that.)
The last step in SET.DATE is to fold the year, month, and day together as described above and store the results in the Global Page. The comments in the listing illustrate how the bits are shuffled around to the correct format. After setting the date we fall into RETURN.DATE to display the result.
RETURN.DATE, at lines 2080-2290, is quite straightforward. It just gets the bytes from the Global Page, unfolds them, and calls DEC.OUT to translate them to decimal numbers and display those numbers. Again, the comments illustrate the bit manipulations involved in the unfolding process.
The final section of code is DEC.OUT, at lines 2750-2910. In lines 2760-2810 we use the Y-register to count how many times we can subtract 10 from the number passed in the A-register. Then lines 2830-2910 restore and save the A-register, make sure the tens count is non-zero, convert it to a character and print it. We then recover the units value and print that out.
1000 *SAVE S.DATE 1010 *-------------------------------- 1020 * 1030 * Program to read or set the 1040 * date bytes in the Global Page 1050 * 1060 * by Bill Morgan 1070 * 1080 *-------------------------------- 1090 POINTER .EQ $40,41 1100 ACCUM .EQ $42 1110 MONTH .EQ $43 1120 DAY .EQ $44 1130 TEMP .EQ $45 1140 1150 WBUF .EQ $200 1160 1170 EXTRNCMD .EQ $BE07 1180 EXTRNADDR .EQ $BE50,51 1190 XLEN .EQ $BE52 1200 XCNUM .EQ $BE53 1210 PBITS .EQ $BE54 1220 GP.DATE .EQ $BF90 1230 1240 PRAX .EQ $F941 1250 CROUT .EQ $FD8E 1260 COUT .EQ $FDED 1270 *-------------------------------- 1280 .OR $803 1290 * .TF B.DATE 1300 *-------------------------------- 1310 INSTALL 1320 LDA EXTRNCMD+1 exit to old 1330 STA EXIT+2 user command 1340 LDA EXTRNCMD 1350 STA EXIT+1 1360 LDA /DATE become new 1370 STA EXTRNCMD+1 user command 1380 LDA #DATE 1390 STA EXTRNCMD 1400 RTS 1410 *-------------------------------- 1420 COMMAND .AS /DATE/ 1430 *-------------------------------- 1440 DATE LDY #0 1450 STY POINTER point to input buffer 1460 LDA /WBUF 1470 STA POINTER+1 1480 .1 LDA (POINTER),Y scan command 1490 AND #%01111111 1500 CMP COMMAND,Y 1510 BNE ERR.BRIDGE not mine 1520 INY 1530 CPY #4 1540 BCC .1 1550 *--- ProDOS bookkeeping --------- 1560 DEY 1570 STY XLEN command length - 1 1580 INY 1590 LDA #0 1600 STA PBITS don't parse parms 1610 STA XCNUM external command 1620 LDA #RTS1 1630 STA EXTRNADDR no execution after 1640 LDA /RTS1 command parsing 1650 STA EXTRNADDR+1 1660 *--- set or display date? ------- 1670 LDA (POINTER),Y 1680 CMP #$8D DATE only? 1690 BEQ RETURN.DATE yes, return old date 1700 *-------------------------------- 1710 SET.DATE 1720 DEY 1730 JSR ACCUMULATE.DIGITS get month 1740 CMP #13 1750 BCS ERROR >12 no good 1760 STA MONTH 1770 JSR ACCUMULATE.DIGITS get day 1780 PHP save status 1790 CMP #32 1800 BCC GO.ON <=31 ok 1810 1820 PLP 1830 ERR.BRIDGE 1840 BNE ERROR ...always 1850 1860 GO.ON STA DAY 1870 PLP recover status 1880 BCC .1 .CC. if "/" 1890 LDA #85 year defaults to '85 1900 BNE .2 ...always 1910 .1 JSR ACCUMULATE.DIGITS get year 1920 CMP #100 1930 BCS ERROR >99 no good 1940 .2 PHA save year 1950 LDA MONTH X 0000MMMM 1960 LSR M 00000MMM 1970 ROR M M00000MM 1980 ROR M MM00000M 1990 ROR M MMM00000 2000 STA MONTH 2010 PLA M 0YYYYYYY 2020 ROL 0 YYYYYYYM 2030 STA GP.DATE+1 2040 LDA MONTH MMM00000 2050 ORA DAY MMMDDDDD 2060 STA GP.DATE 2070 *-------------------------------- 2080 RETURN.DATE 2090 JSR CROUT 2100 LDA GP.DATE+1 X YYYYYYYM 2110 LSR M 0YYYYYYY 2120 PHA 2130 LDA GP.DATE M MMMDDDDD 2140 PHA 2150 ROR X MMMMDDDD 2160 LSR X 0MMMMDDD 2170 LSR X 00MMMMDD 2180 LSR X 000MMMMD 2190 LSR X 0000MMMM 2200 JSR DEC.OUT display month 2210 LDA #"/" / 2220 JSR COUT 2230 PLA X MMMDDDDD 2240 AND #%00011111 X 000DDDDD 2250 JSR DEC.OUT display day 2260 LDA #"/" / 2270 JSR COUT 2280 PLA X 0YYYYYYY 2290 JSR DEC.OUT display year 2300 *-------------------------------- 2310 GOOD.EXIT 2320 CLC signal no error 2330 RTS1 RTS 2340 *-------------------------------- 2350 ERROR1 PLA clean up 2360 PLA return addresses 2370 ERROR SEC signal error 2380 EXIT JMP RTS1 INSTALL makes address 2390 *-------------------------------- 2400 ACCUMULATE.DIGITS 2410 LDA #0 2420 STA ACCUM zero accumulator 2430 2440 .1 INY next character 2450 LDA (POINTER),Y 2460 AND #%01111111 hi-bit off 2470 CMP #' ' space? 2480 BEQ .1 back for another 2490 CMP #'/' slash? 2500 BEQ .2 yes, exit .CC. 2510 CMP #$0D <CR>? 2520 BEQ .3 yes, exit .CS. 2530 CMP #'0' too small? 2540 BCC ERROR1 not digit 2550 CMP #'9'+1 too big? 2560 BCS ERROR1 not digit 2570 2580 AND #%00001111 isolate value 2590 STA TEMP stash it 2600 LDA ACCUM 2610 ASL X 2 2620 ASL X 4 2630 ADC ACCUM X 5 2640 ASL X 10 2650 ADC TEMP add new digit 2660 BCS ERROR1 too big 2670 STA ACCUM 2680 BCC .1 ...always 2690 2700 .2 CLC .CC. if / 2710 .3 LDA ACCUM return value 2720 BEQ ERROR1 0 no good 2730 RTS 2740 *-------------------------------- 2750 DEC.OUT 2760 LDY #0 zero counter 2770 SEC get ready 2780 .1 SBC #10 subtract 10 2790 BCC .2 borrow? 2800 INY count a 10 2810 BPL .1 ...always 2820 2830 .2 ADC #10 restore borrow 2840 PHA save units 2850 TYA print 10's count 2860 BEQ .3 no leading zero 2870 ORA #$B0 make character 2880 JSR COUT print it 2890 .3 PLA recover units 2900 ORA #$B0 make character 2910 JMP COUT return through COUT |
In previous versions of the S-C assemblers, expressions were evaluated in 16 bits, and symbol values were kept in the table in 16-bit form. Version 2.0 works with 32-bit expressions and symbol values. We added this feature for your benefit, but it may sometimes be a mixed blessing.
For example, Bob Bernard had a problem with a program which assembled perfectly under Version 1.0, but gave countless BAD ADDRESS errors in version 2.0. We traced the problem to his origin statement, which was ".OR -31488". In older versions, -31488 is the same as $8500, but in version 2.0 it is $FFFF8500. The following code will not assemble:
.OR -31488 SSS JMP SSS
Why? Because the value of SSS is also $FFFF8500, and it will not fit in a JMP instruction. In 65816 mode, using a JML instruction, it would be legal.
Two ways to fix come to mind. You normally work in hexadecimal when you are in assembly language, rather than decimal. Therefore, change the origin statement to ".OR $8500". Or, if you really want to use decimal, write ".OR 65536-31488".
Another owner of version 2.0 had a problem with a program that used many macros, and lots of private labels. Private labels are the ones used inside macro definitions, which are written with a colon and a one or two digit number. The private label table normally begins at $FFF and grows downward toward $800. His program assembled with no problems before, but under version 2.0 it got a MEM FULL error. Reason, again, the 32-bit symbol values. Each entry in the private label table now takes two more bytes, so he ran out of space sooner. His solution was to move the beginning of the label table higher.
Apple Assembly Line is published monthly by S-C SOFTWARE CORPORATION, P.O. Box 280300, Dallas, Texas 75228. Phone (214) 324-2050. Subscription rate is $18 per year in the USA, sent Bulk Mail; add $3 for First Class postage in USA, Canada, and Mexico; add $14 postage for other countries. Back issues are available for $1.80 each (other countries add $1 per back issue for postage).
All material herein is copyrighted by S-C SOFTWARE CORPORATION,
all rights reserved. (Apple is a registered trademark of Apple Computer, Inc.)