ProDOS Macro Assembler
We are now shipping the ProDOS version of the S-C Macro Assembler. As reported last month, the ProDOS version alone is $100 and the DOS and ProDOS versions together are $120. The ProDOS update for owners of the DOS Version 2.0 is $30, and for owners of DOS Version 1.x is $50.
The S-C Cross Reference Utility and the Laumer Research Full Screen Editor have been updated to ProDOS versions. The ProDOS code will be included on the back of the disk in all new shipments, and current owners can return their original disks to be updated at a cost of only $5 per program.
65802 Chips
Good News! We have arranged a quantity price on 65802 processors, so we will be able to sell them to our readers for only $50 + shipping. That's only $51.50 in the US for this powerful new 16-bit processor that plugs right into your Apple II, II+, //e, or //c. Combine this chip with the S-C Macro Assembler Version 2.0 and you can start writing faster, more compact code. Order yours today!
Updated VideoTerm Driver
We recently revised the Videx VideoTerm driver in the S-C Macro Assembler Version 2.0 to make it firmware-independent and ViewMaster-compatible. This revision is effective with Serial Number T-1483, so owners of earlier copies can send in their original disks and $5 for an updated copy.
At the track and sector level, DOS 3.3 disks are identical to ProDOS disks. They both have 35 tracks, 16 sectors, and the sectors are laid out on the tracks the same way in both systems. You can use DOS's COPYA program to copy ProDOS disks, and you can use some ProDOS utilities on DOS disks.
The structure of the files is of course entirely different between the two systems. Hence the need for the CONVERT program found on ProDOS system master disks, and the System Utilities Disk that comes with the //c. Unfortunately both of the above programs have bugs that get in the way nearly every time I want to move a file from DOS to ProDOS. The one that bites me the most is the way CONVERT dies when it encounters a DOS filename which does not start with a letter. We routinely use such "illegal" filenames on our disks to separate and identify sections of long catalogs, but CONVERT goes absolutely crazy when it finds one.
Therefore, I decided to write a program which could "LOAD" assembler source files from a DOS 3.3 disk while I am running the ProDOS version of the S-C Macro Assembler. Even with error messages and other fancy features, the program turns out to be only a little over $280 bytes long, and it works.
It is based on the fact that the Block Read MLI call does not care whether the disk being read is a DOS or a ProDOS disk. The Block Read MLI call reads 512 bytes, or two sectors, at a time. The call looks like this:
JSR $BF00 (MLI link in global page) .DA #$80 (block read code) .DA PARMLIST (address of parameters)
MLI returns with carry clear if there was no error, or carry set if there was an error. The error code will be in the A-register if there was an error.
The PARMLIST for Block Read looks like this:
PARMLIST .DA #3 (3 parameters) .DA #$60 (1-byte unit number) .DA BUFFER (address of 512-byte buffer) .DA 2 (2-byte block number)
Page 3-17 of "Beneath Apple ProDOS" contains a table which converts block numbers to physical track/sector, and vice versa. The latest printing of the book also includes a line which correlates the physical sector values to the DOS 3.3 logical sector. Boiling it down, you can derive a ProDOS block number from the DOS 3.3 logical sector by multiplying the track number by 8 and adding a value according to the sector number from the following table:
DOS sector #: 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 7 6 6 5 5 4 4 3 3 2 2 1 1 0 F
For example, track 0 sector 2 is in ProDOS block 6. The only problem is, so is DOS track 0 sector 3. We also need to remember whether a given sector is in the upper or lower half of a 512-byte block.
I developed the following subroutine, which will translate the DOS logical track and sector numbers into the appropriate block number, read the block, and return with the address of the buffer page in which the sector data has been read. Call the routine with the track number in the A-register and the sector number in the X-register. The high-byte of the buffer address will return in the X-register. If MLI detects an error, the subroutine will return with carry set.
RTS LDY #0 ASSUME BLOCK # < $100 ASL FORM TRACK*8 ASL ASL BCC .1 ...BLOCK < $100 INY ...BLOCK > $0FF .1 ASL *2, MAKE ROOM FOR H/L FLAG BIT ORA BLKTBL,X MERGE FROM SECTOR TRANSLATION ROR H/L FLAG BIT TO CARRY STA BLOCK STY BLOCK+1 LDX /BLOCK.BUFFER HIGH BYTE OF BUFFER ADDRESS BCC .2 ...LOWER HALF OF BUFFER INX ...UPPER HALF OF BUFFER .2 JSR $BF00 .DA #$80,PARMLIST RTS BLKTBL .HS 00.0E.0D.0C.0B.0A.09.08 .HS 07.06.05.04.03.02.01.0F PARMLIST .DA #3 .DA #$60 SLOT 6, DRIVE 1 .DA BLOCK.BUFFER BLOCK .DA 0 <FILLED IN>
After playing with the subroutine a while, I proceeded to write the load program. Using a well-worn copy of "Beneath Apple DOS", I figured out once more how to work through a DOS catalog. I decided to display a menu of files on the screen, and allow a single keystroke to select a file to be loaded.
The program that follows is designed to work with the ProDOS version of the S-C Macro Assembler. Assuming it has been assembled and is in a ProDOS binary file as DOS.LOAD, and assuming you have booted the ProDOS version of the S-C Macro Assembler, you can start up the load program by typing "-DOS.LOAD". It will load source files from DOS disks, which are DOS type I files, and place them in the assembler's edit area. After selecting the slot and drive, the program reads the DOS catalog and displays 20 filenames at a time. Only type I filenames are displayed, any others are skipped over. If there are more than 20 files, you can page through them. If you change your mind about loading a file, you can abort. If you see the file you want to load, you type a single letter to select it. A few seconds later it has been loaded, and you are returned to the assembler.
The assembler's soft entry point is at $8003, and the load program jumps there after finishing a load or after encountering an error. Three pointer locations in page zero which the assembler uses are used by the load program: HIMEM ($73,74) points one byte higher than the program can be loaded; PP ($CA,CB) will point to the beginning of the program, if it is successfully loaded; LOMEM ($67,68) points to the lowest address the program can occupy. HIMEM is normally at $7400, and LOMEM at $1000, but these can be changed with the HIMEM and LOMEM commands. LOMEM could be set as low as $0800.
With these limitations on the program extent ($0800...73FF), you can see that the maximum size assembler source file that can be loaded from a DOS disk is $6C00 bytes, or 108 sectors. Or, if you prefer to leave LOMEM at $1000, you can load $6400 bytes or 100 sectors. Most likely you do not have any source files which are bigger than that anyway. If you do, you need to load the DOS version of the assembler and split the files before they can be transferred to ProDOS. The maximum size file of 108 data sectors would only have one track/sector list, so I did not include any logic to chain to a second track/sector list. You may be wondering where the load program itself loads....
The command interpreter I developed for the ProDOS version of the S-C Macro Assembler has three 1024-byte buffers permanently allocated between $7400 and $7FFF. None of them will be in use while the load program is executing, so I borrowed some of that space for the load program. The load program itself loads inside the buffer space allocated to the EXEC command, at $7400-77FF. The blocks read by MLI will be stored at $7C00-7DFF, and I will save a copy of the track/sector list for the file being loaded at $7E00-7EFF.
Now for a description of the actual code. Lines 1270-1410 ask you to type in the slot and drive numbers of the floppy drive the DOS disk is in. ProDOS uses a "unit number", which is a coded form of the slot and drive all in one byte. The slot number is in bits 4-6, and the drive number (0 or 1, corresponding to drives 1 or 2 respectively) in bit 7. My subroutine GETNUM prints a prompt message (selected by the Y-register), inputs a single character from the keyboard, and checks it for legal range. GETNUM is designed to accept only digits, starting with "1", and up to but not including the value in the A-register when GETNUM is called.
Once the unit number has been established, we fall into the LOAD.MENU code. This code is somewhat convoluted, enough to disgust even me. Interlocking loops? Multiple entries and exits? Ouch! Maybe it really IS structured code, but just not in Euclidean space. I think maybe it could be diagrammed on the surface of a Klein bottle (recursive torus?).
Anyway, let's walk through it. Line 1440-1500 set up a fresh menu display and read in the DOS VTOC page so we can start reading the catalog. The second and third bytes in the VTOC page give the track and sector of the first catalog sector. This is almost always track $11, sector $0F; however, by starting at VTOC, we are a little more general. We are still assuming we know where the VTOC is, which is track $11, sector 0. Some non-standard software sets up disks with the VTOC somewhere else, but you are very unlikely to find any S-C source code on such a disk. Each sector of the catalog also contains the track/sector of the next catalog sector in the 2nd and 3rd bytes.
Lines 1530-1550 read in the next catalog sector and set the pointer to the first file entry in that sector. Each file entry is 35 bytes long, and the first one starts at $0B within the sector. The subroutine READ.NEXT.CATALOG.SECTOR will return with carry set if there are no more catalog sectors. The first time through this code, when we fall in from the code above, we will read the first catalog sector.
Lines 1570-1960 pick up filenames out of the catalog sectors and write them on the screen. Not all file names are used: line 1610 filters out deleted files; lines 1660-1700 filter out files which are not type I. The track and sector of the active type-I files are saved in an array, indexed by the menu letter. These values are first picked up in lines 1620-1650, and added to the array in lines 1870-1940. Lines 1720-1770 print the menu letter and two dashes, and then lines 1780-1850 print the filename.
Lines 1950-1960 decrement the line count and test if the screen is full yet. I arbitrarily call a screen full if it has 20 filenames, leaving room for my three-line prompt message. We jump to MENU.SELECTION when we reach 20 lines or when we reach the end of the catalog, whichever comes first.
If we are not yet at the end of catalog and have not yet filled the screen, or if the file was one that got filtered out of the menu, we come to GET.NEXT.FILE at line 1980. Lines 1990-2040 update the pointer into the catalog sector so that it points at the next file, if there is another one. If so, we branch back to NEXT.FILE.NAME, to try the next one in the current sector. If no more names in this sector, we go back to NEXT.CAT.SECTOR to get the next catalog sector (if any).
When we reach the end of catalog, lines 2070,2080 set a flag. We need a flag to tell whether it was screen-full or catalog- end which caused us to come to MENU.SELECTION, so we can either continue through the catalog or wrap-around to the beginning should you wish to see another screenful of filenames.
The MENU.SELECTION section prints a three-line prompt message and waits for you to type a character. If you type a space, you seethe next screenful of filenames. (Of course, if there are fewer than 21 type I files on the disk you will see the same ones over again.) If you type the RETURN or ESCAPE keys, the load program will abort, returning directly to the assembler without loading a file. If you type a letter in the range of the menu, that file will be loaded. Any other key is ignored.
Lines 2260-2370 convert the menu letter you typed into an index to get the track and sector for the track/sector list of the selected file. The track/sector list contains the track and sector for every data sector in the file. Line 2310 reads the track/sector list, and lines 2330-2370 copy it into a special buffer.
The first two bytes of the first data sector of a type-I file contain the length of the file. We need to know the length so we can figure out where to read the data. Lines 2390-2510 read in the first data sector and get the file size.
Lines 2520-2630 figure out where PP should be set so that the file exactly fits between PP and HIMEM, and checks to make sure that it does not go below LOMEM.
Lines 2650-2670 copy the rest of that first sector into the load area, starting at PP. If the file is so short it doesn't fill the first data sector, the LOAD.FROM.SECTOR subroutine will return with carry set and we will return to the assembler, all finished. Otherwise, we fall into the code below, to load the succeeding data sectors. Eventually we will bump into HIMEM, and we are finished.
Now that this program is working I can see neat ways to extend it. Why restrict it to type-I files? It could also BLOAD type-B files, as long as an appropiate load address was set up. It could do the equivalent of a BLOAD on a type-T file, which then could be BSAVE as type TXT in ProDOS. Seems like we might be able to do away with the need for CONVERT, at least in the direction of moving from DOS to ProDOS.
1000 *SAVE S.DOS.LOAD 1010 *-------------------------------- 1020 .OR $7400 1030 .TF DOS.LOAD 1040 *-------------------------------- 1050 PNTR .EQ $00,01 1060 CAT.INDEX .EQ $02 1070 MENU.LETTER .EQ $03 1080 LINE.COUNT .EQ $04 1090 TRACK .EQ $05 1100 SECTOR .EQ $06 1110 DONE.FLAG .EQ $07 1120 SIZE .EQ $08,09 1130 LIMIT .EQ $0A 1140 *-------------------------------- 1150 LOMEM .EQ $67,68 1160 HIMEM .EQ $73,74 1170 PP .EQ $CA,CB 1180 *-------------------------------- 1190 BLOCK.BUFFER .EQ $7C00 1200 TS.LIST .EQ $7E00 1210 *-------------------------------- 1220 MON.RDKEY .EQ $FD0C 1230 MON.CROUT .EQ $FD8E 1240 MON.PRHEX .EQ $FDDA 1250 MON.COUT .EQ $FDED 1260 *-------------------------------- 1270 DOS.LOAD 1280 LDY #EM3 "SLOT:" 1290 LDA #"8" 1...7 1300 JSR GETNUM 00000SSS 1310 LSR 000000SS S 1320 ROR S000000S S 1330 ROR SS000000 S 1340 ROR SSS00000 1350 STA UNIT 1360 LDY #EM4 "DRIVE:" 1370 LDA #"3" 1...2 1380 JSR GETNUM 1390 LSR 1400 LSR 1410 ROR UNIT DSSS0000 1420 *-------------------------------- 1430 LOAD.MENU 1440 JSR SETUP.SCREEN 1450 LDA #17 TRACK 17 1460 LDX #0 SECTOR 0 1470 STX DONE.FLAG 1480 STX PNTR 1490 JSR RTS READ DOS 3.3 VTOC 1500 STX PNTR+1 SET POINTER 1510 *-------------------------------- 1520 NEXT.CAT.SECTOR 1530 JSR READ.NEXT.CATALOG.SECTOR 1540 BCS END.OF.CATALOG 1550 LDY #$0B 1560 *-------------------------------- 1570 NEXT.FILE.NAME 1580 STY CAT.INDEX 1590 LDA (PNTR),Y TRACK 1600 BEQ END.OF.CATALOG 1610 BMI GET.NEXT.FILE ...DELETED FILE 1620 STA TRACK 1630 INY 1640 LDA (PNTR),Y 1650 STA SECTOR 1660 INY 1670 LDA (PNTR),Y FILE TYPE 1680 ASL INGORE LOCK BIT 1690 CMP #2 MUST BE TYPE I 1700 BNE GET.NEXT.FILE ...NOT I, SKIP OVER IT 1710 *---DISPLAY MENU LINE------------ 1720 LDA MENU.LETTER 1730 JSR MON.COUT DISPLAY MENU LETTER, 1740 INC MENU.LETTER 1750 LDA #"-" 1760 JSR MON.COUT ...TWO DASHES 1770 JSR MON.COUT 1780 LDX #30 1790 .1 INY 1800 LDA (PNTR),Y 1810 ORA #$80 1820 JSR MON.COUT ...AND FILENAME 1830 DEX 1840 BNE .1 1850 JSR MON.CROUT 1860 *---SAVE T/S OF TS-LIST---------- 1870 LDA MENU.LETTER 1880 AND #$1F CONVERT TO INDEX 1890 TAX 1900 DEX ...SINCE LETTER INC'ED ALREADY 1910 LDA TRACK 1920 STA TRACKS,X 1930 LDA SECTOR 1940 STA SECTORS,X 1950 DEC LINE.COUNT 1960 BEQ MENU.SELECTION BRANCH IF SCREEN FULL 1970 *-------------------------------- 1980 GET.NEXT.FILE 1990 CLC 2000 LDA CAT.INDEX 2010 ADC #35 2020 TAY BUMP INDEX 2030 BCC NEXT.FILE.NAME 2040 BCS NEXT.CAT.SECTOR 2050 *-------------------------------- 2060 END.OF.CATALOG 2070 LDA #1 2080 STA DONE.FLAG 2090 MENU.SELECTION 2100 LDY #EM0 3-LINE PROMPT 2110 JSR PRINT.MSG 2120 .2 JSR MON.RDKEY 2130 CMP #$E0 LOWER CASE? 2140 BCC .3 2150 AND #$DF STRIP CASE 2160 .3 CMP #" " SPACE? 2170 BEQ MENU.NEXT.SCREEN 2180 CMP #$8D RETURN? 2190 BEQ ABORT 2200 CMP #$9B ESCAPE? 2210 BEQ ABORT 2220 CMP #"A" 2230 BCC .2 NOT A-Z, SO IGNORE 2240 CMP MENU.LETTER 2250 BCS .2 BEYOND VALID VALUES 2260 *---GET T/S LIST----------------- 2270 AND #$1F CONVERT LETTER TO INDEX 2280 TAY 2290 LDX SECTORS,Y 2300 LDA TRACKS,Y 2310 JSR RTS READ TRACK/SECTOR LIST 2320 STX PNTR+1 SET POINTER 2330 LDY #0 2340 .4 LDA (PNTR),Y MOVE T/S LIST TO ITS BUFFER 2350 STA TS.LIST,Y 2360 INY 2370 BNE .4 2380 *---GET THE FILE SIZE------------ 2390 LDY #$0C POINT AT FIRST T/S 2400 STY CAT.INDEX 2410 LDA TS.LIST,Y TRACK 2420 BEQ ERR.EMPTY.FILE 2430 LDX TS.LIST+1,Y SECTOR 2440 JSR RTS READ FIRST SECTOR 2450 STX PNTR+1 2460 LDY #0 2470 LDA (PNTR),Y GET FILE SIZE 2480 STA SIZE 2490 INY 2500 LDA (PNTR),Y 2510 STA SIZE+1 2520 *---MAKE ROOM FOR FILE----------- 2530 SEC 2540 LDA HIMEM 2550 SBC SIZE 2560 STA PP SET ASSEMBLER'S POINTER 2570 STA LPTR+1 AND OUR LOAD POINTER 2580 LDA HIMEM+1 2590 SBC SIZE+1 2600 STA PP+1 2610 STA LPTR+2 2620 CMP LOMEM+1 2630 BCC ERR.TOO.BIG ...TOO LOW 2640 *---LOAD FROM 1ST SECTOR--------- 2650 INY POINT AT FIRST PROGRAM BYTE 2660 .5 JSR LOAD.FROM.SECTOR 2670 BCS ABORT ...END OF LOAD 2680 *---LOAD REST OF FILE------------ 2690 LDY CAT.INDEX 2700 INY 2710 INY 2720 BEQ ABORT 2730 STY CAT.INDEX NEXT TRACK/SECTOR 2740 LDA TS.LIST,Y TRACK 2750 BEQ ABORT ...END OF FILE 2760 LDX TS.LIST+1,Y SECTOR 2770 JSR RTS READ IT 2780 STX PNTR+1 SET POINTER 2790 LDY #0 2800 BEQ .5 ...ALWAYS 2810 *-------------------------------- 2820 ABORT JMP $8003 WARMSTART ASSEMBLER 2830 *-------------------------------- 2840 MENU.NEXT.SCREEN 2850 LDA DONE.FLAG 2860 BEQ .1 2870 JMP LOAD.MENU START ALL OVER 2880 .1 JSR SETUP.SCREEN 2890 JMP GET.NEXT.FILE 2900 *-------------------------------- 2910 ERR.EMPTY.FILE 2920 LDY #EM1 2930 .HS 2C 2940 ERR.TOO.BIG 2950 LDY #EM2 2960 JSR PRINT.MSG 2970 JMP $8003 2980 *-------------------------------- 2990 PRINT.MSG 3000 .1 LDA EMS,Y 3010 BEQ .2 00 IS END OF MESSAGE 3020 JSR MON.COUT 3030 INY 3040 BNE .1 ...ALWAYS 3050 .2 RTS 3060 *-------------------------------- 3070 GETNUM 3080 STA LIMIT 3090 JSR PRINT.MSG PROMPT 3100 .1 JSR MON.RDKEY 3110 CMP #"1" 3120 BCC .1 GO BACK IF TOO SMALL 3130 CMP LIMIT 3140 BCS .1 ...OR TOO LARGE 3150 JSR MON.COUT ECHO CHARACTER 3160 EOR #"0" EXTRACT VALUE 3170 RTS 3180 *-------------------------------- 3190 READ.NEXT.CATALOG.SECTOR 3200 LDA #$0B RESTART INDEX 3210 STA CAT.INDEX 3220 SEC IN CASE NO MORE SECTORS 3230 LDY #2 3240 LDA (PNTR),Y 3250 TAX SECTOR 3260 DEY 3270 LDA (PNTR),Y TRACK 3280 BEQ .1 END OF CATALOG 3290 JSR RTS READ IT 3300 STX PNTR+1 PAGE IN BUFFER 3310 CLC SIGNAL WE GOT A SECTOR 3320 .1 RTS 3330 *-------------------------------- 3340 * READ TRACK/SECTOR 3350 * (A)=TRACK, (X)=SECTOR 3360 * RETURNS (X)=PAGE OF BUFFER CONTAINING SECTOR 3370 * CARRY SET IF ERROR 3380 * CLOBBERS (A) AND (Y) 3390 *-------------------------------- 3400 RTS 3410 LDY #0 3420 ASL TRACK*8 3430 ASL 3440 ASL 3450 BCC .1 BLOCK < $100 3460 INY BLOCK > $0FF 3470 .1 ASL *2, MAKE ROOM FOR H/L FLAG BIT 3480 ORA BLKTBL,X 3490 ROR H/L BIT TO CARRY 3500 STA BLOCK 3510 STY BLOCK+1 3520 LDX /BLOCK.BUFFER 3530 BCC .2 LOWER HALF OF BLOCK 3540 INX UPPER HALF OF BLOCK 3550 .2 JSR $BF00 3560 .DA #$80,PARMLIST 3570 BCS .3 ...ERROR 3580 RTS 3590 .3 PHA SAVE ERROR CODE 3600 LDY #EM5 "ERROR" 3610 JSR PRINT.MSG 3620 PLA 3630 JSR MON.PRHEX DISPLAY CODE 3640 JMP $8003 SOFTLY BACK TO S-C MACRO 3650 *-------------------------------- 3660 SETUP.SCREEN 3670 LDA #20 LINES PER SCREEN 3680 STA LINE.COUNT 3690 LDA #"A" START MENU WITH LETTER "A" 3700 STA MENU.LETTER 3710 JSR MON.CROUT THREE BLANK LINES 3720 JSR MON.CROUT 3730 JMP MON.CROUT RETURN THROUGH CROUT 3740 *-------------------------------- 3750 * RETURN .CS. IF END OF LOAD 3760 *-------------------------------- 3770 LOAD.FROM.SECTOR 3780 LDA LPTR+1 IS THERE ROOM FOR 3790 CMP HIMEM ANOTHER BYTE? 3800 LDA LPTR+2 3810 SBC HIMEM+1 3820 BCS LFS2 NO, END OF LOAD 3830 LDA (PNTR),Y 3840 LPTR STA $5555 3850 INC LPTR+1 3860 BNE .1 3870 INC LPTR+2 3880 .1 INY 3890 BNE LOAD.FROM.SECTOR 3900 LFS2 RTS 3910 *-------------------------------- 3920 EMS 3930 EM0 .EQ *-EMS 3940 .HS 8D 3950 .AS -/TYPE LETTER TO LOAD A FILE,/ 3960 .HS 8D 3970 .AS -/OR <SPACE> FOR MORE FILES,/ 3980 .HS 8D 3990 .AS -/OR <RET> OR <ESC> TO ABORT: / 4000 .HS 00 4010 EM1 .EQ *-EMS 4020 .HS 8D 4030 .AS -/FILE IS EMPTY/ 4040 .HS 00 4050 EM2 .EQ *-EMS 4060 .HS 8D 4070 .AS -/FILE IS TOO BIG/ 4080 .HS 00 4090 EM3 .EQ *-EMS 4100 .AS -/ SLOT: / 4110 .HS 00 4120 EM4 .EQ *-EMS 4130 .HS 8D 4140 .AS -/DRIVE: / 4150 .HS 00 4160 EM5 .EQ *-EMS 4170 .HS 8D 4180 .AS -/ERROR / 4190 .HS 00 4200 *-------------------------------- 4210 BLKTBL .HS 00.0E.0D.0C.0B.0A.09.08 4220 .HS 07.06.05.04.03.02.01.0F 4230 *-------------------------------- 4240 PARMLIST 4250 .DA #3 4260 UNIT .HS 60 DRIVE-1*8+SLOT*16 4270 .DA BLOCK.BUFFER 4280 BLOCK .DA 2 4290 *-------------------------------- 4300 TRACKS .BS 21 4310 SECTORS .BS 21 |
Is the Apple II a slow machine? Hey, it MUST be! After all, it is over 8 years old! It only has an 8-bit microprocessor! It only has a 1-MHz clock! It must be many times slower than today's PC clones, etc. Isn't it?
No.
The 6502 is inherently faster than most other microprocessors. An old rule of thumb had it that a 4-MHz Z-80 ran roughly the same speed as a 1-MHz 6502. Other factors, such as memory speeds, overhead for screen and keyboard, and disk I/O also influence the overall speed, often in favor of the venerable Apple.
Some comparisons come to mind with machines from the past. Anyone remember MIT's "Whirlwind"? A long time ago, its speed was considered super. I'll bet it wasn't as fast as an Apple. According to the book, it had an upper limit of 2048 16-bit words of "high-speed" memory, and had a design limit of 50,000 instructions per second. In actual implementation, it only ever achieved 20,000 operations per second. And that was with a 1 MHz clock! The 6502 with a 1 MHz clock runs from 500,000 to 142,000 operations per second, depending on which ones you are doing. Probably an average of 250,000.
How about the Bendix G-15? It was the "personal" computer of the 1950's, roughly the size of a large refrigerator (much warmer though) and selling for only $50,000. Engineering firms bought them eagerly for their friendly features, amazing flexibility, capacity, and speed. Let's see.... G-15 had 2183 words of RAM, on a magnetic drum, 29 bits per word. Most operations were measured in milliseconds. A floating point interpretive package, called Intercom 500 (or 1000 for double precision), could almost keep up with the typewriter (an IBM Executive, the primary user I/O device). Paper tape cassettes served as handy off-line storage devices.
Some other popular systems were considered fast with memory cycle times over ten microseconds per byte. Fast enough to support several users in a timesharing environment, compile large Fortran programs, and manage large businesses. And usually with smaller than 128K bytes of RAM. Or "core", as we called it in those days.
Nevertheless, Apples often seem slow. Because we ask them to do a lot, and don't want to wait around while it is done. And tolerable waiting times one day seem intolerable the next, because we get used to it. Remember when a trip around the world in 80 days seemed impossibly fast?
Perceived necessity being a prime motivator for innovation, several methods for dramatically accelerating Apples have been developed. Titan Technologies markets the Accelerator, and Microcomputer Technologies (McT) the SpeedDemon. These both promise "up to" 3.5 times faster running speed, and actually deliver an average of over 2 times faster.
We have wanted to try one of these boards for years. The price was too high and our faith too low, so we never bought one. Recently the price has dropped considerably, and reports from friends using them have increased our faith. When McT offered to loan us one for a month, we had no more resistance at all.
Imagine this scenario: the card arrives by UPS at noon. Thirty seconds later we have it in our hands, and are trying to find an Apple with at least one empty slot. Despairing of that, we take out a card and make room for the SpeedDemon in our //e. We turn on the //e, load up the S-C Macro Assembler, and proceed to assemble the biggest program we have. Wow! That's fast!
We promptly ran a lot of speed tests, timing various programs we commonly use around here:
S-C Word Processor Load 89 sectors 6.8 5.5 1.2 Search /###/ 10.4 3.3 3.2 Replace /85/##/ 8.3 2.8 3.0 Mail Label System (primarily Applesoft) Load 48 sectors 23.7 13.8 1.7 Sort - last name 140.6 49.1 2.9 Sort - zip code 56.0 20.0 2.8 S-C Macro Assembler Assemble 771 lines 7.2 3.0 2.4 AppleWorks Data Base Load 47K 25.7 25.0 1.0+ Sort - last name 2.2 1.0 2.2 Sort - zip code 5.0 2.0 2.5 AppleWorks Spreadsheet Load 35K 20.3 19.3 1.1 Recalculate 14.9 6.6 2.3 Insert 9 rows 4.9 1.8 2.7
In a review by Lee The, Personal Computing, Jan 85, the Apple with SpeedDemon was compared to a Compaq PC. Lee compared the systems using word processors on the two machines. The accelerated Apple ran faster in most cases, except when disk I/O was involved. In one case, even an un-accelerated Apple ran faster; the SpeedDemon to Compaq ratio was 4.4!
To summarize, the SpeedDemon really does make your software run faster. The absolute maximum speedup factor is 3.5, but no "real" program would achieve it. The two things that keep you from reaching 3.5 are I/O and memory.
Some I/O cards, notably the disk interface, use software timing. If you speed up the processor while trying to read or write the disk, you are in trouble. SpeedDemon automatically slows down to normal Apple speed when you access slot 6. Jumpers on the card allow you to do the same for slots 4 and 5. I have a disk controller in slot 7 in one of my Apples; I cannot read or write to disks using that controller when the SpeedDemon is active.
Old Apple serial interface cards used software timing loops to convert a byte to a bit stream at a given baud rate. These cards normally were placed in slots 1 or 2, and thus would not be compatible with the SpeedDemon. Modem cards sometimes use software timing for dialing, and they would not work right if accelerated. Any sound effects created through the Apple speaker will be raised way up in pitch. Music cards which depend on timing loops will make a whole new kind of sound.
The card can be turned off in two ways, so the above problem areas can be circumvented. During the power up cycle you have about two seconds during which you may tap the ESCAPE key. If you do, the card will be turned off. Then you hit ctrl-RESET to go into a normal boot. Another way to turn off the card is to store anything into $C05B (POKE 49243,0). After the POKE the Apple will lock up; when you hit ctrl-RESET it will come back in normal speed. There is no way to turn the card back on without turning off the Apple. (Some of you can probably find a way to re-wire it so it could be turned back on.)
The other way the card slows down is during memory access. Apple memory can only be accessed at a 1 MHz rate, so the processor can spend time waiting for memory. SpeedDemon has a 4096-byte cache memory which can run at a full 3.58 MHz rate. The cache is implemented with 4 static RAM chips, providing 8192 bytes of RAM. These are paired so that you get 4096 data bytes and 4096 address bytes. Whenever you read a byte from RAM or ROM, the low-order 12 bits of the address select one of thes 4096 byte pairs. The high 4 bits of the address are compared to the 4 bits in the cache; if they are the same then the data in the cache is presumed to be the data you want. If not, the processor will wait for Apple's memory to read, and then update the cache with the result. Something like that, anyway. Stores into memory always slow down to a 1 MHz rate, because the stores MUST be performed in real RAM, not just cache RAM.
I might have been talking through my hat in the above paragraph. There is no technical documentation available on the SpeedDemon, so I am just deducing the way it works from external appearances.
The Titan Accelerator card has a full 64K RAM, rather than a cache. It is therefore a little bit faster. Reports from those who have tried both indicate Titan is only about 10 percent faster, if that much. Of course you could design artificial situations in which the difference would be much more dramatic. Personally I think I would rather have the cache. And also the cash, since SpeedDemon costs about $25 less.
Titan's card draws about 300 ma at 5 volts, SpeedDemon draws about 600 ma. Titan's card uses more CMOS, and is more sensitive to static electricity.
SpeedDemon uses a 65C02, so you have the additional opcodes and address modes of this enhanced 6502 chip available. I believe you could romove the 65C02 plug a 65802 into the socket and gain even greater enhancements. You would have to have a 65802 rated at 4MHz, but the ones I have are only 2 MHz chips.
There are five PLA's on the SpeedDemon. At least some of these are used to keep track of whatever bank switching you do with Apple's RAM and ROM. Somehow they are able to keep track of the RAMWORKS card too, so the cache doesn't get confused even with a megabyte of RAM. I worry about using it with my STB128 card, or the other cards of the type. Boards which store into Apple RAM using DMA transfer will possible give trouble. I don't know for certain because I don't have any.
I also worried about compatibility with QuikLoader. Both QL and SD want to take control of the bus on power up or reset. Both substitute their own firmware for whatever is plugged into the mother board. Sure enough, when I tried them both in the same machine they did not work. On power up both cpu's began to operate. SD drew its hi-res graphic logo, and then died. QL died too. Take either card out, and all is well.
Speaking of firmware, I should mention that there is a 2716 with 2K of firmware on the SpeedDemon. When you power up or hit ctrl-RESET the firmware on the card takes control. It sets a bunch of //e soft switches, in case it is in a //e, and then looks at the power-up bytes to see whther this is a RESET or power up. (Remember the power up bytes at $3F3 and $3F4? These bytes will be random when you first turn on your Apple, but during initialization they are set so that the exclusive-or of the two bytes is $A5.) If SpeedDemon thinks you have pressed ctrl-RESET, it copies a short (21-byte) program from its own ROM down to $1D0 and jumps to it. The program turns off the SpeedDemon ROM (by storing at $C800) and then uses a loop to make sure the cache doesn't contain misleading information (I call this action TRASHING the CACHE). Then it jumps to Apple's normal reset code.
If SpeedDemon thinks it is power-up time, because the "eor" the bytes at $3F3 and $3F4 is not $A5, it trashes the cache and copies a large program down to RAM at $1000 through $17FF. Then it trashes the cache again, clears the text screen, and jumps to $1000. The copied code at $1000 turns off the firmware ROM, clears the hi-res screen, switches on hi-res graphics, and draws the SpeedDemon logo. This all takes about two seconds. Then it reads the keyboard to see whether you have typed an ESCAPE, a "1", or a "T". ESCAPE signals SpeedDemon you want to run at normal Apple speed, so it shuts itself off. The other codes cause self-testing code to be executed.
I had a lot of fun figuring out the firmware. It so happens they purposely arranged all the bits in the EPROM in reverse order, so that I had to write a program to flip the bytes around before disassembling the code. I guess it was an attempt to frustrate reverse engineering. I think they should have re-arranged the address lines too, if they really are worried about it.
If all the above makes you want to rush right out and buy one, the price is $295 from Microcomputer Technologies (McT), at 1745 21st St., Santa Monica, CA 90404. Their phone number is (213) 829-3641. If you are a member of Call APPLE, they are selling the SpeedDemon card for only $199. The name on the card has been changed to "Mach 3.5", but it is the same as SpeedDemon. Call them at (206) 251-5222. Since the Call APPLE price is as close to wholesale price as we can get, we will not be trying to sell this board at S-C Software.
By the way, Call APPLE's ad contains a warning: "Mach 3.5 is not compatible in speedup mode with Saturn, Legend, Prometheus expansion memory cards with programs that make use of the extra banks on these cards. A compatible version of Mach 3.5 may be specially ordered."
Last week I looked through some old piles of papers and came across a program by Greg Seitz, dated Dec 20, 1983. It was attached to a set of ProDOS Tech Notes, and Greg apparently worked at Apple at that time.
Greg's program lists the filenames of an entire ProDOS directory, showing the whole tree. It shows directory files by printing a slash in front of the filename, and shows the level by indenting. For example, a typical listing might look like this:
PRODOS BASIC.SYSTEM /UTILITIES HELPER DOER /MORE WHATEVER AND.ANOTHER TEXT.FILE ANOTHER
A listing like this can be a big help in finding things on a large hard disk. The program can also be extended in many ways. One that comes to mind immediately is to print the rest of the CATALOG information as well as the file names. Another is to create a complete CATALOG MANAGER utility, which would permit re-arranging the filenames, promoting and demoting files, and so on.
I typed in Greg's program, and then I rewrote it. The listing that follows bears very little resemblance to his code, but I do thank him for the help in getting started.
The program assumes a prefix has been set. If there is no prefix, you will get a beep and no listing. If there is a prefix, and the directory named is online, the listing will begin with that directory. Another enhancement would be to display the current prefix, and allow accepting it or changing it before starting the filename listing.
If we were always starting with the volume directory, it would be a little easier. The volume directory always starts in block 2. However, since we are able to start with any directory, we do not know where it starts. ProDOS allows you to read a directory, and we can get the first block of any directory by using MLI to open the directory file.
Lines 1100-1120 read the current prefix into a buffer. The lines 1130-1150 open that file. Although I have never seen it in the books, apparently OPEN also reads the first block. After the OPEN call, BUFFER.ONE contains the first block of the directory file. Unless we are willing to do a complete search without ProDOS's help, this is the only way I know of to find the first block of a directory file (other than the volume directory).
Since the only reason to OPEN the directory file was to read the first block, lines 1180-1200 close it again. If any of these MLI calls don't go through, line 1210 will ring the alarm and stop.
Lines 1230-1260 start up the directory listing. The first block ONLY will be in BUFFER.ONE. All subsequent blocks will be read into BUFFER.TWO. In order to make the LIST.DIRECTORY program completely recursive, it is called with the buffer address in a zero-page pointer. SETUP.NEXT.BLOCK also gets the next block pointer from the buffer and saves it in NEXT.BLOCK.
LIST.DIRECTORY is really quite simple, in spite of its size. Its main function is to print a list of filenames. Each filename is preceded by a number of blanks, determined by NEST.LEVEL. NEST.LEVEL is incremented at line 1290, each time LIST.DIRECTORY is called. If a file listed happens to be a directory file, LIST.IDRECTORY saves all the pointers and counters on the stack and then calls itself. When the subdirectory's files have all been listed, that recursive call of LIST.DIRECTORY will return, the pointers and counters can be unstacked, and the listing can continue.
The format of the information in a directory is detailed quite well in both "Beneath Apple ProDOS" and "Apple ProDOS Advanced Features". (We recommend and sell both books.) The first four bytes of each block are two block numbers: that of the previous block, and that of the next block, in the same directory. This allows scanning in both forward and reverse directions through a directory. We will only use the next-block pointers in our program. After the block numbers there are 13 descriptors of 39 bytes each. The first descriptor in a directory describes the directory itself, and the rest describe files.
For some reason Apple was not quite sure that it would always use 13 39-byte descriptors, so they stored these two numbers in the directory descriptor. Anyone who access a directory is supposed to look up these two numbers and use them, just in case Apple decides to change them someday. The directory descriptor also contains an active file count. When a file is deleted this count is decremented, but the file descriptor remains. We use the active file count to determine when we reach the end of a directory. Lines 1300-1360 pick up the bytes per descriptor, descriptors per block, and active file count and save them.
Lines 1370-1450 set up PNTR to point at the first file descriptor, which follows the directory header. CURRENT.ENTRY.NUMBER will count up to 13, so we will know when it is time to read another block. We start at 2, because the first block uses the first descriptor for the header. We also clear the file count.
Lines 1460-1500 check for the special case of an empty directory. If there are no active files, we are finished.
Lines 1510-1750 print out the file name from the current file descriptor. The first byte of a descriptor contains a code for the type of file in the first nybble, and the length of the file name in the second nybble. If both are zero, the file has been deleted. The other legal values are $1x, $2x, and $3x to signify a seedling, sapling, or tree file, respectively; and $Dx to signify a directory file. All we care about is whether is a directory file or not, and how long the file name is.
If it is a directory file, lines 1760-2100 will be executed. Lines 1760-1860 push the counters and pointers on the stack. Lines 1870-1930 read in the first block of the sub-directory. Line 1950 calls LIST.DIRECTORY to list the subdirectory. After it is finished, line 1960 will decrement the nesting level. Lines 1970-2060 unstack the pointers and counters. If we were still in the first block of the highest level directory (where we started), we do not need to read the block again: it is still in BUFFER.ONE. Otherwise, lines 2070-2100 read the block back in. If we did not care how much memory we used, we could make this program a lot faster by using more buffers. We could have a different buffer for each level, so that blocks would never have to be re-read.
Lines 2110-2210 count the file just listed, and then check to see if our count is the same as the active file count from the directory header. If so, we are finished.
If we are not finished, lines 2220-2290 bump the pointer into the directory block by the size of a descriptor entry. If we are still in the same block, that is all that we need to do. If not, lines 2350-2420 read in the next block and set things up for it. Then it's back to the top again for the next file name!
We hope some time in the not-so-distant future to be able to write a complete catalog manager program like I started to describe back at the beginning of this article. Some of you are using Bill Morgan's CATALOG ARRANGER for DOS 3.3, and this would be an equivalent utility for ProDOS. We're not quite ready yet, but this program is a step in the right direction.
1000 *SAVE S.RECURCAT 1010 *-------------------------------- 1020 MLI .EQ $BF00 1030 DEVNUM .EQ $BF30 1040 BELL .EQ $FBDD 1050 CROUT .EQ $FD8E 1060 COUT .EQ $FDED 1070 PNTR .EQ $EB AND EC 1080 *-------------------------------- 1090 CAT 1100 JSR MLI GET CURRENT PREFIX 1110 .DA #$C7,P.PREFIX 1120 BCS .1 ...ERROR 1130 JSR MLI OPEN THE DIRECTORY 1140 .DA #$C8,P.OPEN AND READ FIRST BLOCK 1150 BCS .1 ...ERROR 1160 LDA DEVNUM SET UP READ MLI BLOCK 1170 STA R.DEVNUM 1180 JSR MLI CLOSE THE DIRECTORY 1190 .DA #$CC,P.CLOSE 1200 BCC .2 ...NO ERROR 1210 .1 JSR BELL INDICATE ERROR 1220 RTS 1230 .2 LDA #0 BUFFERS ON PAGE BOUNDARIES 1240 STA NEST.LEVEL START AT TOP LEVEL 1250 LDY /BUFFER.ONE POINT TO NEXT BLOCK 1260 JSR SETUP.NEXT.BLOCK 1270 *-------------------------------- 1280 LIST.DIRECTORY 1290 INC NEST.LEVEL DROP TO NEXT LEVEL 1300 *---GET DIR DATA----------------- 1310 LDY #38 1320 .1 LDA (PNTR),Y GET: BYTES.PER.ENTRY....35 1330 STA BYTES.PER.ENTRY-35,Y ENTRIES.PER.BLOCK..36 1340 DEY FILE.COUNT......37,38 1350 CPY #35 1360 BCS .1 1370 *---POINT TO FIRST FILE NAME----- 1380 LDA #2 SKIP OVER DIR HEADER 1390 STA CURRENT.ENTRY.NUMBER 1400 ASL A=4, CLEAR CARRY 1410 ADC BYTES.PER.ENTRY 1420 STA PNTR POINT AT FIRST NAME 1430 LDA #0 START FILE COUNT 1440 STA CURRENT.FILE.COUNT 1450 STA CURRENT.FILE.COUNT+1 1460 *---STOP IF NO ACTIVE FILES------ 1470 LDA ACTIVE.FILE.COUNT 1480 ORA ACTIVE.FILE.COUNT+1 1490 BNE .2 ...AT LEAST ONE FILE 1500 RTS ...END OF DIRECTORY 1510 *---PRINT FILE NAME-------------- 1520 .2 LDY #0 POINT TO TYPE/LENGTH 1530 LDA (PNTR),Y 1540 BEQ .8 0 = DELETED FILE 1550 AND #$0F ISOLATE NAME LENGTH 1560 TAX X = #CHARS IN NAME 1570 LDY NEST.LEVEL NUMBER OF LEADING BLANKS 1580 LDA #" " 1590 .3 JSR COUT INDENT BY DIRECTORY LEVEL 1600 DEY 1610 BNE .3 1620 LDA (PNTR),Y GET TYPE/LENGTH 1630 PHA 1L, 2L, 3L, OR DL 1640 BPL .4 ...NOT DIR FILE 1650 LDA #"/" DIR FILE, PRINT A SLASH 1660 JSR COUT 1670 .4 INY PRINT THE FILE'S NAME 1680 LDA (PNTR),Y 1690 ORA #$80 1700 JSR COUT 1710 DEX 1720 BNE .4 1730 JSR CROUT 1740 PLA GET TYPE/LENGTH AGAIN 1750 BPL .7 ...NOT DIR FILE 1760 *---PUSH DATA ON STACK----------- 1770 LDA PNTR+1 SAVE POINTER IN CURRENT BLOCK 1780 PHA 1790 LDA PNTR 1800 PHA SAVE: R.BLOCK 1810 LDX #0 BYTES.PER.ENTRY 1820 .5 LDA PUSH.VARS,X ENTRIES.PER.BLOCK 1830 PHA ACTIVE.FILE.COUNT 1840 INX CURRENT.FILE.COUNT 1850 CPX #PUSH.COUNT CURRENT.ENTRY.NUMBER 1860 BNE .5 NEXT.BLOCK 1870 *---READ HEADER OF SUBDIR-------- 1880 LDY #$12 POINT AT KEYBLOCK POINTER 1890 LDA (PNTR),Y GET HIGH BYTE 1900 TAX 1910 DEY 1920 LDA (PNTR),Y GET LOW BYTE 1930 JSR READ.NEXT.BLOCK 1940 *---RECURSIVE CALL--------------- 1950 JSR LIST.DIRECTORY 1960 DEC NEST.LEVEL POP TO HIGHER LEVEL 1970 *---POP DATA OFF STACK----------- 1980 LDX #PUSH.COUNT GET BLOCK OF VARS 1990 .6 PLA 2000 STA PUSH.VARS-1,X 2010 DEX 2020 BNE .6 2030 PLA 2040 STA PNTR GET KEYBLOCK POINTER 2050 PLA 2060 STA PNTR+1 2070 CMP /BUFFER.TWO IS BLOCK IN BUFFER.TWO? 2080 BCC .7 ...NO, DON'T NEED TO READ 2090 JSR MLI ...YES, MUST READ THE BLOCK 2100 .DA #$80,P.READ 2110 *---COUNT THE FILE--------------- 2120 .7 INC CURRENT.FILE.COUNT 2130 BNE .8 2140 INC CURRENT.FILE.COUNT+1 2150 *---SEE IF THAT WAS LAST FILE---- 2160 .8 LDA CURRENT.FILE.COUNT 2170 CMP ACTIVE.FILE.COUNT 2180 LDA CURRENT.FILE.COUNT+1 2190 SBC ACTIVE.FILE.COUNT+1 2200 BCC .9 ...NOT LAST FILE 2210 RTS ...END OF DIRECTORY 2220 *---ADVANCE PNTR TO NEXT ENTRY--- 2230 .9 CLC 2240 LDA PNTR GET RESULT IN Y,X 2250 ADC BYTES.PER.ENTRY 2260 TAX 2270 LDA PNTR+1 2280 ADC #0 2290 TAY 2300 *---ARE WE STILL INSIDE BLOCK?--- 2310 LDA CURRENT.ENTRY.NUMBER 2320 INC CURRENT.ENTRY.NUMBER 2330 CMP ENTRIES.PER.BLOCK 2340 BCC .10 ...INSIDE SAME BLOCK 2350 *---READ NEXT BLOCK-------------- 2360 LDA NEXT.BLOCK 2370 LDX NEXT.BLOCK+1 2380 JSR READ.NEXT.BLOCK 2390 LDA #1 START WITH FIRST ENTRY 2400 STA CURRENT.ENTRY.NUMBER IN NEW BLOCK 2410 LDX #4 SKIP OVER BLOCK NUMBERS 2420 LDY /BUFFER.TWO 2430 .10 STX PNTR NEW PNTR VALUE 2440 STY PNTR+1 2450 JMP .2 ...TO LIST NEXT FILENAME 2460 *-------------------------------- 2470 READ.NEXT.BLOCK 2480 STA R.BLOCK BLOCK # IN X,A 2490 STX R.BLOCK+1 2500 JSR MLI READ THE BLOCK 2510 .DA #$80,P.READ 2520 LDA #BUFFER.TWO WE USED BUFFER.TWO 2530 LDY /BUFFER.TWO 2540 SETUP.NEXT.BLOCK 2550 STA PNTR PNTR FROM Y,A 2560 STY PNTR+1 2570 LDY #2 GET NEXT BLOCK # 2580 LDA (PNTR),Y 2590 STA NEXT.BLOCK 2600 INY 2610 LDA (PNTR),Y 2620 STA NEXT.BLOCK+1 2630 RTS RETURN 2640 *-------------------------------- 2650 P.PREFIX .DA #1 2660 .DA BUFFER.TWO 2670 *-------------------------------- 2680 P.OPEN .DA #3 2690 .DA BUFFER.TWO 2700 OPENBUF .DA BUFFER.ONE 2710 .DA #0 2720 *-------------------------------- 2730 P.CLOSE .DA #1 2740 .DA #0 2750 *-------------------------------- 2760 P.READ .DA #3 2770 R.DEVNUM .DA #$60 2780 .DA BUFFER.TWO 2790 PUSH.VARS .EQ * 2800 R.BLOCK .DA 0 2810 *-------------------------------- 2820 BYTES.PER.ENTRY .BS 1 2830 ENTRIES.PER.BLOCK .BS 1 2840 ACTIVE.FILE.COUNT .BS 2 2850 CURRENT.FILE.COUNT .BS 2 2860 CURRENT.ENTRY.NUMBER .BS 1 2870 NEXT.BLOCK .BS 2 2880 PUSH.COUNT .EQ *-PUSH.VARS 2890 *-------------------------------- 2900 NEST.LEVEL .BS 1 2910 *-------------------------------- 2920 WASTED .EQ *+255/256*256-* 2930 .BS WASTED 2940 *-------------------------------- 2950 BUFFER.ONE .BS 512 2960 BUFFER.TWO .BS 512 2970 *-------------------------------- |
I consider it a bug: BASIC.SYSTEM doesn't allow BSAVEing to a new file unless the type is binary. Yet it is equally desirable to be able to BSAVE to non-binary files without first CREATEing them.
I discovered this problem while implementing FIG-FORTH in ProDOS when I wanted to save the data blocks using as little code as possible, and at the same time allow use of standard text-file word processors.
BSAVEing would solve the code length problem, but to make a text file I would have had to CREATE the file first, thus decreasing speed and increasing code length. Therefore I looked for the BSAVE code inside BASIC.SYSTEM to fix the bug.
As it comes from Apple, BASIC.SYSTEM's parser puts the specified type in $BE6A and then the BSAVE processor places it there again. I used the space this redundant code took for my patch.
There seems to be no good reason for Apple to purposely prevent BSAVEing to new non-binary files, so I think my patch is both worthwhile and safe.
The following applies only to Apple's BASIC.SYSTEM version 1.1, which is the latest as far as I know. The addresses shown are the actual running position. If you want to patch the SYS file by BLOADing at A$2000, then addresses $ADxx will be at $37xx and addresses $AExx will be at $38xx.
The following is in the CREATE code.
Now is:
AD41- A9 0F LDA #$0F DEFAULT SYS FILE AD43- 8D 6A BE STA $BE6A PUT IN GLOBAL PAGE
Change to:
AD41- A2 0F LDX #$0F AD43- 8E 6A BE STX $BE6A
The following is in the BSAVE code, and is only reached if it is a new file:
Now is:
ADF5- A9 06 LDA #$06 ASSUME TYPE IS BIN ADF7- 8D 6A BE STA $BE6A PUT IN GLOBAL PAGE ADFA- 8D B8 BE STA $BEB8 SET-FILE-INFO LIST ADFD- AD 56 BE LDA $BE56 CHECK IF TYPE GIVEN AE00- 29 04 AND #$04 AE02- D0 0E BNE $AE12 IF YES, THEN ERROR AE04- 20 46 AD JSR $AD46 CREATE NEW FILE
Change to:
ADF5- AE 6A BE LDX $BE6A FILE TYPE FROM PARSING ADF8- AD 56 BE LDA $BE56 CHECK IF TYPE GIVEN ADFB- 29 04 AND #$04 ADFD- D0 02 BNE $AE01 IF YES SKIP DEFAULT ADFF- A2 06 LDX #$06 DEFAULT BIN FILE AE01- 8E B8 BE STX $BEB8 SET-FILE-INFO LIST AE04- 20 43 AD JSR $AD43 GO CREATE FILE
Thanks to Don Worth and Pieter Lechner for their help in dis-assembling, through their book "Supplement to Beneath Apple ProDOS." (This is the book you get by sending in $10 and a coupon from Beneath Apple ProDOS.)
In the May issue of AAL Bob S-C published my article "A New Catalog for Dos 3.3" - he failed to mention or take credit for the fact that he modified my routine and managed to leave a whopping 17 spare bytes - which is 16 more than I left. I was happy enough to have added the new features.
At the end of that article Bob S-C set the challenge to add the Disk Volume message back. However, I have another possible use for those 17 spare bytes - well at least 14 of them!
How about a single-key format control feature for the Catalog command? The user issues the CATALOG command normally; then one more keypress will select either a normal or double- barrelled Catalog display.
Once you install the following additional code, when you issue the CATALOG command the routine waits for a keypress. If you press "D" you get a double-barrelled Catalog listing for your 80-column card or printer. Any other keypress will result in the normal 40-column version.
The line numbers on the 14-byte routine which follows make the code fit into the listing from the May article.
1320 CATALOG AD98- 20 0C FD 1321 JSR MON.RDKEY await keypress AD9B- 49 8E 1322 EOR #$8E "D" ($C4) eor LSR ($4A) AD9D- C9 4A 1323 CMP #$4A if was "D", now LSR AD9F- F0 02 1324 BEQ .0 ...it was "D" ADA1- A9 38 1325 LDA #$38 SEC opcode ADA3- 8D 21 AE 1326 .0 STA DBL.SWITCH set option . . . 2010 DBL.SWITCH SEC . . . 2150 .BS 3 three free bytes.
The code above is of the deadly self-modifying variety, so beware.
Note that if you have version 2.0 of the S-C Macro Assembler, you can write line 1322 as EOR #"D"^$4A if you wish.
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.)