In This Issue...
65816 Books
The race is on! "Programming the 65816", by David Eyes from Prentice-Hall, originally scheduled for publication last October, is now expected in late April. "65816/65802 Assembly Language Programming", by Michael Fischer from Osborne/McGraw-Hill, scheduled for May publication, is now also due in late April. We have plenty of copies of these books on order, and a long list of patient people waiting for complete information on programming these powerful new chips. Coming Soon...
More Memory Expansion
We'd like to call your attention to the new ad for Applied Engineering's RamFactor board. This is a "Slinky" style memory expansion card for any standard slot of an Apple II, II+, or //e. We've been doing some of the firmware for this product, and it's been a delight to work with.
One thing the ad doesn't really emphasize is the power and flexibility of the program switcher firmware. You can set up the card with a variable number of variable-sized partitions and then switch between them almost instantly. Any partition can be based on any operating system, or on your own program. Couple this with the battery backup option (it's really more of an uninterruptible power suppply for the card) and you have what amounts to a hard disk operating at RAM speed!
From time to time it happens. One way or another I manage to clobber a catalog track on a disk. I have done it three times to Volume 1 in the DOS partition on my 10-megabyte Sider. (All it takes is "INIT HELLO,V1", forgetting that the last slot I accessed was the Sider's.)
All of the other tracks are still intact, but there is no way to get to them because the catalog is totally wiped out. One solution would be to have an accurate backup floppy for each Sider volume. This should be especially easy for Volume 1, because it is mostly standard Sider utilities. Mostly.... I have modifed several of them, and somehow I almost always have several programs-under-development that end up in V1. Of course, I could just as easily destroy the catalog track on any other volume, or any floppy for that matter.
It is for mistakes like mine that the program FIXCAT in "Bag of Tricks" was invented. FIXCAT looks over a diskette, finds all the sectors which look like they contain track/sector lists, and tries to piece together a new catalog track. Even though it is fairly automatic, I find it very difficult to use. I am always getting confused between old (deleted) copies of files and the current ones, and my disks usually have at least 2 or 3 dozen active files.
Recently it happened again. In fact, while I was working on one of the other articles in this issue of AAL. I decided to write a couple of utilities to help me make more effective use of FIXCAT. My new tools turn out to be useful even without FIXCAT, and you might enjoy just playing with them.
I assume you have a copy of "Beneath Apple DOS", or some other reference work which explains the format of DOS disks, catalog tracks, and track/sector lists.
The first tool I wrote looks through the tracks and sectors of a damaged disk for any sectors containing what could be track/sector lists. When one is found, I display the location of the supposed TS-list, all of the track/sectors in the list, and the first 64 bytes of the first data sector of the supposed file. Here is an example of the display:
03-5: 03-4 03-3 03-2 07 02 09 E8 03 81 2E 4C 49 46 00 16 F2 03 2A C0 ...h...LIF..r.*@ 06 08 53 41 56 45 81 42 43 44 81 4D 41 47 49 43 ..SAVE.BCD.MAGIC 00 08 FC 03 2A C0 20 2D 00 05 06 04 54 00 0B 10 ..|.*@ -....T... 04 87 4C 44 41 81 23 30 00 0C 1A 04 2E 31 85 53 ..LDA.#0.....1.S
The first 64 bytes are displayed in both hexadecimal and in ASCII, with periods being substituted for unprintable characters.
Having this information on paper before starting up FIXCAT is a big help. I can peacefully analyze the data at my desk, without the fear and panic associated with making "life and death" decisions at the keyboard. The first few bytes of a file will usually reveal what type of file it is.
If it is a source code file for the S-C Macro Assembler, Integer BASIC, or Applesoft, it will begin with a two-byte length for the file. Binary files begin with the load address, then the length. Text files start right in with data in ASCII, normally with all the high bits on. Since I almost always have a line near the beginning of my source files which contains the file name, I can usually read that file name in the dump of the first 64 bytes.
The FIND.TS.LISTS program is fairly short and simple. Starting from the bottom, the subroutine READTS at lines 2370-2430 calls on RWTS to read a particular track and sector. I elected to use my own IOB, rather than the one inside DOS at $B7E8. For simplicity's sake I assembled in the slot, drive, and volume information in my IOB. READTS only has to store the desired track and sector numbers, and call RWTS. I limited error handling to just re-calling RWTS, in the hopes of eventually succeeding. Should this begin to be a problem, I could print out an error message and either quit or continue with the next sector.
The subroutine READ.NEXT.SECTOR, lines 2200-2350, is used to scan through the disk from beginning to end. TS-lists cannot be in track 0, so I start with track 1. Since DOS allocates sectors in a track starting with sector $0F and going backwards to sector $00, I decided to scan the same way. This makes the files found list more closely to the same order as they were in the original catalog. I first advance the track/sector to the next one, then read it. Thus after reading, CUR.TRACK and CUR.SECTOR are pointing to the one we just read.
Now back to the top. Lines 1100-1130 start CUR.TRACK and CUR.SECTOR at 0. The first call to READ.NEXT.SECTOR will advance them to track 1, sector $0F. Successive calls will read the rest of track 1, then advance to track 2, and so on until we have finished track $22. When we try to read track $23, which does not exist, READ.NEXT.SECTOR will return with carry set and our program will end.
Lines 1170-1290 examine the data in the sector just read to see if it might be a track/sector list. The method I use is to require that there be at least one TS-pair, at BUF+12. I also require that all of the bytes beyond BUF+12 are within the range of valid track-sector pairs. If any bytes are out of range, I assume the current sector is not a TS-list. My tests seem to be adequate, because with every disk I have used it on it found all and only the TS-lists.
Having found TS-list, I call DISPLAY.TS.LIST to display it. Lines 1450-1540 display the location of the TS-list. The subroutine PR.TS prints the track and sector numbers from the A- and X-registers in the form "TT-S". Lines 1550-1720 list the TS-pairs in the TS-list, stopping at the first pair with a track number of zero. Up to 8 pairs are listed on a line.
Lines 1330-1430 read the first data sector of the supposed file, and display the first 64 bytes in hex and ASCII. This display is done by calling DISPLAY.NEXT.16 four times.
As it happens, I did have a fairly recently made backup of the clobbered disk. I thought I should also run my program against this good disk, and comparing the two displays would enable me to pinpoint each active file. However, what I really want from the GOOD disk is the information in the CATALOG. I decided to modify FIND.TS.LISTS to be driven from the catalog track, rather than from a search for TS-lists. The result was another useful tool, BIG.CATALOG.DISPLAY.
BIG.CATALOG.DISPLAY has the same kind of output that FIND.TS.LISTS does, except that it also lists the file type, file name, and sector count from the catalog. Information is included for deleted files for which entries are found in the catalog, as well as all the active files.
The subroutines DISPLAY.TS.LIST, DISPLAY.NEXT.16, SEVEN.SPACES, PR.TS, and READTS are used without any changes from the FIND.TS.LISTS program. Instead of READ.NEXT.SECTOR, I have now READ.NEXT.CATALOG.SECTOR. This starts at track $11, sector $0F, and works back as far as sector $01. A better way might be to follow the actual chain, beginning in the VTOC sector, but the current scheme is easier and works with most of my disks.
Lines 1140-1180 set up the initial catalog track and sector. Lines 1190-1210 read the catalog sector. If the returned status is positive we did read a sector, and continue processing; if not, we are finished. Lines 1220-1250 set up the buffer address in the IOB for reading TS-lists and data sectors: we do not want to read them over the top of the catalog sector we are working with.
Lines 1270-1320 set up a loop for processing each of the seven file entries in the current catalog sector. The "NEXT" part of the loop is at lines 1350-1440. Each catalog entry takes 35 bytes, so lines 1350-1440 add 35 to the pointer.
DISPLAY.DATA.FOR.ONE.FILE first checks for a zero entry, meaning the end of the catalog. A catalog is initialized to all zeroes, so as soon as we find a zero entry we know there are no more files. Next, at lines 1520-1550, I check for a deleted file. If the track number is negative, it is a deleted file. The actual track number of a deleted file is saved on top of the 30th character of the file name, so I pick it up there. Lines 1560-1590 save the track and sector of the TS-list, so I can read it later. Lines 1600-1650 display the file type as a hex value, followed by two dashes.
Lines 1660-1700 print the first 29 characters of the file name. I don't print the last character because for a deleted file it will have been clobbered by saving the track number there. Probably what I should do here is print either the last character for an active file, or some special symbol for a deleted file. You can add that code if you like.
Lines 1710-1770 pick up the file size, in number of sectors, and print it as a hex value. The sector count includes the sector for the TS-list.
Lines 1780-1860 read the track/sector list for the file. If either the track number or the sector number is out of range, nothing is read and we skip any further processing for this file.
Lines 1870-1940 read in the first data sector for the file. Again, if either the track or sector number is out of range, we don't try to read it. Finally, lines 1950-2000 display the first 64 bytes of the file.
I hope you find these new tools as useful as I have. Of course, I could hope you will never NEED them, but that would prabably be a vain hope. I also hope you have "Bag of Tricks" or some similar utility to put it all back together after you get the information my tools provide. And if I ever clobber Volume 1 on my Sider again (perish the thought), I intend to modify my copies of DOS so they will not allow me to INIT a volume on the Sider.
1000 *SAVE S.FIND T/S LISTS 1010 *-------------------------------- 1020 CUR.SECTOR .EQ 0 1030 CUR.TRACK .EQ 1 1040 *--------------------------------- 1050 COUT .EQ $FDED 1060 CROUT .EQ $FD8E 1070 PRBYTE .EQ $FDDA 1080 ENTER.RWTS .EQ $3D9 1090 *-------------------------------- 1100 FIND.TS.LISTS 1110 LDA #0 1120 STA CUR.SECTOR 1130 STA CUR.TRACK 1140 .1 JSR READ.NEXT.SECTOR 1150 BCC .2 GOT A SECTOR, CHECK IT 1160 RTS END OF DISK, QUIT 1170 *---CHECK IF THIS IS T/S LIST---- 1180 .2 LDA BUF+12 TRACK # FOR FIRST DATA SECTOR 1190 BEQ .1 ...NO, TRY NEXT ONE 1200 LDY #12 1210 .3 LDA BUF,Y 1220 CMP #35 1230 BCS .1 ...NOT VALID TRACK 1240 INY 1250 LDA BUF,Y 1260 CMP #16 1270 BCS .1 ...NOT VALID SECTOR 1280 INY 1290 BNE .3 ...MORE IN SECTOR TO CHECK 1300 *---DISPLAY THE T/S LIST--------- 1310 JSR DISPLAY.TS.LIST 1320 *---READ FIRST DATA SECTOR------- 1330 LDY BUF+12 1340 LDX BUF+13 1350 JSR READTS 1360 *---DISPLAY FIRST 64 BYTES------- 1370 LDY #0 1380 JSR DISPLAY.NEXT.16 1390 JSR DISPLAY.NEXT.16 1400 JSR DISPLAY.NEXT.16 1410 JSR DISPLAY.NEXT.16 1420 JSR CROUT 1430 JMP .1 1440 *-------------------------------- 1450 DISPLAY.TS.LIST 1460 JSR CROUT 1470 LDA CUR.TRACK 1480 LDX CUR.SECTOR 1490 JSR PR.TS 1500 LDA #":" 1510 JSR COUT 1520 LDA #" " 1530 JSR COUT 1540 JSR COUT 1550 LDY #0 1560 .1 LDA BUF+13,Y SECTOR 1570 TAX 1580 LDA BUF+12,Y TRACK 1590 BEQ .2 ...END OF LIST 1600 JSR PR.TS 1610 LDA #" " 1620 JSR COUT 1630 TYA 1640 AND #$0F 1650 CMP #$0E 1660 BNE .3 1670 JSR SEVEN.SPACES 1680 .3 INY 1690 INY 1700 CPY #-12 1710 BCC .1 1720 .2 RTS 1730 *-------------------------------- 1740 DISPLAY.NEXT.16 1750 JSR SEVEN.SPACES 1760 .1 LDA BUF,Y 1770 JSR PRBYTE 1780 LDA #" " 1790 JSR COUT 1800 INY 1810 TYA 1820 AND #$0F 1830 BNE .1 1840 TYA 1850 SEC 1860 SBC #16 1870 TAY 1880 .2 LDA BUF,Y 1890 ORA #$80 1900 CMP #$A0 1910 BCS .3 1920 LDA #"." 1930 .3 JSR COUT 1940 INY 1950 TYA 1960 AND #$0F 1970 BNE .2 1980 RTS 1990 *-------------------------------- 2000 SEVEN.SPACES 2010 JSR CROUT 2020 LDA #" " 2030 LDX #7 2040 .4 JSR COUT 2050 DEX 2060 BNE .4 2070 RTS 2080 *-------------------------------- 2090 PR.TS 2100 JSR PRBYTE 2110 LDA #"-" 2120 JSR COUT 2130 TXA 2140 ORA #"0" 2150 CMP #$BA 2160 BCC .1 2170 ADC #6 2180 .1 JMP COUT 2190 *-------------------------------- 2200 * READ NEXT SECTOR 2210 *-------------------------------- 2220 READ.NEXT.SECTOR 2230 LDX CUR.SECTOR 2240 LDY CUR.TRACK 2250 DEX NEXT SECTOR 2260 BPL .1 ...SAME TRACK 2270 LDX #15 ...NEXT TRACK 2280 INY 2290 CPY #35 2300 BCS .2 ...END OF DISK 2310 .1 STY CUR.TRACK 2320 STX CUR.SECTOR 2330 JSR READTS 2340 CLC 2350 .2 RTS 2360 *-------------------------------- 2370 READTS STX IOB.SECTOR 2380 STY IOB.TRACK 2390 .2 LDA /IOB 2400 LDY #IOB 2410 JSR ENTER.RWTS 2420 BCS .2 ...TRY AGAIN IF ERROR 2430 RTS 2440 *--------------------------------- 2450 * IOB FOR RWTS CALLS 2460 *--------------------------------- 2470 IOB 2480 IOB.TYPE .HS 01 0--MUST BE $01 2490 IOB.SLOT16 .HS 60 1--SLOT # TIMES 16 2500 IOB.DRIVE .HS 01 2--DRIVE # (1 OR 2) 2510 IOB.VOLUME .HS 00 3--DESIRED VOL # (0 MATCHES ANY) 2520 IOB.TRACK .BS 1 4--TRACK # (0 TO 34) 2530 IOB.SECTOR .BS 1 5--SECTOR # (0 TO 15) 2540 IOB.PNTDCT .DA DCT 6--ADDRESS OF DCT 2550 IOB.BUFFER .DA BUF 8--ADDRESS OF DATA 2560 IOB.SECTSZ .DA 256 10--# BYTES IN A SECTOR 2570 IOB.OPCODE .HS 01 12--0=SEEK, 1=READ, 2=WRITE, OR 4=FORMAT 2580 IOB.ERROR .BS 1 13--ERROR CODE: 0, 8, 10, 20, 40, 80 2590 IOB.ACTVOL .BS 1 14--ACTUAL VOLUME # FOUND 2600 IOB.PRVSLT .HS 60 15--PREVIOUS SLOT # 2610 IOB.PRVDRV .HS 01 16--PREVIOUS DRIVE # 2620 *-------------------------------- 2630 DCT .HS 0001EFD8 2640 *-------------------------------- 2650 BUF .BS 256 2660 *-------------------------------- |
1000 *SAVE S.BIG CATALOG DISPLAY 1010 *-------------------------------- 1020 CAT.SECTOR .EQ 0 1030 CAT.TRACK .EQ 1 1040 CNTR .EQ 2 1050 PNTR .EQ 3,4 1060 TS.TRACK .EQ 5 1070 TS.SECTOR .EQ 6 1080 *--------------------------------- 1090 COUT .EQ $FDED 1100 CROUT .EQ $FD8E 1110 PRBYTE .EQ $FDDA 1120 ENTER.RWTS .EQ $3D9 1130 *-------------------------------- 1140 BIG.CATALOG.DISPLAY 1150 LDA #15 1160 STA CAT.SECTOR 1170 LDA #17 1180 STA CAT.TRACK 1190 .1 JSR READ.NEXT.CATALOG.SECTOR 1200 BPL .2 GOT A SECTOR 1210 .4 RTS 1220 .2 LDA #BUF 1230 STA IOB.BUFFER 1240 LDA /BUF 1250 STA IOB.BUFFER+1 1260 *-------------------------------- 1270 LDA #CAT+11 1280 STA PNTR 1290 LDA /CAT+11 1300 STA PNTR+1 1310 LDA #7 1320 STA CNTR 1330 .3 JSR DISPLAY.DATA.FOR.ONE.FILE 1340 BCS .4 ...END OF CATALOG 1350 LDA PNTR 1360 ADC #35 1370 STA PNTR 1380 LDA PNTR+1 1390 ADC #0 1400 STA PNTR+1 1410 DEC CNTR 1420 BNE .3 1430 JSR CROUT 1440 JMP .1 1450 *-------------------------------- 1460 DISPLAY.DATA.FOR.ONE.FILE 1470 LDY #0 1480 LDA (PNTR),Y 1490 BNE .1 1500 SEC 1510 RTS 1520 .1 BPL .15 1530 LDY #32 1540 LDA (PNTR),Y REAL TRACK OF DELETED FILE 1550 LDY #0 1560 .15 STA TS.TRACK 1570 INY 1580 LDA (PNTR),Y 1590 STA TS.SECTOR 1600 INY 1610 LDA (PNTR),Y GET FILE TYPE 1620 JSR PRBYTE 1630 LDA #"-" 1640 JSR COUT 1650 JSR COUT 1660 .2 INY 1670 LDA (PNTR),Y PRINT FILE NAME 1680 JSR COUT 1690 CPY #31 DON'T PRINT LAST CHAR OF NAME 1700 BCC .2 1710 INY 1720 INY 1730 LDA (PNTR),Y 1740 JSR PRBYTE 1750 INY 1760 LDA (PNTR),Y 1770 JSR PRBYTE 1780 *---READ T/S LIST---------------- 1790 LDX TS.SECTOR 1800 CPX #16 1810 BCS .9 1820 LDY TS.TRACK 1830 CPY #35 1840 BCS .9 1850 JSR READTS 1860 JSR DISPLAY.TS.LIST 1870 *---READ FIRST DATA SECTOR------- 1880 LDY BUF+12 1890 CPY #35 1900 BCS .9 1910 LDX BUF+13 1920 CPX #16 1930 BCS .9 1940 JSR READTS 1950 *---DISPLAY FIRST 64 BYTES------- 1960 LDY #0 1970 JSR DISPLAY.NEXT.16 1980 JSR DISPLAY.NEXT.16 1990 JSR DISPLAY.NEXT.16 2000 JSR DISPLAY.NEXT.16 2010 .9 JSR CROUT 2020 CLC 2030 RTS 2040 *-------------------------------- 2050 DISPLAY.TS.LIST 2055 .LIST OFF 2060 JSR CROUT 2070 LDA TS.TRACK 2080 LDX TS.SECTOR 2090 JSR PR.TS 2100 LDA #":" 2110 JSR COUT 2120 LDA #" " 2130 JSR COUT 2140 JSR COUT 2150 LDY #0 2160 .1 LDA BUF+13,Y SECTOR 2170 TAX 2180 LDA BUF+12,Y TRACK 2190 BEQ .2 ...END OF LIST 2200 JSR PR.TS 2210 LDA #" " 2220 JSR COUT 2230 TYA 2240 AND #$0F 2250 CMP #$0E 2260 BNE .3 2270 JSR SEVEN.SPACES 2280 .3 INY 2290 INY 2300 CPY #-12 2310 BCC .1 2320 .2 RTS 2325 .LIST ON 2330 *-------------------------------- 2340 DISPLAY.NEXT.16 2345 .LIST OFF 2350 JSR SEVEN.SPACES 2360 .1 LDA BUF,Y 2370 JSR PRBYTE 2380 LDA #" " 2390 JSR COUT 2400 INY 2410 TYA 2420 AND #$0F 2430 BNE .1 2440 TYA 2450 SEC 2460 SBC #16 2470 TAY 2480 .2 LDA BUF,Y 2490 ORA #$80 2500 CMP #$A0 2510 BCS .3 2520 LDA #"." 2530 .3 JSR COUT 2540 INY 2550 TYA 2560 AND #$0F 2570 BNE .2 2580 RTS 2585 .LIST ON 2590 *-------------------------------- 2600 SEVEN.SPACES 2605 .LIST OFF 2610 JSR CROUT 2620 LDA #" " 2630 LDX #7 2640 .4 JSR COUT 2650 DEX 2660 BNE .4 2670 RTS 2675 .LIST ON 2680 *-------------------------------- 2690 PR.TS 2695 .LIST OFF 2700 JSR PRBYTE 2710 LDA #"-" 2720 JSR COUT 2730 TXA 2740 ORA #"0" 2750 CMP #$BA 2760 BCC .1 2770 ADC #6 2780 .1 JMP COUT 2785 .LIST ON 2790 *-------------------------------- 2800 * READ NEXT CATALOG SECTOR 2810 *-------------------------------- 2820 READ.NEXT.CATALOG.SECTOR 2830 LDA #CAT 2840 STA IOB.BUFFER 2850 LDA /CAT 2860 STA IOB.BUFFER+1 2870 LDX CAT.SECTOR 2880 LDY CAT.TRACK 2890 JSR READTS 2900 DEC CAT.SECTOR 2910 RTS 2920 *-------------------------------- 2930 READTS STX IOB.SECTOR 2935 .LIST OFF 2940 STY IOB.TRACK 2950 .2 LDA /IOB 2960 LDY #IOB 2970 JSR ENTER.RWTS 2980 BCS .2 ...TRY AGAIN IF ERROR 2990 RTS 2995 .LIST ON 3000 *--------------------------------- 3010 * IOB FOR RWTS CALLS 3020 *--------------------------------- 3030 IOB 3035 .LIST OFF 3040 IOB.TYPE .HS 01 0--MUST BE $01 3050 IOB.SLOT16 .HS 60 1--SLOT # TIMES 16 3060 IOB.DRIVE .HS 01 2--DRIVE # (1 OR 2) 3070 IOB.VOLUME .HS 00 3--DESIRED VOL # (0 MATCHES ANY) 3080 IOB.TRACK .BS 1 4--TRACK # (0 TO 34) 3090 IOB.SECTOR .BS 1 5--SECTOR # (0 TO 15) 3100 IOB.PNTDCT .DA DCT 6--ADDRESS OF DCT 3110 IOB.BUFFER .DA BUF 8--ADDRESS OF DATA 3120 IOB.SECTSZ .DA 256 10--# BYTES IN A SECTOR 3130 IOB.OPCODE .HS 01 12--0=SEEK, 1=READ, 2=WRITE, OR 4=FORMAT 3140 IOB.ERROR .BS 1 13--ERROR CODE: 0, 8, 10, 20, 40, 80 3150 IOB.ACTVOL .BS 1 14--ACTUAL VOLUME # FOUND 3160 IOB.PRVSLT .HS 60 15--PREVIOUS SLOT # 3170 IOB.PRVDRV .HS 01 16--PREVIOUS DRIVE # 3175 .LIST ON 3180 *-------------------------------- 3190 DCT .HS 0001EFD8 3200 *-------------------------------- 3210 BUF .BS 256 3220 CAT .BS 256 3230 *-------------------------------- 3235 .LIST OFF |
The idea for the following program came from some similar code in the Cirtech Flipster software. Their "program manager" software displays a series of messages and menus in selected windows using a simple subroutine.
The windows are not quite as sophisticated as you may be used to if you are a Macintosh fan. This program divides the screen up vertically, with each window running the full screen width. Calls to the program specify which window to write a message into. The JSR MSG.IN.WINDOW is followed by a single byte specifying which window to use, the ASCII text of the message, and a final 00 byte signifying the end of message. MSG.IN.WINDOW first sets up the window, then clears it, then displays the message in it, and then returns to continue execution right after the 00 byte. MSG.IN.WINDOW does not make any provision for saving the previous contents of the screen inside the window and restoring it later. As I said, this is much simpler than Mac windows.
The Apple monitor has built-in window capability, with the current window being defined by four bytes in page zero. $20 is called LEFT, and defines the starting column of a screen line. This is normally 0, meaning the first column. $21 is called WIDTH, and specifies how many characters are in each line. This is usually 40 ($28), but may be 80 ($50) in a //c or enhanced //e in 80-column mode. MSG.IN.WINDOW does not make any changes to LEFT or WIDTH, although you could modify it to do so.
$22 is called WNDTOP, and specifies the top line of the working window. This is usually 0, meaning to start at the top of the screen. It could be as large as 23 ($17), meaning the bottom line of the screen. $23 is called WNDBOT, and specifies the bottom line of the working window. The number in WNDBOT is actually the number of the next line below the working window, and is usually 24 ($18) to specify a window that goes all the way to the bottom of the screen. MSG.IN.WINDOW stores new values in WNDTOP and WNDBOT, according to a table of line numbers called WINDOW.DATA.
My WINDOW.DATA table lists six different windows, but of course you could have as many as you wish. They can even overlap. The table I used contains the line numbers 0, 24, 0, 3, 9, 18, 20, and 24. This corresponds to the following windows:
Index WNDTOP WNDBOT Window 0 0 24 0-23 <full screen> 1 <better not use!!!> 2 0 3 0-2 3 3 9 3-8 4 9 18 9-17 5 18 20 18-19 6 20 24 20-23
Lines 1080-1130 in the listing below detail the calling sequence for MSG.IN.WINDOW. The test program in lines 1500 and following shows some actual calls, with a "wait for any keystroke" between messages so you can see it happen.
Lines 1140-1180 save the caller's return address, placed on the stack by the JSR MSG.IN.WINDOW. This address will be used to pick up the calling parameters, and then used to return to the calling program. The subroutine in lines 1400-1460 increments the pointer and picks up the next byte from the calling sequence.
When we are finished displaying the message, the pointer will be pointing at the terminal 00 byte. Placing the pointer address back on the stack lets us use an RTS opcode to return to the caller. This is done in lines 1340-1390.
Lines 1200-1250 pick up the window index from the first byte following the JSR instruction. This indexes the WINDOW.DATA table, so two entries from that table are moved into WNDTOP and WNDBOT. The the monitor HOME subroutine can be called to clear the window and place the cursor in the top-left corner of the window.
Lines 1270-1330 display the message, if any. If there is no message, there still must be a terminal 00 byte. By judicious use of 8D (return) and 8A (linefeed) characters, you can display the message any way you like. If the message is too large for the window, lines will be scrolled out the top of the window and lost.
The MSG.IN.WINDOW subroutine illustrates a commonly used technique of placing messages to be printed "in-line", like PRINT "message" statements in Applesoft. I personally prefer to collect all my messages together, and use a message number in a register to select which one to print. One problem with my preferred method is that my programs are then easier to disassemble ... if that is a problem. The 6502 was not designed for easy transfer of calling parameters which follow the JSR. (The 65816 makes this kind of code easier, with its stack-relative address mode.)
1000 *SAVE S.MSG.INTO.WNDW 1010 *-------------------------------- 1020 HOME .EQ $FC58 1030 COUT .EQ $FDED 1040 *-------------------------------- 1050 PNTR .EQ $00,01 1060 WNDTOP .EQ $22 1070 WNDBOT .EQ $23 1080 *-------------------------------- 1090 * CALL: JSR MSG.IN.WINDOW 1100 * .DA #<window number> 1110 * .AS text of message 1120 * .HS 00 <end of msg flag> 1130 *-------------------------------- 1140 MSG.IN.WINDOW 1150 PLA GET RETURN ADDRESS INTO PNTR 1160 STA PNTR LO BYTE 1170 PLA 1180 STA PNTR+1 HI BYTE 1190 *---SETUP WINDOW TOP & BOTTOM---- 1200 JSR GET.NEXT.CALL.BYTE 1210 TAX WINDOW INDEX 1220 LDA WINDOW.DATA,X 1230 STA WNDTOP 1240 LDA WINDOW.DATA+1,X 1250 STA WNDBOT 1260 JSR HOME CLEAR THE WINDOW 1270 *---DISPLAY MESSAGE, IF ANY------ 1280 LDY #0 1290 .1 JSR GET.NEXT.CALL.BYTE 1300 BEQ .2 END OF MESSAGE 1310 ORA #$80 ...JUST IN CASE 1320 JSR COUT 1330 JMP .1 1340 *---RETURN TO CALLER------------- 1350 .2 LDA PNTR+1 HI BYTE 1360 PHA 1370 LDA PNTR LO BYTE 1380 PHA 1390 RTS 1400 *-------------------------------- 1410 GET.NEXT.CALL.BYTE 1420 INC PNTR LO BYTE 1430 BNE .1 1440 INC PNTR+1 HI BYTE 1450 .1 LDA (PNTR),Y 1460 RTS 1470 *-------------------------------- 1480 WINDOW.DATA 1490 .DA #0,#24,#0,#3,#9,#18,#20,#24 1500 *-------------------------------- 1510 T 1520 JSR MSG.IN.WINDOW 1530 .DA #2 TOP WINDOW 1540 .AS -/TOP LINE OF THE SCREEN/ 1550 .HS 8D 1560 .AS -/SECOND LINE OF THE SCREEN/ 1570 .HS 8A 1580 .AS -/...AND THE THIRD/ 1590 .HS 00 END MSG 1600 JSR W 1610 JSR MSG.IN.WINDOW 1620 .DA #6 BOTTOM WINDOW 1630 .AS -/LINE 21/ 1640 .HS 8A 1650 .AS -/...LINE 22/ 1660 .HS 8A.8A 1670 .AS -/...AND LINE 24/ 1680 .HS 00 END MSG 1690 JSR W 1700 JSR MSG.IN.WINDOW 1710 .DA #0 FULL SCREEN 1720 .AS -/MY MESSAGE/ 1730 .HS 00 END MSG 1740 RTS 1750 *-------------------------------- 1760 W LDA $C000 WAIT FOR KEY BEFORE CONTINUING 1770 BPL W 1780 STA $C010 1790 RTS 1800 *-------------------------------- |
The 6502 allows two kinds of addition and subtraction operations, depending on the state of the D-bit in the status register. After a SED (Set D) instruction, the ADC and SBC instructions will operate in decimal mode; after CLD (CLear D), ADC and SBC will operate in binary mode.
In decimal mode the range of values in a byte is from $00 to $99. The left nybble is the ten's digit, and the right nybble is the unit's digit. The decimal mode makes some programs much easier to write, and others more difficult. Having both modes is nice.
In binary mode, if you want to divide by four you just shift the value right two bit-positions. If by 8, shift 3 times. And so on. In decimal mode, you can very easily divide by powers of ten; however, dividing by four is more difficult.
I needed a quick way to tell if a number in decimal mode was divisible by four. After inspecting the binary values of the decimal-mode numbers between 00 and 99 a, I found a way. If the ten's digit is even and the unit's digit 0, 4, or 8, the number is divisible by four. Also, if the ten's digit is odd and the unit's digit is 2 or 6, the number is divisible by four. This can be tested as follows:
LDA VALUE AND #$13 BEQ ... ...TEN'S EVEN, UNITS=0,4,8 EOR #$12 BEQ ... ...TEN'S ODD, UNITS=2,6 ... ...NOT DIVISIBLE
Next I needed a way to actually divide by four. Again I started by inspecting the various values involved. Simply shifting right twice does not do the job, except for numbers less than ten. You cannot even divide by two by simply shifting right once, unless the ten's digit is even. Hmmm.... If the ten's digit is odd, I could subtract 6 frist and then shift right once to divide by two. Doing all that twice would result in a division by four. The subtraction must be done in binary mode, not decimal. The subroutine below in lines 1460-1590 will divide the decimal number in VALUE by four, truncating any remainder, and return the quotient in the A-register. Lines 1600-1700 show a shorter way to divide by two, provided you don't mind using the X-register.
To test my subroutines, I wrote some test programs. The first program, lines 1000-1370, runs through the values 00 to 99, printing ten values to a line. Each number that is evenly divisible by four is flagged with an asterisk. The second program, lines 1720-1990, shows the quotient after calling DIVIDE.BCD.VALUE.BY FOUR.
I am sure there must be lots of other neat tricks possible by combining binary and decimal modes in the 6502. Do you know some? Send them in, and we will publish the best!
1000 *SAVE BCD.MAGIC 1010 *-------------------------------- 1020 CROUT .EQ $FD8E 1030 PRBYTE .EQ $FDDA 1040 COUT .EQ $FDED 1050 *-------------------------------- 1060 VALUE .EQ 0 1070 *-------------------------------- 1080 T 1090 LDA #0 FOR VALUE = 0 TO $FF 1100 .1 STA VALUE 1110 LDA #" " 1120 JSR COUT 1130 LDA VALUE 1140 JSR PRBYTE 1150 *-------------------------------- 1160 JSR IS.BCD.VALUE.DIVISIBLE.BY.FOUR 1170 BEQ .2 ...YES 1180 LDA #" " ...NO 1190 .HS 2C 1200 .2 LDA #"*" 1210 JSR COUT 1220 *-------------------------------- 1230 LDA #" " SEPARATE ITEMS IN CHART 1240 JSR COUT 1250 LDA VALUE NEW LINE AFTER TEN VALUES 1260 AND #$0F 1270 CMP #9 1280 BNE .3 1290 JSR CROUT 1300 *---NEXT VALUE------------------- 1310 .3 SED MUST DO ARITHMETIC 1320 LDA VALUE IN DECIMAL MODE 1330 CLC 1340 ADC #1 1350 CLD BACK TO BINARY 1360 BCC .1 ...UNTIL WRAP-AROUND 1370 RTS 1380 *-------------------------------- 1390 IS.BCD.VALUE.DIVISIBLE.BY.FOUR 1400 LDA VALUE RETURN .EQ. STATUS IF YES 1410 AND #$13 .NE. STATUS IF NOT 1420 BEQ .1 1430 EOR #$12 1440 .1 RTS 1450 *-------------------------------- 1460 DIVIDE.BCD.VALUE.BY.FOUR 1470 LDA VALUE 1480 JSR DIVIDE.BCD.VALUE.BY.TWO 1490 DIVIDE.BCD.VALUE.BY.TWO 1500 PHA 1510 AND #$10 1520 BEQ .1 1530 PLA 1540 SBC #6 1550 LSR 1560 RTS 1570 .1 PLA 1580 LSR 1590 RTS 1600 *-------------------------------- 1610 SHORTER.DIV.BY.TWO 1620 LSR 1630 TAX 1640 AND #8 1650 BEQ .1 1660 DEX 1670 DEX 1680 DEX 1690 .1 TXA 1700 RTS 1710 *-------------------------------- 1720 D 1730 LDA #0 FOR VALUE = 0 TO $FF 1740 .1 STA VALUE 1750 LDA #" " 1760 JSR COUT 1770 LDA VALUE 1780 JSR PRBYTE 1790 LDA #"." 1800 JSR COUT 1810 *-------------------------------- 1820 JSR DIVIDE.BCD.VALUE.BY.FOUR 1830 JSR PRBYTE 1840 *-------------------------------- 1850 LDA #" " SEPARATE ITEMS IN CHART 1860 JSR COUT 1870 LDA VALUE NEW LINE AFTER TEN VALUES 1880 AND #$0F 1890 CMP #9 1900 BNE .3 1910 JSR CROUT 1920 *---NEXT VALUE------------------- 1930 .3 SED MUST DO ARITHMETIC 1940 LDA VALUE IN DECIMAL MODE 1950 CLC 1960 ADC #1 1970 CLD BACK TO BINARY 1980 BCC .1 ...UNTIL WRAP-AROUND 1990 RTS |
The ProDOS version of the S-C Macro Assembler is carefully written to operate in either 40 or 80 columns. When you boot the disk the assembler starts out in the 40 column mode, because we couldn't take for granted that you would have (or want) the 80 column display. Well it turns out that most people (myself included) are using 80 columns and are getting tired of typing PR#3 every time they start up the assembler.
Marc Wolfgram called up today from Wisconsin to ask how to make the assembler start up in 80 columns, and that finally got me around to finding out how. It's embarassingly easy: just a two-byte patch. Here's the procedure, assuming you're in S-C Macro Assembler ProDOS:
UNLOCK SCASM.SYSTEM BLOAD SCASM.SYSTEM,A$2000,TSYS $6001:00 C3 BSAVE SCASM.SYSTEM,A$2000,TSYS,L17920 LOCK SCASM.SYSTEM
We just changed the IO.INIT call from JMP MON.HOME to JMP $C300, and that's all there is to it! Now the next time you boot up, the assembler will be in 80 column mode. RESET will return you to 40 columns. PR#3 or NEW will restore 80 columns.
Thanks, Marc, for prompting me to find out about this.
A freshly initialized DOS 3.3 disk has 496 free sectors, less whatever is used by your HELLO program. There are 16 more sectors that are either never used or which are wasted, in tracks 0 and 2. The following program modifies the code which writes the DOS image and the code which reads it back during boot, so that the entire image fits in tracks 0 and 1. A further change makes the space in track 2 available for normal files.
The new boot procedure actually is faster than the standard one, and all the new code takes less space than that which is replaced. All you give up is the ability to boot into machines with less than 48K. Does anyone still have one?
Standard DOS 3.3 stores the DOS image in two pieces. The code destined for $B600-BFFF is on track 0, sectors 0 through 9. The code for $9D00-B500 follows, from track 0/sector 10 through track 2/sector 4. Sectors 5-15 of track 2 are not used. The information stored in sectors 3 and 4 of track 2 (aimed at $B400-B5FF) is useless, because all this space is variables for DOS which do not need to be initialized. The same goes for sector 5 of track 0. The contents of sectors 10 and 11 of track 0 is not used on a "slave" disk, which is what you get with the INIT command. My disks have to stay slave disks, because we are going to reshuffle everything around so all the unused sectors end up in track 2.
My new layout stores $9D00-9DFF in track 0/sector 5, and $9E00-B3FF in track 0/sector 10 through track 1/sector 15. The following table summarizes the old and new layouts.
Sector Track 0 Track 1 Track 2 Old New Old New Old New 0 B6 B6 A1 A4 B1 .. 1 B7 B7 A2 A5 B2 .. 2 B8 B8 A3 A6 B3 .. 3 B9 B9 A4 A7 B4 .. 4 BA BA A5 A8 B5 .. 5 BB 9D A6 A9 .. .. 6 BC BC A7 AA .. .. 7 BD BD A8 AB .. .. 8 BE BE A9 AC .. .. 9 BF BF AA AD .. .. 10 .. 9E AB AE .. .. 11 .. 9F AC AF .. .. 12 9D A0 AD B0 .. .. 13 9E A1 AE B1 .. .. 14 9F A2 AF B2 .. .. 15 A0 A3 B0 B3 .. ..
I published the complete commented disassembly of the code which writes the DOS image on a disk and the code for the second stage boot in AAL way back in October, 1981. The second stage boot code begins at $B700, and the DOS writer starts at $B74A. They both use a subroutine at $B793 to read/write a range of sectors. I preserved the starting points for these two routines in the program which follows, but there is a lot of new empty space. If you are interested, you could go ahead and shove all the code segments together, patch all the calls for the new locations, and get one big area of free space for adding new features.
I was able to save coding space in several ways. First, by deciding that I would not worry about running in less than 48K. Second, that I could eliminate the extra code used to clobber the language card. This is a very common patch anyway, because most of us do not want to have to keep re-loading the language card area just because we re-boot DOS. Third, by eliminating the redundant calls to $FE89 and $FE93. The first stage boot does both of these just before jumping to the second stage boot, so there is no reason to do them again. And fourth, by being more efficient. If you want to, you can save even more by doing away with the subroutine at $B7C2: part of it is redundant, and the rest can be combined with the code at $B74A.
The standard DOS boot first loads $B600-BFFF from track 0, and then skips out to track 2 to read the rest hind-end-first. The track steps are 0-1-2-1-0. My new version starts in track 0, reads it all, then reads all of track 1, and it is done. The track steps are simply 0-1. It is a lot faster. However, the overall boot time is not significantly faster, due to the time spent finding track 0 in the first place, and the time spent loading the HELLO program.
Lines 1060-1140 install the new code. The entire $B7 page is replaced, as well as a single byte at $AEB3. This byte changes the VTOC on the newly initialized disk so that track 2 is available. While I was looking at this area, I noticed that the VTOC written on the new disk is not necessarily correct. DOS does not create an entirely new VTOC for the new disk. The bitmap area is new, and several other bytes are set up. However, DOS does not store any values in the bytes which tell how many tracks, sectors per track, sector size, and T/S entries per T/S list. This means that if the last access to a disk prior to initializing a new one was to a non-standard disk, the VTOC may be incorrect on the new disk. If I load a file from a large volume on my Sider, and then INIT a floppy, the floppy's VTOC indicates 32 sectors per track and 50 tracks! Ouch! Beware!
Lines 1180-1480 are the second stage boot code. The first stage boot is located at $B601, and actually executed at $801. It loads in sectors 0-9 of track 0 into $B600-BFFF, calls $FE89 and $FE93 to set the standard 40-column input hooks, and then jumps to $B700 with the slot*16 in the X-register. My stage two begins by copying the information which came from sector 5, now found at $BB00-BBFF, to the place it belongs at $9D00-9DFF (lines 1270-1320). Next I set up a call to my RWFT subroutine.
RWFT stands for Read/Write From Table. I have a table that describes all of the segments which must be loaded from the disk during boot, or written during initialization. Stage two boot must read the same things written by initialization, but init-ing requires first writing the stuff which will be loaded by stage one boot. Stage two boot calls RWFT with A=1 (read opcode for the IOB) and Y=2 (skipping the first two entries in the table). Initialization calls RWFT with A=2 (write opcode) and Y=0 (start at the beginning of the table).
RWFT gets four items out of the table for each step. The page number and sector number indicate the end of the range to be read or written. The count tells how many pages (or sectors) need to be read or written. All of the sectors must be in the track specified by the table entry. After one range has been read, RWFT steps to the next. The table terminates when the page address of 0 is found.
For some reason the code at $AEFF looks like this:
AEFF- JSR $B7C2 AF02- JSR $B74A
Both of these subroutines are never called from any other place, so they could be combined into one. Doing so would save several bytes. Furthermore, at least with my new RWFT program, lines 2120 and 2130 could be deleted, saving six more bytes.
There are still more ways to increase the storage on standard floppies, as you probably know. You can shorten the catalog, make a few other patches, and use some sectors in track 17 ($11).
You can usually use more than 35 tracks, since most drives will handle at least 36 and many a full 40. This also only takes a few simple patches. At $AEB5 you normally find a value $8C. Add 4 to this value for each additional track. This controls the loop that builds the bitmap of available sectors in the VTOC. The byte at $BEFE controls how many tracks the formatter in RWTS lays down. It is normally $23 (decimal 35), so add one for each additional track. Just before you start the INIT command, change the byte at $B3EF. This is normally $23, the number of tracks. Add 1 for each additional track. You have to be sure to do this last patch just prior to the INIT, because reading or writing another disk will cause it to be changed back.
Incidentally, this reminds me of the potential bug I mentioned above regarding writing out an incorrect VTOC. Once today I tried to catalog a disk that had been only partially initialized. The tracks had been written, but no VTOC or catalog sectors were. Of course I got an I/O ERROR. Next I decided to INIT that same disk. It went through the formatting stage, then bombed out with an I/O error when trying to write the catalog. Looking at the VTOC on this disk, the bytes for number of tracks, et cetera, were all zero!.
Now back to extra tracks. After making a disk with the extra tracks, you really need to check them to be sure your drive handles them. Use a disk zap program and try to write on the last track. Then try to write on the previous track. If your drive will go out that far, you will be successful. If you get an error trying to find the next to the last track, keep backing up until you find a track that does work. All the ones in between were written in the same location on the disk surface as the last track. If there were any missing tracks, you need to reformat the disk with fewer tracks.
An interesting side note to this discussion is that you could format a disk with LESS than 35 tracks if you wish. Just so you at least include track 17 ($11), you can reduce the values at $BEFE, $B3EF, and $AEB5 and stop short of a full disk. Some copy protection schemes do this, along with other tricks, to frustrate the making of copies.
1000 *SAVE S.B700-B7FF DOS 3.3 1010 *--------------------------------- 1020 FMP.SUBCOD .EQ $B5BC 1030 FMW.VOLUME .EQ $B5F9 1040 RWTS .EQ $BD00 1050 *--------------------------------- 1060 INSTALL 1070 LDY #0 COPY NEW CODE INTO DOS 1080 .1 LDA NEW.B700,Y $B700...B7FF 1090 STA $B700,Y 1100 INY 1110 BNE .1 1120 LDA #8 PATCH TO INCLUDE TRACK 2 1130 STA $AEB3 AS FREE SPACE 1140 RTS 1150 *--------------------------------- 1160 NEW.B700 .PH $B700 1170 *-------------------------------- 1180 BOOT.STAGE2 1190 STX IOB.SLOT16 1200 STX IOB.PRVSLT 1210 TXA SLOT*16 1220 LSR GET SLOT # 1230 LSR 1240 LSR 1250 LSR 1260 TAX X = SLOT NUMBER 1270 *---COPY BB00-FF TO 9D00-FF------ 1280 LDY #0 1290 .1 LDA $BB00,Y 1300 STA $9D00,Y 1310 DEY 1320 BNE .1 1330 *---SET CURRENT TRACKS @ 0------- 1340 TYA A = Y = 0 1350 STA $4F8,X 1360 STA $478,X 1370 *---BUILD RWFT CALL-------------- 1380 INY Y = 1 1390 STY IOB.PRVDRV 1400 STY IOB.DRIVE DRIVE = 1 1410 TYA A = 1 (READ OPCODE) 1420 INY Y = 1 (RWFT INDEX) 1430 JSR RWFT 1440 *---COLD START DOS--------------- 1450 LDX #$FF 1460 TXS EMPTY STACK 1470 STX IOB.VOLUME 1480 JMP $9D84 DOS HARD ENTRY 1490 *-------------------------------- 1500 .BS $B74A-* <<<FILLER>>> 1510 *--------------------------------- 1520 * WRITE DOS IMAGE ON TRACKS 0-2 1530 *--------------------------------- 1540 WRITE.DOS.IMAGE 1550 LDA #2 WRITE OPCODE FOR RWTS 1560 LDY #0 RWFT INDEX 1570 *-------------------------------- 1580 * READ/WRITE FROM TABLE 1590 *-------------------------------- 1600 RWFT 1610 STA IOB.OPCODE 1620 .1 STY RWFT.INDEX 1630 LDA RWFT.ADDR,Y 1640 BEQ .3 ...END OF RWFT TABLE 1650 STA IOB.BUFFER+1 1660 LDA RWFT.TRACK,Y 1670 STA IOB.TRACK 1680 LDA RWFT.SECTOR,Y 1690 STA IOB.SECTOR 1700 LDA RWFT.COUNT,Y 1710 STA RWFT.N 1720 .2 LDA /IOB 1730 LDY #IOB 1740 JSR ENTER.RWTS 1750 BCS .2 ...TRY AGAIN IF ERROR 1760 DEC IOB.SECTOR NEXT SECTOR 1770 DEC IOB.BUFFER+1 NEXT PAGE 1780 DEC RWFT.N 1790 BNE .2 1800 LDY RWFT.INDEX 1810 INY 1820 BNE .1 ...ALWAYS 1830 .3 RTS 1840 *-------------------------------- 1850 RWFT.N .BS 1 1860 RWFT.INDEX .BS 1 1870 *-------------------------------- 1880 RWFT.ADDR .HS BF.9D.A3.B3.00 1890 RWFT.TRACK .HS 00.00.00.01 1900 RWFT.SECTOR .HS 09.05.0F.0F 1910 RWFT.COUNT .HS 0A.01.06.10 1920 *-------------------------------- 1930 .BS $B7B5-* <<<FILLER>>> 1940 *--------------------------------- 1950 * ENTER RWTS 1960 *--------------------------------- 1970 ENTER.RWTS 1980 PHP SAVE STATUS ON STACK 1990 SEI DISABLE INTERRUPTS 2000 JSR RWTS CALL RWTS 2010 BCS .1 ERROR RETURN 2020 PLP RESTORE STATUS 2030 CLC SIGNAL NO RWTS ERROR 2040 RTS RETURN TO CALLER 2050 .1 PLP RESTORE STATUS 2060 SEC SIGNAL RWTS ERROR 2070 RTS RETURN TO CALLER 2080 *--------------------------------- 2090 * SET UP RWTS TO WRITE DOS 2100 *--------------------------------- 2110 SETUP.WRITE.DOS 2120 LDA FMP.SUBCOD IMAGE ADDRESS 2130 STA IOB.BUFFER+1 2140 LDA #0 2150 STA IOB.BUFFER 2160 LDA FMW.VOLUME VOLUME # 2170 EOR #$FF UNCOMPLEMENT IT 2180 STA IOB.VOLUME 2190 RTS 2200 *--------------------------------- 2210 * CLEAR 256 BYTES STARTING AT ($42,43) 2220 *--------------------------------- 2230 ZERO.CURRENT.BUFFER 2240 LDA #0 2250 TAY 2260 .1 STA ($42),Y 2270 INY 2280 BNE .1 2290 RTS 2300 *--------------------------------- 2310 .BS $B7E8-* <<<FILLER>>> 2320 *--------------------------------- 2330 * IOB FOR RWTS CALLS 2340 *--------------------------------- 2350 IOB 2360 IOB.TYPE .HS 01 0--MUST BE $01 2370 IOB.SLOT16 .HS 60 1--SLOT # TIMES 16 2380 IOB.DRIVE .HS 01 2--DRIVE # (1 OR 2) 2390 IOB.VOLUME .HS 00 3--DESIRED VOL # (0 MATCHES ANY) 2400 IOB.TRACK .BS 1 4--TRACK # (0 TO 34) 2410 IOB.SECTOR .BS 1 5--SECTOR # (0 TO 15) 2420 IOB.PNTDCT .DA DCT 6--ADDRESS OF DCT 2430 IOB.BUFFER .BS 2 8--ADDRESS OF DATA 2440 IOB.SECTSZ .DA 256 10--# BYTES IN A SECTOR 2450 IOB.OPCODE .BS 1 12--0=SEEK, 1=READ, 2=WRITE, OR 4=FORMAT 2460 IOB.ERROR .BS 1 13--ERROR CODE: 0, 8, 10, 20, 40, 80 2470 IOB.ACTVOL .BS 1 14--ACTUAL VOLUME # FOUND 2480 IOB.PRVSLT .HS 60 15--PREVIOUS SLOT # 2490 IOB.PRVDRV .HS 01 16--PREVIOUS DRIVE # 2500 .BS 2 2510 DCT .HS 0001EFD8 2520 .BS 1 2530 *-------------------------------- |
Apple seems to have installed a bug in the new ROM for the Apple //c which affects DOS 3.3. I am talking about the 3.5 ROM that supports Unidisk 3.5 and AppleTalk.
The new bug manifests itself when you use the control-IxxN command to either serial port. The older //c ROMs accumulated the "xx" number in $47F; the new ones do it in $47E. Location $47E is supposed to be dedicated to slot 6, the slot where the disk drives are. DOS uses $47E to keep track of the current track position for drive 1. So, after doing the serial port command to set line length, the next time DOS tries to look at drive 1 it will have to re-calibrate.
Re-calibration is not a disaster, but it is annoying. A needless and not noiseless waste of time. To avoid it with the new ROMs, you have to save and restore the contents of $47E around any serial port command that involves scanning a numeric field.
I have looked through the entire listing of the 3.5 ROM that came with my upgrade kit, and there does not appear to be any reason why this variable was moved. Location $47F is not used for any new value that I can see.
Even though the Apple //c Technical Reference Manual reserves $47E for the firmware, and ProDOS doesn't use the cell, using a "slot 6" screen-hole for a slot 1 and 2 activity is a serious breach of the protocol for their use that dates back to the earliest Wozdays. I know Apple is dropping (or at least decreasing) their support of DOS 3.3, but this is ridiculous!
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.)