Volume 8 -- Issue 6 | March 1988

- Inside AppleWorks 1.3, Part 4: Applications Overlay Manager
- Dissecting AppleWorks SEG.M0 and SEG.M1 Files
- Backup/Restore for RamFactor DOS Partition

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.

Peeking Inside AppleWorks 1.3, Part 4: Application Overlay Manager | Bob Sander-Cederlof

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

Dissecting AppleWorks SEG.M0 and SEG.M1 Files | Bob Sander-Cederlof

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

Backup/Restore for RamFactor DOS Partition | Bob Sander-Cederlof

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