Apple Assembly Line
Volume 8 -- Issue 6March 1988

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