Something Personal
I had another birthday last weekend. Now my age is a perfect square again, for the seventh time. Another 15 years and it will be square again. At most I can expect two or three more square ages. Numbers like these are interesting to me. Moses said, after recalling the brevity and frailty of human life, "So teach us to number our days, that we may apply our hearts unto wisdom."
Certainly we all should echo that prayer. We want our years to count for something. Solomon said, "The fear of the LORD is the beginning of knowledge: but fools despise wisdom and instruction." David said, "The fool has said in his heart, 'There is no God'." James said, "If any of you lack wisdom, let him ask of God, who gives to all men liberally, and upbraids not; and it shall be given him." Paul said, "In Jesus Christ are hidden all the treasures of wisdom and knowledge."
And I say, the longer I live, the more I find that God is there, and faithful to his Word. The better part of wisdom is trusting the truly trustworthy. Solomon says it best: "Trust in the LORD with all your heart, and lean not unto your own understanding. In all your ways acknowledge him, and he shall direct your paths."
As I enter my jubilee year, I re-commit myself to doing just that.
AppleWorks is a lot bigger than a normal Apple. The 6502 microprocessor is restricted to 64K of memory address space, and the fraction of this left to a ProDOS-based program is normally less than 48K. The AppleWorks applications use large areas of memory for the active data, so the amount left for programs is very small. So how does it work? By using overlays.
The file on an AppleWorks Program disk named APLWORKS.SYSTEM (which we have been analyzing over the last four months) contains subroutines which stay in RAM regardless of what activity you are performing. After starting up AppleWorks, this code resides in RAM from $1000 up to $2Exx. RAM from $800 to $FFF is used for various variables and buffers. RAM from $3000 up to $BEFF is used for application programs and data.
All of the rest of the code for the three applications is on the two files named SEG.M0 and SEG.M1. These two files together contain 43 small segments of code, designed to be loaded into RAM only when needed. The first 15 segments appear to me to be the data base application; the next 8, the word processor; and the rest, the spreadsheet. At the beginning of each file is an "index" which tells where in the file each program segment begins. The article "Dissecting AppleWords SEG.M0 and SEG.M1 Files" later in this same issue goes into detail about the structure of the index.
This month we will look in detail at the code inside AppleWorks which loads in the various overlays. Since it happens to be very near the beginning of APLWORKS.SYSTEM, I am also listing the JMP vector that starts at $1000.
Since each of the 43 overlays will need to use various subroutines from APLWORKS.SYSTEM, the author of AppleWorks needed a straightforward way for them to know their addresses. Woz's monitor is a good example of what happens when you do not provide such a method.
Back in 1977 we found hundreds of neat entry points in that tiny little ROM, all at very specific addresses. We used them flagrantly, to save RAM for better uses. Then Apple started taking routines out, moving others, until they finally printed a list of the 110 or so they will continue to support. There is no easy system to using these entry points, because Woz originally coded them with the idea of squeezing the most function into the smallest amount of memory.
The Apple IIgs monitor, on the other hand, is a good example of what happens when you go overboard in trying to provide a calling system. After acquiring over ten pounds of documentation, I still am only dimly understanding all the ins and outs of the toolboxes. I know it all starts by loading a 16-bit coded number into the X-register, and doing a JSL $E10000 command. Parameters are passed on the stack, and an error code is returned in the A-register. All is done very systematically, very cleanly, very macintoshly, but not very efficiently. The toolbox calls must be done in full 16-bit mode, cannot use the registers to pass data, eat up many machine cycles getting to the actual tool and back again, and do require the use of the A- and X-registers. Still, it may be the best way to create, organize, and control an open-ended set of tools in a machine like the IIgs.
ProDOS-8 MLI gives an example of another method, in which a single entry point processes all calls. The byte following the JSR to that entry point contains a command code, and the next two bytes point to a parameter block. ProDOS-16 uses two bytes for the command code and four bytes for the parameter block address.
Robert Lissner uses a simple system of vectoring all subroutine calls through several JMP vectors throughout AppleWorks. Some of his subroutines pass all their data in the three 6502 registers, some use fixed locations in page zero or in the $800-$FFF area, and some use a standard calling sequence with parameters after the JSR. One set of JMP vectors starts at $1000, and is used for calling all of the APLWORKS.SYSTEM subroutines. Another set begins at $D002 in the alternate $D000 bank of RAM, where either SEG.00 or SEG.XM has been loaded. Each overlay segment also begins with a JMP vector.
I have shown the JMP vector for APLWORKS.SYSTEM in lines 1520-2030 of the listing. To save space in the newsletter, I plugged in actual hexadecimal addresses for all those subroutines which are not listed in this issue. Where I have given them names, I included them as comments. The rest of them I will name later, when I get to them and figure out what they do and think of a unique meaningful moniker.
When AppleWorks is first fired up by executing APLWORKS.SYSTEM, one of the tasks is to look for either a 64K (or larger) memory card in the auxiliary slot or a card like RamFactor in one of the other slots. If it finds a RamFactor-like card with enough free memory, it loads SEG.XM into the 4K area at $D000. (RamFactor is Applied Engineerings version of the Apple Memory Card, and of course there are other companies also making these kinds of cards. In the rest of this article I will call this kind of memory SlotRAM memory.) If there is not enough SlotRAM memory available but there is 64K in the AuxRAM, AppleWorks loads SEG.00 instead.
How can it do that? ProDOS supposedly has that $D000 space all tied up!
Well, ProDOS claims it all, but only really USES from $D100 through $D3FF. This is where the standard QUIT code is kept. During initialization AppleWorks copies that $300-byte area to the SEG.PR file, and then loads the appropriate SEG.00 or SEG.XM file. When you QUIT out of AppleWorks, it copies those $300 bytes from SEG.PR back into $D100 before doing the ProDOS Quit call. Note: AppleWorks only saves and restores $300 bytes! If you are using a non-standard Quit processor which takes over $300 bytes, running AppleWorks will clobber it. You will then have to reboot after quitting AppleWorks.
There are 24 subroutines inside the $D000 area which are accessed through a JMP vector starting at $D002. Depending on which SEG.xx is loaded there, they either talk to AuxRAM or SlotRAM. The routines that are of interest this month are equated in lines 1200-1220; listing them will have to wait for a future issue.
I defined some useful macros in lines 1320-1510. Macros are gradually growing on me. When I first added them to the S-C Assembler II, creating the S-C Macro Assembler version 1.0, I really couldn't think of many uses for them beyond sales appeal. Then I started using them for generating various types of data lists, and often-used code sequences like MLI calls. Now I am finding more and more uses.
The MLI.SL macro is a slight modification of my standard MLI-call macro. I added lines 1360-1400 to generate the error-tracking code which Lissner uses. After nearly every call to MLI he uses a BEQ to branch around an INC of the error flag. ProDOS returns with status EQ and carry clear if there was no error, or NE and carry set if there was an error. The various ProDOS manuals make it clear that the carry status is supposed to be the preferred error flag, and I always got the impression that future versions might not support the EQ/NE method. Well, now they will HAVE to continue that support, because the world's most popular Apple program says so. Most other software I have looked at or written uses the CC/CS method, including such basic software as BASIC.SYSTEM.
If I use the SLI.ML macro with only two parameters, it generates only the two-line MLI call. If I add a third parameter, it generates the two extra lines. The third parameter in this macro is never actually used, just counted. The .DO ]#>2 line says "only assemble the following lines (up to the .FIN line) if the number of parameters is greater than two."
By using the private label :1 in the macro definition I avoid having to clutter up the code with lots of extra local or normal labels. This makes the listing easier to read with understanding. For example, look at lines 4340-4360. Those three lines actually generate 12 lines of code with three labels. You do have to remember when you are reading the code what these macros generate, if you are trying to understand the code. In these three lines, remember that any errors cause SEGLOAD.ERROR.FLAG to be incremented. If you want to, you can leave out the .LIST MOFF line that I used at the beginning (the unlisted line 1010). Then all of the code generated by each macro will be listed during assembly. I wanted to suppress the macro expansion listing to make the code easier to understand and to make it fit in the newsletter.
The HS macro is entirely for the purpose of shortening the listing. I use it in the SEGMENT.TABLE definition in lines 2330-2590. This table would take up two or three times as much paper if I did not use the macro.
The MSG macro is useful for defining strings that begin with a character count and include only printing ASCII characters. Since almost all of the text messages included in AppleWorks are like that, MSG is quite useful. I used it here in line 5230.
The SEGMENT.TABLE in lines 2230-2600 keeps track of vital information about the segments. There are four bytes for each segment. The first byte is the page number the segment should be loaded at. For example, Segment 01 has 35 in this byte, so it should be loaded at $3500.
The second byte of each group of four is used to keep track of how long it has been since the segment was loaded. Each time a segment is loaded the second byte for its entry in the SEGMENT.TABLE is zeroed while the second byte for all other entries is incremented, by the code in lines 3220-3360. I call this byte the "age" of a segment. I have not yet found any code which takes advantage of the information in the age-byte, but at least it is there. It may be, or could be, used to determine which segments to keep in AuxRAM or SlotRAM if there is not room for all of them.
The third and fourth bytes of each four-byte entry are 0000 if the segment must be loaded from disk. Naturally, this is what they are initially. After a segment is loaded from disk, if there is room in AuxRAM or SlotRAM it is also copied there. Then these two bytes in the segment table are set to a coded address so that the segment can be downloaded from RAM the next time it is needed.
When you select one of the three applications from the main menu in AppleWorks, LOAD.PROGRAM.SEGMENT.A will be called. I put a lot of information about the calling sequence of this program in the comments in lines 3000-3140. Basically, the segment number will be in the A-register. Lines 3170-3210 save this number and multiply it by four to make an index into the segment table. As already mentioned, lines 3220-3360 then increment the second byte of all entries and zero the second byte for the entry for te segment we want to load.
Lines 3370-3430 check to see if this segment is the same as the last one loaded. If it is, there is nothing left to do so it exits. I say "exits" rather than "returns" because it may or may not return. Bit 7 in the A-register at the time of call controls whether it returns after loading a segment or jumps into the loaded segment. If it does the latter, it jumps to $xx02, where xx is the page number the segment was loaded into. In this case, the X-register is a function number to be interpreted by the segment. When the segment is finished, it may return with an RTS to the program which called LOAD.PROGRAM.SEGMENT.A. The exit options are processed in lines 4810-4880.
If the segment is not the same as the most recently loaded one, line 3430 stores the new segment number in the CURRENTLY.LOADED.SEGMENT variable.
Lines 3440-3520 pick up the load address byte out of the segment table and install it in the various places it will be needed later. If that byte is 00, then the segment does not exist and the program returns with an RTS. This should never happen, of course, and I presume if it did it would be a bug. There are three null segments (0F, 14, and 15), but I would be surprised if any useful purpose is served by trying to load them. On the other hand, if there is some code somewhere which attempts to load all the segments in a range so that they will be copied in to AuxRAM or SlotRAM memory, the null segments might be included in the range without any disastrous effects.
Lines 3530-3570 treat segment $20 in a special way. I am not sure what that segment is, or why it is special. If you are trying to load segment $20 and the flag at $EA7 is non-zero it will not be loaded. Instead the loader will exit, either with an RTS or by using the function call (JMP to $4502 with a code in the X-register). I presume that $EA7 is set non-zero when function $20 is loaded, and stays that way until it is covered up by something else. This would let functions within segment $20 be called without reloading it unnecessarily even when other segments have been loaded after it was. I don't know, it sounds sort of kludgy. I'll just have to wait until I have looked into and disassembled a lot more of the AppleWorks code.
Lines 3580-3690 make sure that the variable CURRENT.APPLICATION is changed whenever you load segments $01, $10, or $18. I haven't found any use for this yet, and I am just hoping I have correctly guessed its significance. I have assumed that those segment numbers are the initially segments for each of the three applications, and have so indicated in the comments in lines 2130-2210.
Lines 3700-3750 look at the third and fourth bytes in the segment table entry to see if they are 0000. If so, the segment must be loaded from the AppleWorks Program disk. If not, lines 3760-3810 download the segment from AuxRAM or SlotRAM memory. The downloading is handled by a subroutine from either SEG.00 or SEG.XM, which I will probably describe in detail in a future issue of AAL. They handle AuxRAM or SlotRAM in interesting ways.
If a segment must be loaded from disk, the subroutine beginning at line 3830 takes over. Lines 3840-3920 modify the general SEG.xx pathname buffer to point to either SEG.M0 or SEG.M1. For some reason the first nine segments are stored on SEG.M0 and the rest on SEG.M1. Once upon a time it probably made sense....
Lines 3930-3970 will then try to open the selected SEG.Mx file. If the file will not open, AppleWorks assumes the only possible reason could be that the disk is not mounted and calls CALL.FOR.AWPROGRAM.DISK (lines 2780-2950) to tell you to do so. The message says "Place the AppleWorks PROGRAM disk in Drive 1 and press Return." Actually you SHOULD be able to place the disk in ANY drive, if you have specified a complete pathname for the program disk. And, actually, you can press any key, not just RETURN.
Once the file has been opened successfully, lines 3990-4050 store the reference number ProDOS assigns the file in all the other IOBs. Line 4060 calls CLR.PRODOS.BITMAP to make sure ProDOS will allow reading into the range $800-9FFF. Lines 4070-4080 clear a byte used as an error flag for all the subsequent MLI calls. After going through all the motions of loading the segment this flag will still contain a zero if no errors were reported by ProDOS.
Lines 4090-4120 read in the first 140 bytes of the SEG.Mx file. The front of the file contains a segment offset table with 3-byte values for each segment. These three bytes tell what offset (or MARK) value to send via the MLI "Set Mark" call before reading in the segment. Subtracting a segment's offset from that of the next segment gives the length of the segment we want to load. Since there are 43 segments, will need 43 3-byte values for starting addresses, plus one more for the zero-th entry, plus still one more for computing the length of the 43rd segment. That is a total of 45*3 bytes, or 135. Appleworks reads 140, which allows for one more segment to be added without changing this constant.
Note that the program goes right on reading the SEG.Mx file whether there is a reading error or not. SEGLOAD.ERROR.FLAG gets incremented if there is an error, but nothing else is done about it until we have gone through all the motions of loading the segment and closing the file. If there was any error at all, lines 4380-4390 find it out and return with an RTS to whoever called the segment loader. This seems a little dangerous, because the user will never know what happened. A glitched AppleWorks Program disk could cause very erratic behavior without any explanation, yet AppleWorks COULD have reported what was wrong and exactly which segment could not be loaded.
Lines 4130-4180 multiply the segment number by 3 to get a pointer into the SEG.Mx segment offset table. Lines 4190-4320 pick up the offset and save it for the Set Mark MLI call, and also compute the segment length for the Read MLI call. Lines 4340-4360 set the mark, read the segment, and close the file. After the segment has been read, line 4370 calls SET.PRODOS.BITMAP to re-protect all RAM from $800 through $9FFF.
The first two bytes of every segment on both SEG.Mx files contains the end address plus 1 of that segment. This value may be different from the number of bytes loaded plus the starting address, if the segment needs some private RAM at the end of itself. However, I haven't found any cases where this is so. We already knew the length in order to read the segment from disk, so recomputing the same value seems like make-work. But who knows? Lines 4400-4520 pick up the address from the beginning of the segment, subtract the loading address, and store the result in SL.LEN. That gives the "uploader" the number of bytes to copy into AuxRAM or SlotRAM.
Lines 4530-4610 compute the actual address of the current segment's entry in the segment table, and save this address at SL.SEG. We are gradually building up the calling sequence for the "uploader". Lines 4620-4710 determine whether there is enough RAM left in either AuxRAM or SlotRAM, whichever is being used, for uploading the segment. If so, lines 4720-4770 call on the uploader to do so. The uploader will set the 3rd and 4th bytes for the segment in the segment table so that future calls to load this segment can download them from RAM instead of reading the AppleWorks Program disk.
Lines 4810-4880 have already been discussed above. They decide whether to return to the caller with an RTS, or to JMP into the segment just loaded with a function code in the X-register.
Lines 4930-5200 are the parameter blocks, or IOBs, used by the various MLI calls in the segment loader.
Lines 5220-5240 define the message used by the program which prompts you to mount the AppleWorks program disk. I used the .PH and .EP because this message does not immediately follow the IOBs in the real code. The .PH address shows where it really is assembled in version 1.3.
Lines 5250-5400 are the two subroutines for de-protecting and protecting RAM from $800 through $9FFF. Why are these necessary? Since nobody calls ProDOS when you are using AppleWorks except AppleWorks, why do we ever need to turn on protection for RAM? Why not turn off all protection at the beginning and leave it that way? If other programs that merge with AppleWorks, such as Beagle Bros. TimeOut series diddle with RAM, they can surely override the BITMAP protection too, so protection doesn't really protect anything anyway! Maybe Lissner put it here to help catch programming errors early in his development cycle, or during checkout of enhancements.
1000 *SAVE AW.SRC.V8N6 1010 .LIST MOFF Do not list macro expansions 1020 ***missing line above was ".LIST MOFF", which 1030 ***shrinks the listing by not listing macro expansions 1040 *---AppleWorks Variables--------- 1050 BUF.900 .EQ $0900 1060 STR.A00 .EQ $0A00 1070 X.0EA7 .EQ $0EA7 1080 SEGMENT.ADDRESS .EQ $0F14 1090 X.0FCE .EQ $0FCE 1100 X.0FF3 .EQ $0FF3 1110 FREE.MEMORY.xxxxK .EQ $0FF5 1120 *-------------------------------- 1130 DISPLAY.ON.LINE.23 .EQ $1FF5 1140 AW.KEYIN .EQ $1D35 1150 X.B700 .EQ $B700 1160 *---ProDOS Global Page----------- 1170 MLI .EQ $BF00 1180 PRODOS.BITMAP .EQ $BF58 1190 *---Subroutines from SEG.00 or SEG.XM--- 1200 Measure.free.Memory .EQ $D002 1210 Download.from.AuxRAM.or.Memory.Card .EQ $D005 1220 Upload.to.AuxRam.or.Memory.Card .EQ $D011 1230 *---Page Zero Variables---------- 1240 Z.85 .EQ $85 1250 SEGLOAD.ERROR.FLAG .EQ $8D 1260 P0 .EQ $9A 1270 P4 .EQ $9E 1280 P5 .EQ $9F 1290 DISPLAY.ACTIVE.FLAG .EQ $A4 1300 *-------------------------------- 1310 .PH $1000 1320 *-------------------------------- 1330 .MA MLI.SL Macro for MLI calls 1340 JSR MLI 1350 .DA #$]1,SLIOB.]2 1360 .DO ]#>2 If 3rd parameter, include these lines 1370 BEQ :1 ...no error reported by MLI 1380 INC SEGLOAD.ERROR.FLAG error, so set flag 1390 :1 1400 .FIN 1410 .EM 1420 *-------------------------------- 1430 .MA HS Macro to shrink listing only 1440 .HS ]1 1450 .EM 1460 *-------------------------------- 1470 .MA MSG Macro to make a string with first byte 1480 .DA #:1-*-1 giving the length, rest is ASCII. 1490 .AS "]1" 1500 :1 1510 .EM 1520 *-------------------------------- 1530 JMP $3E25 RELOCATE.AND.START+$1000 1540 JMP CALL.FOR.AWPROGRAM.DISK 1550 JMP $11A1 LOAD.PROGRAM.SEGMENT.A 1560 JMP $1341 APPEND.STRINGS 1570 JMP $1366 CLEAR.MAIN.WINDOW 1580 JMP $136E 1590 JMP $139D CLR.LINE.X.TO.LINE.Y 1600 JMP $14D1 DISPLAY.STRING (A) bytes, starting at $80,81 1610 JMP $179D CONVERT.A.TO.RJBF.STRING 1620 JMP $1815 HANG 1630 JMP $17D1 DIVIDE.P0.BY.P2 1640 JMP $1818 BEEP.AND.CLEAR.KEYBUF 1650 JMP $1823 MOVE.CURSOR.TO.XY 1660 JMP $1837 1670 JMP $1842 1680 JMP $1850 1690 JMP $186C 1700 JMP $1872 1710 JMP $187A COPY.SCRN.LINE.TO.0900 1720 JMP $18B4 GET.A.PARMS 1730 JMP $191D 1740 JMP $1A77 1750 JMP SET.PRODOS.BITMAP 1760 JMP CLR.PRODOS.BITMAP 1770 JMP $1B0B DRAW.TOP.AND.BOTTOM.LINES 1780 JMP $1B2B 1790 JMP $1B3A MULTIPLY.X.BY.Y 1800 JMP $1B4E MULTIPLY.P0.BY.P2 1810 JMP $1B84 MOVE.BLOCK.DOWN 1820 JMP $1BAC MOVE.BLOCK.UP 1830 JMP $1BDF 1840 JMP $1BF1 WAIT.FOR.SPACE.RETURN.OR.ESCAPE 1850 JMP $1C21 PRINTER.DRIVER 1860 JMP $1D0F 1870 JMP $1D35 AW.KEYIN 1880 JMP $1E80 MOVE.CURSOR.TO.TCOL.TROW 1890 JMP $1E8A 1900 JMP $1EB4 MAP.LOWER.TO.UPPER char in A-reg 1910 JMP $1EBF FILTER.LC.TO.UC string 1920 JMP $1ED9 COMPARE.STRINGS 1930 JMP $1EF8 MOVE.STRING 1940 JMP $1F3E DISPLAY.AT 1950 JMP $2029 1960 JMP $1FD1 DELAY.A.TENTHS 1970 JMP $1FE0 CLEAR.KEYBUF 1980 JMP $2093 DISPLAY.STRING.P0 1990 JMP $1FE9 DISPLAY.TOKEN.X 2000 JMP $20AE 2010 JMP $20BE 2020 JMP $1FF5 DISPLAY.ON.LINE.23 2030 JMP $20D6 2040 *-------------------------------- 2050 * CONSTANTS & VARIABLES 2060 *-------------------------------- 2070 HANDLE.STR.A00 .DA STR.A00 2080 HANDLE.SEGMENT.TABLE .DA SEGMENT.TABLE 2090 *-------------------------------- 2100 * Holds result after calling CONVERT.A.TO.RJBF.STRING 2110 * but this result is apparently never referenced. 2120 RJBF.STRING >HS 03.20.20.20 2130 *-------------------------------- 2140 * 10A1 holds 00, 01, 02, or 03, indicating which of segments 2150 * 1, 16, or 24 was the most recently loaded. 2160 * 00 means none of these have yet been loaded. 2170 * 01 means segment 1 (Data Base) 2180 * 02 means segment 16 (Word Processor) 2190 * 03 means segment 24 (Spread Sheet) 2200 CURRENT.APPLICATION .HS 00 2210 APPLICATION.SEGMENT.LIST >HS FF.01.10.18 2220 *-------------------------------- 2230 * Segment Table 2240 * There are 43 program segments in files SEG.M0 and SEG.M1 2250 * Four bytes in this table for each segment. 2260 * Byte 1: Starting page to load segment into. 2270 * Byte 2: Age of segment (00=just loaded, FF=oldest) 2280 * Bytes 3,4: 0000 if must be loaded from disk 2290 * xxxx if in RAM or Aux RAM 2300 *-------------------------------- 2310 CURRENTLY.LOADED.SEGMENT .HS FF 2320 SEGMENT.TABLE 2330 >HS 30.000000 Dummy Entry, seg 00 2340 >HS 35.000000 Seg 01 (Data Base) 2350 >HS 45.000000.45.000000 Segs 02,03 2360 >HS 45.000000.45.000000 Segs 04,05 2370 >HS 45.000000.4E.000000 Segs 06,07 2380 >HS 4E.000000.4A.000000 Segs 08,09 2390 >HS 4E.000000.4E.000000 Segs 0A,0B 2400 >HS 4E.000000.53.000000 Segs 0C,0D 2410 >HS 4E.000000.00.000000 Segs 0E,0F 2420 * 2430 >HS 35.000000 Seg 10 (Word Processor) 2440 >HS 3D.000000.3D.000000 Segs 11,12 2450 >HS 40.000000.00.000000 Segs 13,14 2460 >HS 00.000000.67.000000 Segs 15,16 2470 >HS 77.000000 Seg 17 2480 * 2490 >HS 33.000000 Seg 18 (Spread Sheet) 2500 >HS 3B.000000.53.000000 Segs 19,1A 2510 >HS 53.000000.53.000000 Segs 1B,1C 2520 >HS 53.000000.53.000000 Segs 1D,1E 2530 >HS 53.000000.45.000000 Segs 1F,20 2540 >HS 64.000000.64.000000 Segs 21,22 2550 >HS 64.000000.64.000000 Segs 23,24 2560 >HS 64.000000.64.000000 Segs 25,26 2570 >HS 64.000000.64.000000 Segs 27,28 2580 >HS 64.000000.64.000000 Segs 29,2A 2590 >HS 64.000000 Seg 2B 2600 SEGMENT.TABLE.SIZE .EQ *-SEGMENT.TABLE 2610 *-------------------------------- 2620 POSITION.IN.STRING .HS 00 Used by DISPLAY.STRING 2630 *-------------------------------- 2640 * Note **SECRET** limitation: the pathname 2650 * to the SEG.M0 and SEG.M1 files is limited 2660 * to a total of 29 bytes, including the "/". 2670 SEGMENT.PATHNAME .HS 01.2F 2680 .BS 28 2690 *-------------------------------- 2700 KEYIN.CURSOR.TYPE .HS 01 00=underline, 01=flashing 2710 KEYIN.CURSOR.FLAG .HS 01 00=no cursor, 01=cursor 2720 SCROLL.DIRECTION .HS 00 Used by DISPLAY.STRING 2730 BYTES.IN.STRING .HS 00 Used by DISPLAY.STRING 2740 *-------------------------------- 2750 KEYBUF .BS 10 2760 KEYBUF.IN .HS 00 2770 KEYBUF.OUT .HS 00 2780 *-------------------------------- 2790 * Subroutine to request mounting of the AppleWorks 2800 * Program Disk, so a SEG.xx file can be opened. 2810 *-------------------------------- 2820 * (1186) 1003 124B 275F 2830 CALL.FOR.AWPROGRAM.DISK 2840 LDA DISPLAY.ACTIVE.FLAG Save Display lockout flag 2850 PHA 2860 LDA #0 Allow display 2870 STA DISPLAY.ACTIVE.FLAG 2880 JSR DISPLAY.ON.LINE.23 "Place the AppleWorks 2890 .DA MSG..1 PROGRAM disk in Drive 1 and press Return." 2900 JSR AW.KEYIN Wait for Any Key 2910 LDA #0 Indicate Program Disk mounted 2920 STA X.0FCE 2930 PLA Restore Display lockout flag 2940 STA DISPLAY.ACTIVE.FLAG 2950 RTS 2960 *-------------------------------- 2970 SEGMENT.INDEX .BS 1 2980 SEGMENT.SAVEX .BS 1 2990 SEGMENT.NUMBER .BS 1 3000 *-------------------------------- 3010 * (A)=Segment Number or Segment Number + $80. 3020 * There are 43 segments, numbered 1 to 43. 3030 * Segments 1-9 are in file SEG.M0, and 3040 * segments 10-43 are in file SEG.M1. 3050 * 3060 * The Segment is loaded at $xx00, where xx 3070 * is the first byte for the entry in the 3080 * Segment Table. 3090 * 3100 * Bit 7 of A controls the execution option. 3110 * If 0, then exit with JMP $xx02. 3120 * If 1, then exit with an RTS. 3130 * (X)=Function Number in segment 3140 *-------------------------------- 3150 * (11A1) 1006 1877 3160 LOAD.PROGRAM.SEGMENT.A 3170 STA SEGMENT.NUMBER 3180 STX SEGMENT.SAVEX 3190 ASL *4 to get index into Segment Table 3200 ASL 3210 STA SEGMENT.INDEX 3220 *---Increment all entries-------- 3230 LDX #SEGMENT.TABLE.SIZE 3240 .1 DEX For X = $AC to $04 step -4 3250 DEX 3260 DEX 3270 DEX 3280 INC SEGMENT.TABLE+1,X 3290 BNE .2 3300 DEC SEGMENT.TABLE+1,X max value is $FF 3310 .2 CPX #0 3320 BNE .1 3330 *---Clear indexed entry---------- 3340 LDX SEGMENT.INDEX 3350 LDA #0 3360 STA SEGMENT.TABLE+1,X 3370 *---Keep track of loaded segment-- 3380 LDA SEGMENT.NUMBER 3390 AND #$7F 3400 CMP CURRENTLY.LOADED.SEGMENT 3410 BNE .3 3420 JMP LOAD.EXIT.BY.OPTION Already loaded, so we're done! 3430 .3 STA CURRENTLY.LOADED.SEGMENT 3440 *---Get Load Address------------- 3450 LDX SEGMENT.INDEX 3460 LDY SEGMENT.TABLE,X 3470 BNE .4 3480 JMP LOAD.EXIT.RTS ...no such segment, quit now 3490 .4 STY SLIOB.READ.SEGMENT+3 3500 STY .10+1 In call to Downloader Subroutine 3510 STY SL.ADR+1 3520 STY SEGMENT.EXECUTION.VECTOR+2 3530 *---Is this segment $20?--------- 3540 CMP #$20 3550 BNE .5 3560 LDX X.0EA7 3570 BNE .11 ...Exit 3580 *---Keep track of application---- 3590 .5 LDX CURRENT.APPLICATION 3600 BEQ .6 3610 CMP APPLICATION.SEGMENT.LIST,X 3620 BEQ .11 ...Exit 3630 .6 LDX #3 3640 .7 CMP APPLICATION.SEGMENT.LIST,X 3650 BEQ .8 3660 DEX 3670 BNE .7 3680 BEQ .9 ...not in the list 3690 .8 STX CURRENT.APPLICATION 3700 *---Decide how to load it-------- 3710 .9 LDX SEGMENT.INDEX If address in SEGMENT.TABLE is 0000, 3720 LDA SEGMENT.TABLE+2,X then load from Program Disk; 3730 STA SEGMENT.ADDRESS otherwise, download from Ram 3740 ORA SEGMENT.TABLE+3,X 3750 BEQ LOAD.SEGMENT.FROM.DISK 3760 LDA SEGMENT.TABLE+3,X 3770 STA SEGMENT.ADDRESS+1 3780 JSR Download.from.AuxRAM.or.Memory.Card 3790 .DA SEGMENT.ADDRESS 3800 .10 .HS 00.00 Hi-byte filled in by program 3810 .11 JMP LOAD.EXIT.BY.OPTION ...Exit 3820 *-------------------------------- 3830 LOAD.SEGMENT.FROM.DISK 3840 LDY SEGMENT.PATHNAME Change name end to 'M0' or 'M1' 3850 LDA #'M' 3860 STA SEGMENT.PATHNAME-1,Y 3870 LDA #'0' 3880 LDX CURRENTLY.LOADED.SEGMENT 3890 CPX #10 3900 BCC .1 Segments 1-9 from SEG.M0 3910 LDA #'1' Segments 10-43 from SEG.M1 3920 .1 STA SEGMENT.PATHNAME,Y 3930 *---Attempt to open SEG.Mx------- 3940 .2 >MLI.SL C8,OPEN Open the file 3950 BEQ .3 ...no problems 3960 JSR CALL.FOR.AWPROGRAM.DISK 3970 JMP .2 3980 *-------------------------------- 3990 .3 LDA #0 Indicate Program Disk mounted 4000 STA X.0FCE 4010 LDA SLIOB.OPEN+5 Copy File RefNum to IOBs 4020 STA SLIOB.READ.SEGMENT+1 4030 STA SLIOB.CLOSE+1 4040 STA SLIOB.SETMARK+1 4050 STA SLIOB.READ.INDEX+1 4060 JSR CLR.PRODOS.BITMAP 4070 LDA #0 Clear Error Flag 4080 STA SEGLOAD.ERROR.FLAG 4090 *---Read Segment Index----------- 4100 * $8C bytes at beginning of SEG.Mx file 4110 * into buffer at $0900. 4120 >MLI.SL CA,READ.INDEX,S 4130 *---Form segnum*3 for index------ 4140 LDA CURRENTLY.LOADED.SEGMENT 4150 ASL 4160 CLC 4170 ADC CURRENTLY.LOADED.SEGMENT 4180 TAX 4190 *---Get Mark and Length---------- 4200 LDA #3 Do for 3 loops 4210 STA P0 4220 LDY #0 Start with lsb 4230 SEC 4240 .4 LDA BUF.900,X Byte of Mark for this segment 4250 STA SLIOB.SETMARK+2,Y 4260 LDA BUF.900+3,X Byte of Mark for next segment 4270 SBC BUF.900,X Byte of Mark for this segment 4280 STA SLIOB.READ.SEGMENT+4,Y Byte of Length 4290 INX 4300 INY 4310 DEC P0 4320 BNE .4 More bytes 4330 *---Read the Segment------------- 4340 >MLI.SL CE,SETMARK,S Set Mark 4350 >MLI.SL CA,READ.SEGMENT,S Read Segment 4360 >MLI.SL CC,CLOSE,S Close File 4370 JSR SET.PRODOS.BITMAP 4380 LDA SEGLOAD.ERROR.FLAG 4390 BNE LOAD.EXIT.RTS ...Error(s), give it up. 4400 *---Save length of segment------- 4410 LDA #0 Build pointer to segment in P4,P5 4420 STA P4 4430 LDA SEGMENT.EXECUTION.VECTOR+2 4440 STA P5 4450 LDY #0 Get first two bytes 4460 LDA (P4),Y which are end address + 1 4470 STA SL.LEN and subtract load address 4480 INY to get length 4490 LDA (P4),Y (I thought we already knew 4500 SEC the length!) 4510 SBC P5 4520 STA SL.LEN+1 4530 *---See if room to save segment in RAM--- 4540 LDA SEGMENT.INDEX 4550 CLC Build pointer to vector in SEGMENT.TABLE 4560 ADC #2 4570 ADC HANDLE.SEGMENT.TABLE 4580 STA SL.SEG 4590 LDA HANDLE.SEGMENT.TABLE+1 4600 ADC #0 4610 STA SL.SEG+1 4620 JSR Measure.free.Memory 4630 LDA FREE.MEMORY.xxxxK+1 If non-zero, at least 256K 4640 BNE .5 ...which is plenty! 4650 LDA FREE.MEMORY.xxxxK 4660 CMP #16 Is there at least 16K? 4670 BCS .5 ...Yes, plenty 4680 ASL Convert to # pages free 4690 ASL 4700 CMP SL.LEN+1 Compare to what is needed 4710 BCC LOAD.EXIT.BY.OPTION ...not enough room 4720 .5 LDA #1 4730 STA X.0FF3 4740 JSR Upload.to.AuxRam.or.Memory.Card 4750 SL.SEG .HS 00.00 Address in SEGMENT.TABLE of vector 4760 SL.ADR .HS 00.00 Load Address of Segment 4770 SL.LEN .HS 00.00 Length of Segment 4780 LDA #0 4790 STA X.0FF3 4800 STA Z.85 ...fall into LOAD.EXIT... 4810 *-------------------------------- 4820 LOAD.EXIT.BY.OPTION 4830 LDX SEGMENT.SAVEX 4840 LDA SEGMENT.NUMBER 4850 BMI LOAD.EXIT.RTS 4860 SEGMENT.EXECUTION.VECTOR 4870 JMP $FF02 Page value filled in by program 4880 * so JMPs to $xx02 in segment. 4890 *-------------------------------- 4900 LOAD.EXIT.RTS RTS 4910 * 4920 *---OPEN IOB--------------------- 4930 SLIOB.OPEN 4940 .DA #3,SEGMENT.PATHNAME,X.B700 4950 .HS 00 Open RefNum 4960 * 4970 *---READ IOB--------------------- 4980 SLIOB.READ.SEGMENT 4990 .DA #4 5000 .HS 00 RefNum 5010 .DA $0000 Load Address 5020 .DA $3800 Load Length 5030 .DA $0000 Actual Length 5040 * 5050 *---CLOSE IOB-------------------- 5060 SLIOB.CLOSE 5070 .DA #1 5080 .HS 00 RefNum 5090 * 5100 *---SETMARK IOB------------------ 5110 SLIOB.SETMARK 5120 .DA #2 5130 .HS 00 RefNum 5140 .HS 00.00.00 5150 * 5160 *---READ IOB--------------------- 5170 SLIOB.READ.INDEX 5180 .DA #4 5190 .HS 00 RefNum 5200 .DA BUF.900,$008C,$0000 5210 *-------------------------------- 5220 .PH $13C1 5230 MSG..1 >MSG "Place the AppleWorks PROGRAM disk in Drive 1 and press Return. " 5240 .EP 5250 *-------------------------------- 5260 .PH $1AFC 5270 * (1AFC) 1042 12B9 24C6 270D 272E 278F 2B2E 2CF3 5280 * (1B00) 1045 1265 23F1 249F 26E4 2771 2960 2CEA 5290 SET.PRODOS.BITMAP 5300 LDA #$FF Protect all main RAM from ProDOS 5310 BNE SC.BM ...always 5320 CLR.PRODOS.BITMAP 5330 LDA #$00 De-protect all main RAM 5340 SC.BM LDX #$13 Affects RAM from $0800 thru $9FFF 5350 .1 STA PRODOS.BITMAP,X 5360 DEX 5370 BNE .1 5380 RTS 5390 .EP 5400 *-------------------------------- |
The AppleWorks Program disk contains two files of type $00, called SEG.M0 and SEG.M1. These contain the actual code for the three applications (data base, word processing, and speadsheet) as a series of overlay segments.
These two files could really be just one, and I don't understand why they are not. It takes extra logic inside AppleWorks to manage them as two files, and the code would be a little simpler if there were only one. As it is, the first nine of 43 overlays are on SEG.M0, and the remaining 34 are on SEG.M1.
The overlay loader first decides which file to open, and then reads in the first 140 bytes of that file. There is a "directory" of sorts at the beginning of each file: one 3-byte value for each segment in the file. The 3-byte value is the offset within the file where the overlay begins. For example, looking at the beginning of the SEG.M0 file for AppleWorks version 1.3, in 3-byte groups, I see:
00 00 00 21 00 00 4C 30 00 9A 35 00 and so on
This means that overlay segment 0, which does not really exist, begins at offset $000000 in the file. Overlay segment 1 begins at $000021, or with the 33rd byte of the file. Segment 2 begins at $034C in the file, and so on. By subtracting the beginning address of the segment I want to load from the address of the begining of the next segment I get the number of bytes in the desired segment.
I decided to write a program to analyze these file headers for me, and print out a list of file offsets and lengths for each segment. The program follows, but before describing it I need to mention a table inside APLWORKS.SYSTEM. A table I call "SEGMENT.TABLE" begins at $10A7 (in version 1.3). There are four bytes for each segment in this table. The first of each group of four is the page number where the corresponding segment should be loaded. Entries for segments $0F, $14, and $15 are zero, meaning these segments do not exist. Segment $00 does not exist either, but it is eliminated by other means. The other three bytes of each 4-byte group are used by the overlay loader to keep track of which overlay segments are already in RAM, in AuxRAM, or in a RamFactor type card.
I decided to copy the loading page numbers from this table into my little analysis program, so that it could also print out the loading address for each segment. You can see my copy at lines 1860-1930. I have added three labels to indicate which overlay segments belong to which of the three applications. The Data Base code is in segments $01 through $0F, the Word Processor in segments $10-17, and the SpreadSheet in segments $18-2B. At least that is what I think is true, someone correct me if you know better.
My analysis program has several interesting wrinkles, of interest beyond the overall function of the program. I have defined two useful macros in lines 1070-1180. The first one, PRINT, generates a JSR PRINT followed by a zero-terminated string. As long as the string is purely printable ASCII characters which will fit in a .AS directive, the macro works nicely. The PRINT subroutine in lines 1680-1840 picks up the string which follows the JSR PRINT and prints it out, modifies the return address, and returns to the next instruction following the string.
The second macro calls on the Apple monitor to print a byte in hexadecimal. If there is no parameter in the >HEX macro call line, the macro will assume the byte to be printed is already in the A-register and generate only a JSR PRBYTE line. However, if you include one parameter, the macro will generate a LDA instruction to load the value into the A-register first. For example:
>HEX generates JSR PRBYTE >HEX #$24 generates LDA #$24 JSR PRBYTE >HEX BUFFER generates LDA BUFFER JSR PRBYTE >HEX "BUF+2,Y" generates LDA BUF+2,Y JSR PRBYTE
Note that in the last example I had to put the BUF+2,Y in quotation marks, so that the assembler would include the ",Y" as part of the ]1 parameter.
When you use >HEX with no parameter, you also must not put a comment on the line. The first word of any comment would be considered as a parameter, generating bad results.
I assembled the program with the .LIST MOFF option, so that macro expansions are not shown.
The program assumes that you have BLOADed the SEG.Mx file header into a buffer starting at $0A00. On my disk I have those files in a subdirectory called AW, so after assembling the program I typed:
BLOAD AW/SEG.M0,T$00,A$A00,L200 MGO T BLOAD AW/SEG.M1,T$00,A$A00,L200 MGO T
Lines 1200-1280 search through the index beginning at $0A00 for the first 3-byte entry which is not all zero. There is one 000000 entry in the SEG.M0 file, and there are ten of them in the SEG.M1 file. The first non-zero entry actually also points just past the end of the index header, so I save that value for a loop termination count in lines 1290-1310.
Lines 1330-1350 print "SEGMENT." and the two digit segment number in hexadecimal. Lines 1360-1390 decide whether the segment exists or not, and prints ": null" if it does not. If the segment does exist, lines 1410-1560 print out the "A$xx00" load address, using the value from my LOAD.ADDRESS.TABLE; the "B$xxxxxx" file offset; and the "L$xxxx" segment length.
Lines 1570-1630 advance to the next three byte entry, and loop back if not at the end of the index header.
Using the information that prints out I could easily load any individual segment from within one of the SEG.Mx files and save it on its own private BIN file. For example, to load and save segment $20 I would type:
BLOAD SEG.M1,T$00,A$1000,B$0144BB,L$1E4E BSAVE SEGMENT.20,A$1000,L$1E4E
I could modify the analysis program to generate an EXEC file which would include two lines like these for each existing segment. Then EXECing the file would automatically produce 40 separate binary files, one for each overlay segment (not 43, because there are three "null" segments). This would make it easier to disassemble each one. I probably will end up modifying it this way eventually.
Another interesting program would create a new SEG.Mx file from a set of separate binary files within a subdirectory. What do you bet Robert Lissner has just such a program?
1000 .LIST MOFF 1010 *SAVE S.SHOW.INDEX 1020 *-------------------------------- 1030 CROUT .EQ $FD8E 1040 PRBYTE .EQ $FDDA 1050 COUT .EQ $FDED 1060 *-------------------------------- 1070 .MA PRINT 1080 JSR PRINT Print 00-term'd string after 1090 .AS -"]1" here is the string 1100 .HS 00 here is the 00-terminator 1110 .EM 1120 *-------------------------------- 1130 .MA HEX 1140 .DO ]# If any parameter, LDA it 1150 LDA ]1 here is the LDA 1160 .FIN 1170 JSR PRBYTE Print <A> in hexadecimal 1180 .EM 1190 *-------------------------------- 1200 T 1210 LDX #-1 X will be the segment number 1220 LDY #-3 FIND FIRST NON-ZERO ENTRY 1230 .1 INY 1240 INY 1250 INY 1260 INX 1270 LDA BUF,Y 1280 BEQ .1 ...this entry was empty 1290 SEC 1300 SBC #3 1310 STA Y.LIMIT 1320 *-------------------------------- 1330 .2 >PRINT "SEGMENT." 1340 TXA PRINT SEGMENT NUMBER 1350 >HEX 1360 LDA LOAD.ADDRESS.TABLE,X 1370 BNE .3 not a null segment 1380 >PRINT ": null" 1390 JMP .4 1400 *-------------------------------- 1410 .3 >PRINT ": A$" Print load address 1420 >HEX "LOAD.ADDRESS.TABLE,X 1430 >PRINT "00, B$" Print file offset 1440 >HEX "BUF+2,Y 1450 >HEX "BUF+1,Y 1460 >HEX "BUF,Y 1470 >PRINT ", L$" Print segment length 1480 SEC 1490 LDA BUF+3,Y 1500 SBC BUF,Y 1510 PHA 1520 LDA BUF+4,Y 1530 SBC BUF+1,Y 1540 >HEX 1550 PLA 1560 >HEX 1570 .4 JSR CROUT Next line 1580 INX Next segment number 1590 INY Next header pointer 1600 INY 1610 INY 1620 CPY Y.LIMIT Into first segment? 1630 BCC .2 ...no, more lines 1640 RTS ...done 1650 *-------------------------------- 1660 Y.LIMIT .BS 1 1670 *-------------------------------- 1680 PRINT 1690 PLA POP RETURN ADDRESS 1700 STA .2+1 BECAUSE IT POINTS TO STRING 1710 PLA 1720 STA .2+2 1730 .1 INC .2+1 BUMP POINTER TO NEXT CHAR 1740 BNE .2 1750 INC .2+1 1760 .2 LDA $3333 GET NEXT CHAR OF STRING 1770 BEQ .3 00 = END OF STRING 1780 JSR COUT PRINT CHAR 1790 JMP .1 ...NEXT 1800 .3 LDA .2+2 PUT RETURN ADDRESS ON STACK 1810 PHA 1820 LDA .2+1 1830 PHA 1840 RTS 1850 *-------------------------------- 1860 LOAD.ADDRESS.TABLE 1870 .HS 30 dummy entry for segment 00 1880 DATA.BASE .HS 35.45.45.45.45.45.4E.4E.4A 1890 .HS 4E.4E.4E.53.4E.00 1900 WORD.PROC .HS 35.3D.3D.40.00.00.67.77 1910 SPRD.SHEE .HS 33.3B.53.53.53.53.53.53.45 1920 .HS 64.64.64.64.64.64.64.64.64 1930 .HS 64.64 1940 *-------------------------------- 1950 BUF .EQ $A00 1960 *-------------------------------- 1970 .LIF |
The Applied Engineering RamFactor memory card is now widely distributed, and with good reason. It is among my very favorite cards, and I use it heavily every day. I use mine with the RamCharger battery backup system, so that the memory stays loaded and ready all the time.
I have mine partitioned into two parts: a DOS 3.3 partition of 140K (floppy size), and a ProDOS partition with all the rest. I have been using Copy II Plus to backup the ProDOS partition on a 3.5 inch disk, but I haven't been keeping an up-to-date backup of the DOS partition. (Copy II Plus cannot access the DOS partition, or at least not when I have just loaded Copy II Plus out of the ProDOS partition on the same card.)
I have sometimes used FID to copy every file from the DOS partition to a floppy, but that takes a long time. In fact, when I tried it today, it took 160 seconds to save 31 files. And that only backs up the files. If the RamFactor is somehow clobbered, I will also need to restore the DOS image. My DOS is significantly patched, so I would really like to have it on the floppy too. Let's see.... I could boot from the RamFactor, go into Applesoft, load the HELLO program, directly type in the INIT command to initialize a floppy, then go into FID and copy all the rest of the files. Too much! And anyway, how do I get the floppy's contents copied back into the RamFactor?
Looking at the whole problem another way, what if I did not have the RamCharger? Then I would need to copy all the files and a DOS image into the card at least once a day. That could be really tiresome.
I decided to write a program to simplify things. My program has three parts: Format, Backup, and Restore. FORMAT.FLOPPY will do a raw format of a floppy disk. That is, it only writes the sector headers for 16 sectors on each of 35 tracks; it does not write a DOS image or an empty catalog. If the floppy I want to use has already been formatted, I can skip using FORMAT.FLOPPY.
BACKUP copies all the sectors of all 35 tracks from the RamFactor to the Floppy, and RESTORE does the reverse. BACKUP takes 46 seconds to copy and verify all 560 sectors, and RESTORE takes 25 seconds to read them back. Not as fast as possible, considering how fast Locksmith can copy one un-protected floppy to another, but my program is considerably shorter than Locksmith. If I leave out the Verify phase, BACKUP takes only 25 seconds.
Since all sector I/O is done by calls to RWTS, this same program could be used to backup and restore floppy-sized volumes on the Sider Hard Disk, with only minor modifications. Sider comes with a modified version of FID which already can do an "image" copy, as they call it, but it is too slow for me.
Another set of modifications would make my program work with 400K DOS partitions on the RamFactor and 3.5 inch disks formatted for use with DOS.
I decided to keep the program simple, for now. The slot numbers for the floppy drive and the RamFactor are assembled in at lines 1020-1030. A fancy program would probably do a slot search to find them, and give you a choice if there were more than one of either in the computer. A fancy program would also give you a little menu for selecting FORMAT, BACKUP, or RESTORE; I didn't do that either, but you can easily add one. One more improvement would be to automatically format the floppy if it isn't already formatted.
Well, let's look at what I DID do! Lines 1300-1370 show the two entry points for BACKUP and RESTORE. They load the slot numbers (shifted into the high nybble, so we say it is slot*16) into the A- and X-registers and go to COPY. COPY copies 35 tracks of 16 sectors each from the slot in the A-register to the slot in the X-register.
Lines 1390-1790 are the COPY subroutine. As each track is copied, I display the track number in hex, and the letters R, W, and V on the screen. After the letter R is displayed, I read the entire track from the source slot/drive into a buffer which starts at $2000. After the letter W is displayed, I write out the track to the destination slot/drive. After the letter V is displayed, I read the entire track again, this time from the destination slot/drive. If RWTS does not report any error, I assume the track is good. A more excellent way might be to read the destination track into a different buffer, and compare all 4096 bytes with the original.
After the track is verified I print two more spaces, making the total number of characters displayed for each track, eight. This means I display either 5 or 10 tracks on a screen line, depending on whether the screen is set to 40- or 80-columns.
If you want to skip the verify step altogether, you can delete lines 1620-1660 and add one more JSR COUT after line 1700.
Reading or writing a track is handled by lines 1840-2070, RW.TRACK. This finishes setting up the IOB and calls RWTS to do the I/O. If RWTS reports any error during the copy process, copying stops and I print all the interesting information about the error. Lines 2090-2280 do the printing. You can also stop a copy by typing any key. Lines 1710-1720 look for a keypress after finishing each track, and abort if one is found.
RW.TRACK reads the sectors in the order 15 down to 0. Of course, RWTS translates the logical sector numbers into "real" sector numbers, but we don't need to worry about that. There is an optimum order to read or write sectors, and it depends on several factors. First, it depends on the interleaving order on the disk, as viewed through the RWTS logical sector numbers. Second, it depends on how much time is wasted between reading or writing each sector. Programs like Locksmith read an entire track in one revolution of the disk, once the beginning of any sector is found. Locksmith also writes an entire track in one revolution. Using RWTS that is not possible, but you can probably do it in an average of 2.5 revolutions if you are smart enough. The drive spins at 5 revolutions per second, by the way.
I tried reading the sectors in the order 0 to 15, and it took 100 seconds just to READ 35 tracks. In the order 15 to 0, this took only 24 seconds. The disk turns 120 times in 24 seconds, so I am averaging less than 3.5 revolutions per track including the time it takes to step from track to track. (Theodore Roosevelt used to warn national leaders around the world about the hazards and long-term negative results of a habit of revolution, but I think he was on a different track.)
If you decide to type in this program, with or without any modifications, be very careful about using it. You can easily wipe out the contents of the RamFactor with only a tiny bug. I carefully made a backup with FID before testing RESTORE. It turned out I didn't need it, but I am still glad I did.
1000 *SAVE S.FAST RAMFACTOR BACKUP 1010 *-------------------------------- 1020 RFSLOT .EQ 4 RAMFACTOR SLOT 1030 FLSLOT .EQ 6 FLOPPY SLOT 1040 *-------------------------------- 1050 GETIOB .EQ $3E3 1060 RWTS .EQ $3D9 1070 *-------------------------------- 1080 KEYBOARD .EQ $C000 1090 STROBE .EQ $C010 1100 *-------------------------------- 1110 CROUT .EQ $FD8E 1120 PRBYTE .EQ $FDDA 1130 COUT .EQ $FDED 1140 *-------------------------------- 1150 .DUMMY 1160 .OR $B7E8 1170 IOB .BS 1 Reference "Beneath Apple DOS" 1180 SLOT16 .BS 1 1190 DRIVE .BS 1 1200 VOLUME .BS 1 1210 TRACK .BS 1 1220 SECTOR .BS 1 1230 .BS 2 ADDR OF DCT 1240 BUFADR .BS 2 1250 .BS 2 1260 CMD .BS 1 1270 ERRCOD .BS 1 1280 .ED 1290 *-------------------------------- 1300 BACKUP 1310 LDA #RFSLOT*16 From RamFactor to Floppy 1320 LDX #FLSLOT*16 1330 JMP COPY 1340 *-------------------------------- 1350 RESTORE 1360 LDA #FLSLOT*16 From Floppy to RamFactor 1370 LDX #RFSLOT*16 1380 *-------------------------------- 1390 COPY STA SRC.SLOT16 Save Source Slot*16 1400 STX DES.SLOT16 Save Destination Slot*16 1410 LDA #1 Both are drive 1 1420 STA DRIVE 1430 LDY #0 For Track = 0 to 34 1440 .1 STY TRACK 1450 TYA 1460 JSR PRBYTE Print track number in hex 1470 LDA #"-" and a dash... 1480 JSR COUT 1490 *---READ TRACK------------------- 1500 LDA #"R" Print "R" 1510 LDX SRC.SLOT16 Read track from Source Slot 1520 LDY #1 READ COMMAND 1530 JSR RW.TRACK 1540 BCS RWTS.ERROR ...Error 1550 *---WRITE TRACK ON FLOPPY-------- 1560 LDA #"W" Print "W" 1570 LDX DES.SLOT16 Write track to Dest. Slot 1580 LDY #2 WRITE COMMAND 1590 JSR RW.TRACK 1600 BCS RWTS.ERROR ...Error 1610 *---VERIFY TRACK ON FLOPPY------- 1620 LDA #"V" Print "V" 1630 LDX DES.SLOT16 Read track for Dest. Slot 1640 LDY #1 READ COMMAND 1650 JSR RW.TRACK 1660 BCS RWTS.ERROR ...Error 1670 *---CHECK FOR ABORT-------------- 1680 LDA #" " Print 2 blanks 1690 JSR COUT allowing 8 screen columns per track 1700 JSR COUT 1710 LDA KEYBOARD Press any key to abort 1720 BMI .2 ...ABORT 1730 *---NEXT TRACK------------------- 1740 LDY TRACK 1750 INY 1760 CPY #35 limit to 35 tracks 1770 BCC .1 ...more to do 1780 .2 STA STROBE ...done 1790 RTS 1800 *-------------------------------- 1810 SRC.SLOT16 .BS 1 1820 DES.SLOT16 .BS 1 1830 TRKBUF .EQ $2000 ... 2FFF 1840 *-------------------------------- 1850 * (A)=CHAR TO BE PRINTED 1860 * (X)=SLOT*16 1870 * (Y)=1 for READ, 2 for WRITE 1880 *-------------------------------- 1890 RW.TRACK 1900 STX SLOT16 Save slot*16 in IOB 1910 STY CMD Save command in IOB 1920 JSR COUT Print R, W, or V 1930 LDA #0 1940 STA VOLUME Accept any volume number 1950 STA BUFADR Lo-byte of buffer address=00 1960 LDA /TRKBUF Hi-byte of buffer address 1970 STA BUFADR+1 1980 LDY #15 For Sector = 15 to 0 step -1 1990 .1 STY SECTOR 2000 JSR GETIOB Setup for RWTS Call 2010 JSR RWTS 2020 BCS .2 ...ERROR 2030 INC BUFADR+1 Next Buffer Address 2040 LDY SECTOR 2050 DEY Next Sector 2060 BPL .1 ...more to do 2070 .2 RTS ...done 2080 *-------------------------------- 2090 RWTS.ERROR 2100 JSR CROUT Print all that's of interest 2110 LDA SLOT16 Slot * 16 2120 JSR PRBYTE.SPACE 2130 LDA DRIVE Drive number 2140 JSR PRBYTE.SPACE 2150 LDA TRACK Track number 2160 JSR PRBYTE.SPACE 2170 LDA SECTOR Sector number 2180 JSR PRBYTE.SPACE 2190 LDA CMD Command Code 2200 JSR PRBYTE.SPACE 2210 LDA ERRCOD Error Code 2220 JSR PRBYTE.SPACE 2230 JMP CROUT 2240 *-------------------------------- 2250 PRBYTE.SPACE 2260 JSR PRBYTE Print value in hex 2270 LDA #" " and a space 2280 JMP COUT 2290 *-------------------------------- 2300 FORMAT.FLOPPY 2310 LDA #1 2320 STA VOLUME Make it volume 1 (why not?) 2330 STA DRIVE on Drive 1 2340 LDA #FLSLOT*16 Do it to the floppy 2350 STA SLOT16 2360 LDA #4 FORMAT COMMAND Code 2370 STA CMD 2380 JSR GETIOB Set up RWTS call 2390 JSR RWTS 2400 BCS RWTS.ERROR 2410 RTS Done 2420 *-------------------------------- |
Psalm 90 A prayer of Moses the man of God | |
1 | LORD, thou hast been our dwelling place in all generations. |
2 | Before the mountains were brought forth,
or ever thou hadst formed the earth and the world, even from everlasting to everlasting, thou art God. |
3 | Thou turnest man to destruction;
and sayest, "Return, ye children of men." |
4 | For a thousand years in thy sight are but as yesterday when it is past,
and as a watch in the night. |
5 | Thou carriest them away as with a flood;
they are as a sleep: in the morning they are like grass which groweth up. |
6 | In the morning it flourisheth, and groweth up;
in the evening it is cut down, and withereth. |
7 | For we are consumed by thine anger,
and by thy wrath are we troubled. |
8 | Thou hast set our iniquities before thee,
our secret sins in the light of thy countenance. |
9 | For all our days are passed away in thy wrath:
we spend our years as a tale that is told. |
10 | The days of our years are three-score years and ten;
and if by reason of strength they be fourscore years, yet is their strength labour and sorrow; for it soon cut off, and we fly away. |
11 | Who knoweth the power of thine anger?
even according to thy fear, so is they wrath. |
12 | So teach us to number our days,
that we may apply our hearts unto wisdom. |
13 | Return, O LORD, how long?
and let it repent thee concerning thy servants. |
14 | O satisfy us early with thy mercy;
that we may rejoice and be glad all our days. |
15 | Make us glad according to the days wherein thou hast afflicted us,
and the years wherein we have seen evil. |
16 | Let thy work appear unto thy servants,
and thy glory unto their children. |
17 | And let the beauty of the LORD our God be upon us:
and establish thou the work of our hands upon us; yea, the work of our hands establish thou it. |
Apple Assembly Line (ISSN 0889-4302) is published monthly by S-C SOFTWARE CORPORATION,
P. O. Box 280300, Dallas, TX 75228 Phone (214) 324-2050.
Subscription rate is $24 per year in the USA, Canada, and Mexico, or $36 in other countries.
Back issues are $1.80 each for Volumes 1-7, $2.40 each for Volume 8
(plus postage). A subscription tothe newsletter with a Monthly
Disk containing all program source code and article text is $64 per year in
the USA, Canada and Mexico, and $90 to other countries.
All material herein is copyrighted by S-C SOFTWARE, all rights reserved.
Unless otherwise indicated, all material herein is authored by Bob Sander-Cederlof.
(Apple is a registered trademark of Apple Computer, Inc.)