Upon purchasing the Virtual ][ emulator for OS X, I decided to experiment with the original Apple ][. Here are my notes from those endeavours.
Note: throughout I use the caret (^) to denote a control character, eg “^P” means “Control-P”, as I find the Apple manual method of subscripts to be counterintuitive, and writing out “Control” to be tedious.
word size: |
At system boot, you are in the Monitor, indicated with an asterisk prompt (*). From here, you can enter Integer BASIC, boot from floppy, execute the mini-assembler, and so on.
Where do these numbers come from?
*^B
*FEB0G
*E000GEnter Integer BASIC
“Cold” launch; erases any loaded program*^C
*FEB3G
*E003GEnter Integer BASIC
“Warm” launch (continue); preserves program state*3D0G Enter Integer BASIC
via DOS call vector (requires DOS to be loaded)
Brilliantly, the Apple ][ ROM comes with a complete listing, which is invaluable for looking up constants like these. The relevant section looks like this:
FEB0: 4C 00 E0 XBASIC JMP BASIC TO BASIC WITH SCRATCH FEB3: 4C 03 E0 BASCONT JMP BASIC2 CONTINUE BASIC
And indeed if you disassemble that part of the Monitor, you get:
*FEB0L FEB0- 4C 00 E0 JMP $E000 FEB3- 4C 03 E0 JMP $E003
There are three useful entry points for returning to the Monitor:
hex dec $FF59 -167 ENTER MONITOR RESET, TEXT mode, "COLD START" $FF65 -155 ENTER MONITOR, ring BELL, "WARM START" $FF69 -151 Go to MONITOR
The source listing labels these three RESET, MON, and MONZ, respectively. In BASIC, use the CALL statement, eg:
>CALL -151
The entry point for the mini-assembler is $F666. So to enter the mini-assembler from the Monitor:
*F666G
And from BASIC:
>CALL -2458
Of course, you can use the RESET button to return to the Monitor. Perhaps a more elegant way would be to use one of the Monitor’s entry points, eg:
!$FF69G
*n^P
where n is the desired slot number. On Virtual ][ it’s slot 6 by default, thus:
*6^P
Strangely the Redbook (p. 71 [PDF p. 76]) says that ^P:
Sets printer output to I/O slot number (X).
which seems a bit nonsensical. But it seems that the ^P command executes whatever is in $Cn00, thus one could do, eg using slot 6 ($C600):
*C600G
Or, from BASIC, eg one of:
>CALL -14848
>PR#6
These are only the most common commands:
[address1][.address2] | *1024.1048 *.4096 *C0F2 * | Examines memory. If address1 is omitted, displays data starting at current position to address2. If .address2 is omitted, displays a single byte at address1. Pressing RETURN on a blank line displays the next 8 bytes. |
[address]: data [data …] | *A256:EF 20 43 *:F0 A2 12 | Changes memory. If address is omitted, starting position is the next byte after the last one previously written. |
[address]G | *300G | Go (execute) from specified address. Can also continue execution without specifying address, eg after pressing RESET during a trace or step. |
[address]T | *800T | Trace execution from specified address until a breakpoint (BRK instruction) is reached. |
[address]S | *C050S | Step execution from specified address. Use S for each successive step. |
[address]L | *C800L | Disassemble (the next) 20 instructions. |
address1.address2R | *300.4FFR | Reads cassette data into memory. The address length must match the amount of data read. |
address1.address2W | *800.9FFW | Writes memory to cassette data. |
[address:] instruction [operand …] | !C010:STA 23FF ! STA 01FF | Assemble a mnemonic 6502 instruction into machine code. Omitting the address: places the instruction in the next address. Note the space preceding the instruction is required regardless of whether the address: is specified. |
$monitor-command | !$C800L | Executes a monitor command, then returns control to the mini-assembler. After such a command, it’s a good idea to explicitly restate the desired assembly address. |
Boot into the desired DOS version using some known working DOS boot disk. If you have a program you’d like to execute at boot, LOAD it now, eg:
>LOAD FUNIONS
Otherwise, create a dummy BASIC program so you don’t get an error at boot, eg:
>NEW
>10 END
Initialise the disk:
>INIT HELLO
(HELLO appears to be the canonical choice, though you can use whatever you like.) This saves the current program to the disk with the name HELLO, which will be executed at boot.
By default, this creates a “slave diskette” (1970s–80s terminology!) suitable for booting on machines with your current memory configuration. If you want a relocatable DOS boot disk, you’ll now want to use MASTER CREATE, eg one of:
>BRUN MASTER CREATE
]RUN MASTER
Looking at the HELLO startup file on my custom Integer-BASIC-only S-C Macro Assembler disk, you’ll see lines like:
>LIST 80,100 80 CALL -936: PRINT "CATALOG": PRINT "INT" 100 PRINT "BLOAD S-C.ASM.MACRO.1000. 2.0": CALL 4096
There are actually invisible ^D characters there which cause those PRINT statements to effectively execute DOS commands. If you could see them, they’d look like:
>LIST 80,100 80 CALL -936: PRINT "^DCATALOG": PRINT "^DINT" 100 PRINT "^DBLOAD S-C.ASM.MACRO.1000. 2.0": CALL 4096
It’s important that these be inside the quote marks.
One of my “pet peeves” about the Apple ][ is that blinking cursor. Its blink rate is very fast to my eyes, and distracting when I have several programs open (besides the Virtual ][ emulator). My preference is a solid cursor, like the NetBSD default.
Apple published an Applesoft BASIC program to accomplish this, but it has bugs, and didn’t seem to play nice with the version of DOS I was using.
10 FOR A = 935 TO 941 20 READ B 30 POKE A,B 40 NEXT A 50 POKE 56,167 60 POKE 57,3 70 CALL 1002 80 END 100 DATA 72,41,63,145,40,104,76,27,253
At first glance, this appears to write some data to $3A7 (935), then a 16-bit word $3A7 to address $38. But there’s 9 bytes of DATA – and only 7 locations (935 through 941) to put it in. So that 941 should really be 943 (or the 935 should be 933) so that all of the bytes are written.
That DATA is machine code, which looks like this when disassembled:
*3A7:48 29 3F 91 28 68 4C 1B FD *3A7L 03A7- 48 PHA 03A8- 29 3F AND #$3F 03AA- 91 28 STA ($28),Y 03AC- 68 PLA 03AD- 4C 1B FD JMP $FD1B
At system boot, the contents of $38 are:
*38.39 0038- 1B FD
Hey, that address looks familiar! From this it appears that address $38 is being patched to execute the above code at $3A7 before it jumps to its original location, $FD1B. This works just fine, provided that DOS isn’t loaded.
After booting DOS, the situation changes slightly. Now $38 points to a DOS I/O handler at $9E81. Not only that, there are protection routines that prevent you from changing this, eg:
*38.39 0038- 81 9E *38:1B FD *38.39 0038- 81 9E
This is because DOS is intercepting all I/O requests – including those from the Monitor. After much digging, I was able to locate the address in DOS, $AA55, which points to the original I/O handler ($FD1B). When DOS boots, it copies $38 (labelled KSW) to $AA55 (labelled KSWTRUE), then patches $38 to its own I/O handler at $9E81. With DOS loaded, this works:
*3A7:48 29 3F 91 28 68 4C 1B FD *AA55:A7 3
After a RESET, $38 will be reset back to $FD1B. I’ve found it best to change $38 before reconnecting the DOS I/O hooks so that it will save $3A7 to $AA55, eg:
*38:A7 3 *3EAG *38.39 0038- 81 9E *AA55.AA56 AA55- A7 03
In fact, this is the same procedure that the Applesoft snippet tried to use (]CALL 1002 is *3EAG); it failed due to the DOS I/O interception protection.
>LOAD
>SAVE
Doesn’t get much easier than that, does it?
>GR | >GR | Enters Graphics mode. GR mode is a 40×40 graphics area, with a 40-column, 4-line text area below. |
>TEXT | >TEXT | Enters Text mode. TEXT mode is a 40-column, 24-line text area. |
>COLOR=color | >COLOR=12 | Changes the plotting color. color must be 0 ≤ x ≤ 15. |
>PLOT col,row | >PLOT 12,34 | Plots a square at the specified location. col must be 0 ≤ x ≤ 39. row must be 0 ≤ y ≤ 47 (but y > 39 starts clobbering the text area). Locations are specified left-to-right, top-to-bottom. |
>HLIN col1,col2 AT row | >HLIN 0,39 AT 20 | Plots a horizontal line from col1 to col2 on the specified row. |
>VLIN row1,row2 AT col | >VLIN 11,32 AT 7 | Plots a vertical line from row1 to row2 on the specified col. |
Which version? I suggest the S-C Macro Assembler 2.0, not the S-C Assembler II 4.0; despite having a higher version number, it’s four years older!
$1000 or $D000? $1000. On the original Apple ][ with the Programmer’s Aid routines, $D000 is already used for the high-resolution graphics system.
The documentation helpfully suggests:
You will probably wish to move the version of the assembler youThis seems like an excellent idea, especially for the original Apple ][, which can’t run the Applesoft BASIC HELLO program on the S-C disk. (In any case, the S-C disk doesn’t boot DOS properly, which is annoying enough on its own to merit building a working disk.)
usually use, with your favorite driver and EXEC loader, to your
working disks. Do it!
The overall procedure looks like:
10 PRINT "^DBLOAD S-C.ASM.MACRO.1000.2.0"Note that the ^D character is invisible.
20 CALL 4096
S-C Macro Assembler 2.0 supports three different 80-column cards:
Feel free to contact me with any questions, comments, or feedback.