Volume 5 -- Issue 10 | July 1985

- Reading DOS 3.3 Disks with ProDOS
- Review of M-c-T SpeedDemon
- Multi-Level ProDOS Catalog
- Allow BSAVE to New Non-Binary Files in BASIC.SYSTEM
- New Catalog Revisited

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.

Reading DOS 3.3 Disks With ProDOS | Bob Sander-Cederlof

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

Review of M-c-T SpeedDemon | Bob Sander-Cederlof

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

Multi-Level ProDOS Catalog | Bob Sander-Cederlof

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

Allow BSAVE to New Non-Binary Files in BASIC.SYSTEM 1.1 | Mark Jackson Chicago, Illinois

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

New Catalog Revisited | Robert F. O'Brien Dublin, Ireland

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