I realize I'm making a significant claim here, and I am rather worried about how this post may be interpreted. I am not criticizing Larry Nelson's very generous act of making his copy of BASIC available for others to use and study, and I doubt he even remembered that he had patched it. In fact his own letters archived here were what allowed me to realize the patch's origin. (And Larry, I still remember the very nice compliment you gave me for a posting I made here back in 2005!)
My only concern is that the history of Apple I BASIC doesn't get confused. Larry Nelson's version has become pretty widely distributed, first via Achim Breidenbach. There have been claims that this BASIC is the unmodified original Apple BASIC, and that doesn't seem to be quite true. Also, some people have supposed that Apple may have released more than one version of BASIC for the Apple I. But I haven't seen any evidence of this, and I'm certain this change isn't from Apple.
Recently I had been investigating two different versions of Apple I BASIC that can be easily found on the net, both of which are included in the "software list" for the MAME emulation project's Apple I driver. (MAME used to be for arcade machines only, and the Apple I driver used to be part of the separate MESS project for personal computers and home game consoles, but the two projects have recently merged.) At the time, MESS developers assumed that Larry's version was the Apple original; it was being touted as such by [url=http://www.pagetable.com/?p=32]Michael Steil[/url] and it clearly came from a cassette recording. The only reason the second BASIC version was also included in the list was because a lot of people had trouble getting Larry's version to load successfully, due to audio glitches in the 800 Hz leader confusing the cassette interface's read routine. When I figured out how to get both versions to load, I compared them, saw differences in the code, and wondered which version had come first. Initially I assumed that Larry's was first, because it came from a cassette recording, and in the differing regions, the code in his version seemed more sensible. (I now realize that the patch's purpose was to make use of the "messed-up" portions in the Apple version and that the "garbage" there actually provided evidence for its origin.)
After spending some time reverse-engineering parts of the BASIC to the point where I could understand the purpose and effects of the changed regions, I am now sure that Larry's was patched. I think this patch was actually done by Larry himself some time around 1980, so long ago that he had probably forgotten it was in the recording.
Both of these different versions have disassemblies posted on the net. The one which I believe is not patched has been disassembled by [url=http://www.brouhaha.com/~eric/retrocomputing/apple/apple1/basic/]Eric Smith[/url]. I've found other copies identical to this version at [url=http://www.apple1notes.com/old_apple/Programs.html]Wendell Sander's site[/url] and at the [url=http://www.brutaldeluxe.fr/projects/cassettes/apple/]Brutal Deluxe site[/url]. Larry Nelson's version, as provided via Achim Breidenbach, has been disassembled by [url=http://michaeljmahon.com/AP1BASIC.txt]Michael J. Mahon[/url], though Mahon's disassembly has several small errors when compared with the actual code in the recording.
These two BASIC versions differ in only three areas: $EE53-$EE62, $EEA6-$EEC7, and $EF86-$EFB2; the rest of the code is identical. This was noted on [url=https://groups.google.com/d/msg/comp.sys.apple2/k31AoUCcN-U/yM_PrA1elnkJ]comp.sys.apple2[/url] as early as 2003, but I haven't found any other attempt to study the differences in detail.
It turns out that all three of these differing regions cover handler routines for executing various BASIC tokens as the interpreter encounters them while running a program or direct command. (This happens well after the interpreter has parsed and tokenized the command or program line being executed.)
The first two areas include handlers for some operations which were undocumented in Apple I BASIC and were either disabled or nonfunctional. Some of these are recognizable from the Apple II as Integer BASIC operations; others were apparently abandoned. Wozniak was working on the Apple II design at the same time he was developing his BASIC, and it looks like he was writing the BASIC for both systems using the same code base, simply disabling certain functions which couldn't work on the Apple I.
The third region is actually the root cause of the differences in the other two. It covers the handlers for the [b]HIMEM=[/b] and [b]LOMEM=[/b] commands, which reset the upper and lower bounds of the memory available to BASIC and which were commands only, not legal BASIC program statements. The Apple I BASIC documentation warns that these commands would automatically erase any program in memory at the time, and indeed they do in the unpatched version. However, the patched version has changed the handlers for these commands so that they [b]always preserve the program in memory[/b].
In the unpatched version, both commands check to make sure that HIMEM will never be below LOMEM. ([b]LOMEM=[/b] prints an error in this case, but [b]HIMEM=[/b] doesn't.)
In the patched version, [b]LOMEM=[/b] checks to make sure that the new value will not overlap the program, and prints an error if it would. Otherwise it resets the LOMEM boundary and clears all variables. [b]HIMEM=[/b] checks to make sure that the new value is not lower (it is impossible to lower the HIMEM boundary with [b]HIMEM=[/b] in this version), and if successful it moves the whole program up to the new HIMEM boundary. This last operation is the most complex of the changes, and it is spread over all three altered regions, overwriting the handlers for the undocumented, nonfunctional commands I already mentioned. These include:
* [b]HIMEM[/b] and [b]LOMEM[/b] (functions to obtain the current HIMEM and LOMEM values, not to be confused with [b]HIMEM=[/b] and [b]LOMEM=[/b])
* [b]COLOR[/b] (a function to obtain the current drawing color, not to be confused with [b]COLOR=[/b])
* [b]OFF[/b] (which became [b]MAN[/b] in Integer BASIC, and turns off the automatic line-numbering mode started with the [b]AUTO[/b] command)
* [b]HLIN[/b] [i]col1[/i] [b],[/b] [i]col2[/i] [b]AT[/b] [i]row[/i]
* [b]VLIN[/b] [i]row1[/i] [b],[/b] [i]row2[/i] [b]AT[/b] [i]col[/i]
* [b]VTAB[/b] [i]row[/i]
Even in the unpatched BASIC, none of these commands work properly; some will crash the interpreter.
The [b]OFF[/b] command "works" but is practically useless, since once [b]AUTO[/b] is activated, every input line gets a line number prefix and is treated as a program line, so the [b]OFF[/b] command (which isn't legal in a program) will be rejected. The documented way to turn [b]AUTO[/b] off is to press Control-D. (There are ways to force [b]OFF[/b] to work; if you cause an input line to be canceled by typing Escape, by deleting all its characters, or by entering too many characters, [b]AUTO[/b] will then be ignored for one input line, so there will be no line number and a non-program command like [b]OFF[/b] will be accepted.)
The [b]HLIN[/b] command is recognized by the parser but obviously required the Apple II's color graphics to work, and it crashes the interpreter due to an improperly terminated token handler routine.
The [b]HIMEM[/b], [b]LOMEM[/b], and [b]COLOR[/b] functions are also recognized by the parser but don't work right; [b]HIMEM[/b] returns the wrong value, while [b]LOMEM[/b] puts the interpreter in a confused state, and [b]COLOR[/b] (also clearly not applicable to the Apple I) isn't even implemented (it gives a [b]*** RANGE ERR[/b], but this may be a lucky accident).
[b]VLIN[/b] and [b]VTAB[/b] were disabled in the parser's syntax table, so trying to enter them will trigger syntax errors. Their keywords can't be found in the syntax table as such, which is how they are disabled in the parser, but their identity can be deduced in two ways. One is by their positions in the syntax table, which determined the token values assigned to them. These values match the corresponding tokens for Integer BASIC's [b]VLIN[/b] and [b]VTAB[/b] statements. The other way to recognize them is by the code (or code remnants) in their corresponding token handlers; this likewise matches up with the code for Integer BASIC's [b]VLIN[/b] and [b]VTAB[/b] token handlers, setting the same system variables.
Since none of these undocumented commands worked, it's obvious why their handlers were chosen to be replaced by the patched version's [b]HIMEM=[/b] handler.
What convinced me that Larry Nelson himself did this patching is that, when I looked for more information on the [b]HIMEM=[/b] and [b]LOMEM=[/b] commands in Apple I BASIC, I found two letters preserved on this site which had been sent by Larry Nelson in [url=http://www.applefritter.com/?q=node/2927]March 1980[/url] and [url=http://www.applefritter.com/?q=node/2928]January 1981[/url], in which he talks about inoperative areas in the BASIC and putting his own code in there; in the second letter he specifically mentions having changed [b]HIMEM=[/b] and [b]LOMEM=[/b]. Quoting:
... Before that I was working on BASIC. I've broken a lot of the code and am working on cleaning out a lot of trash. It looks like they threw this thing together to get a computer on the market fast. Probably other used bits and pieces from other listings, since there are several places with bad listings. Also found USR, RNDX, and OFF statements on commands in there. USR and RNDX don't work, but OFF turns off the auto-line #. (Try it by typing AUTO 10(r), then type "escape", then OFF(r)). We have COLOR=, PLOT, HLIN, too. It adds up to 120 bytes in there + several commands that we can't use. Now, if I can decipher how the ASCII is coded for each command, I'll have it made! Then I'll be able to pull out those unused commands and replace them with something useful.
The INKEY function could be put in code. The problem is that I've used some of BASIC empty spots, and now I'm not sure which places I've filled (HIMEM= and LOMEM= changes, and that routine to randomize). I goofed and didn't save a hard copy of the changes. ... In the original Apple BASIC, there are several areas we could put "fixes": E61C thru E622 = 7[sub]10[/sub] Bytes E98D " E997 = 11[sub]10[/sub] Bytes EE3E " EE67 = 37[sub]10[/sub] Bytes (PLOT, COLOR=, OFF commands) EEA6 thru EECA = 37[sub]10[/sub] Bytes (HLIN, COLOR, Value of HIMEM and LOMEM (don't work) plus another 34 bytes strung over the 4K program in banks of 6 or less.
I think when Larry Nelson made his Apple I BASIC cassette recording available, it already included the changes to HIMEM= and LOMEM= he mentioned, and Larry didn't realize that. (It does not seem to have the "routine to randomize" mentioned in the second letter; or at least there's no difference with the other BASIC version related to that.) I could believe that he might even have recorded this patched version on the original cassette, if that's the one he provided (there's some early sound level drops which might be the result of re-recording). After all, back in 1980 who would have expected one of these tapes to become an historic artifact?
Here are direct comparisons of the unpatched and patched code. These are assembly listings, dumped from MAME, which I have commented.
Especially when looking at blocks which I claim are original Apple code, it's important to keep in mind how this code was produced. Steve Wozniak was writing this directly in 6502 machine language without the benefit of an assembler, because at that time Apple could not afford any tools. He had to assemble the code by hand, and any changes in the location of a routine would have required fixing up all the addresses and relative branch offsets, again by hand. Also he was working on the design of the Apple II at the same time, which was clearly going to be the more important system (Jobs had to be persuaded not to abandon the Apple I before its BASIC was ready), and he was under intense time pressure. So it's not surprising that he used the same code for both systems. He would have had a very strong incentive to do the simplest possible hand-patching to make things work on the Apple I. It's no surprise that some cruft was left lying around.
Important addresses that show up in the code excerpts (all 16-bit values are stored in little-endian order):
* $CE,$CF: ACC, a general-purpose 16-bit accumulator.
* $4C,$4D: HIMEM, 16-bit pointer to the top of BASIC's memory.
* $4A,$4B: LOMEM, 16-bit pointer to the bottom of BASIC's memory.
* $CA,$CB: PP, 16-bit pointer to the start of the current BASIC program (which extends from PP up to HIMEM).
* $48,$49: (unnamed), a temporary 16-bit pointer in zero-page memory. Only used by the patched [b]HIMEM=[/b] handler, not found in Apple's code.
* $C8: TXTNDX, used for both [b]HLIN[/b] and [b]VLIN[/b], among other roles. (Its name comes from its use to index the input text buffer.)
* $2D: V2 (Apple II Monitor variable), bottom row for vertical line.
* $25: CV (Apple II Monitor variable), text cursor vertical position.
* $F8: AUTOFLAG, the flag indicating whether [b]AUTO[/b] line-numbering is on.
* $E715: GET16BIT, a routine to retrieve a token's 16-bit argument and leave it in ACC.
* $EE34: GETBYTE, a routine to retrieve a token's 8-bit argument and leave it in the 6502's accumulator.
The names for these are mostly arbitrary, since no source code is available for either Apple I BASIC or Integer BASIC. For consistency, the names I use here come from a disassembly of Integer BASIC made by the late Paul R. Santa-Maria, available [url=http://www.callapple.org/docs/ap2/special/integerbasic.pdf]here (PDF)[/url]. (Although the assembler format used takes some getting used to, I still found this valuable for understanding Apple I BASIC and comparing it with Integer BASIC.)
I've listed the blocks in the order in which they are executed by the patched [b]HIMEM=[/b] handler, which is the reverse of their memory ordering.
Original Apple $EF80-$EFB5: [b]HIMEM=[/b] and [b]LOMEM=[/b] handlers:
(The [b]SCR[/b] command referred to here is Apple I BASIC's name for what Integer BASIC called the [b]NEW[/b] command. [b]SCR[/b] is a command seen in some older BASIC versions; it stands for "Scratch", as in "scratch the program".)
; HIMEM= command handler:
; This does nothing (with a couple of presumably harmless side effects) if ; its argument is < LOMEM. It seems to have been intended to signal a ; *** RANGE ERR, but the sense of the carry flag is wrong for that. ; To get it right, the comparison would have to be reversed. ; ; Otherwise it changes HIMEM and erases the program, like a SCR command.
EF80: 20 15 E7 jsr $e715 ; Call GET16BIT (command argument -> ACC). EF83: A4 CE ldy $ce EF85: C4 4A cpy $4a EF87: A5 CF lda $cf EF89: E5 4B sbc $4b EF8B: 90 1E bcc $efab ; Branch if ACC < LOMEM. ; (Do nothing; return with no error and a couple harmless side effects)
; If ACC >= LOMEM: EF8D: 84 4C sty $4c EF8F: A5 CF lda $cf EF91: 85 4D sta $4d ; HIMEM = ACC
EF93: 4C AD E5 jmp $e5ad ; Jump to SCR command handler.
; LOMEM= command handler:
; This signals a *** RANGE ERR if its argument is >= HIMEM; otherwise it ; changes LOMEM and erases the program, like a SCR command.
EF96: 20 15 E7 jsr $e715 ; Call GET16BIT (command argument -> ACC). EF99: A4 CE ldy $ce EF9B: C4 4C cpy $4c EF9D: A5 CF lda $cf EF9F: E5 4D sbc $4d EFA1: B0 08 bcs $efab ; Branch if ACC >= HIMEM. ; (Trigger "*** RANGE ERR".)
; If ACC < HIMEM: EFA3: 84 4A sty $4a EFA5: A5 CF lda $cf EFA7: 85 4B sta $4b ; LOMEM = ACC EFA9: 90 E8 bcc $ef93 ; Branch to jump to SCR handler.
EFAB: 4C CB EE jmp $eecb ; Triggers "*** RANGE ERR" if carry set. ; Just returns (with some presumably harmless side effects) if carry clear.
; The following appears to be leftover orphaned code; it looks like a HIMEM ; counterpart to the LOMEM function's handler at $EEBA:
EFAE: A5 4D lda $4d EFB0: 48 pha EFB1: A5 4C lda $4c EFB3: 20 C9 EF jsr $efc9
Patched $EF80-$EFB5: [b]HIMEM=[/b] and [b]LOMEM=[/b] handlers:
; HIMEM= command handler:
; This version does nothing (with the same presumably harmless side effects as ; the original) if its argument is < *HIMEM*. Thus it becomes *impossible* to ; lower HIMEM with this command once it has been raised. (This protects the ; program, but at the cost of flexibility.) ; ; Otherwise it moves the program upward to the new value for HIMEM, adjusting ; HIMEM and PP (the start-of-program pointer) to match. It does this via two ; additional patched blocks, using a new temporary pointer variable $48,$49.
EF80: 20 15 E7 jsr $e715 ; Call GET16BIT (command argument -> ACC). EF83: A4 CE ldy $ce EF85: C4 4C cpy $4c EF87: A5 CF lda $cf EF89: E5 4D sbc $4d EF8B: 90 1F bcc $efac ; Branch if ACC < HIMEM. ; (do nothing; return with no error and a couple harmless side effects)
; If ACC >= HIMEM: EF8D: 84 48 sty $48 EF8F: A5 CF lda $cf EF91: 85 49 sta $49 ; $48,$49 = ACC EF93: 4C B6 EE jmp $eeb6 ; Jump into move-program block...
; LOMEM= command handler:
; This version signals a *** RANGE ERR if its argument is >= PP (the start-of- ; program pointer); otherwise it changes LOMEM and *clears the variables*, ; like a CLR command. ; ; Thus, this LOMEM= command *always protects the program*, signaling an error ; if the new LOMEM would overlap it. If the user wants to force LOMEM high ; enough to do this, the program must first be either deleted explicitly or ; shrunk by editing or deleting lines.
EF96: 20 15 E7 jsr $e715 ; Call GET16BIT (command argument -> ACC). EF99: A4 CE ldy $ce EF9B: C4 CA cpy $ca EF9D: A5 CF lda $cf EF9F: E5 CB sbc $cb EFA1: B0 09 bcs $efac ; Branch if ACC >= PP. ; (Trigger "*** RANGE ERR".)
; If ACC < PP: EFA3: 84 4A sty $4a EFA5: A5 CF lda $cf EFA7: 85 4B sta $4b ; LOMEM = ACC EFA9: 4C B7 E5 jmp $e5b7 ; Jump to CLR command handler.
EFAC: 4C CB EE jmp $eecb ; Triggers "*** RANGE ERR" if carry set. ; Just returns (with some presumably harmless side effects) if carry clear.
EFAF: EA nop EFB0: EA nop EFB1: EA nop EFB2: EA nop
EFB3: 20 C9 EF jsr $efc9
Original Apple $EEA6-$EED2: various nonfunctioning command handlers:
; HLIN statement's comma-token handler ("HLIN <num> , <num> AT <num>"): ; ; (HLIN is undocumented, but allowed by the parser. This handler may crash the ; interpreter due to running off into another handler's code.)
EEA6: 20 34 EE jsr $ee34 ; Call GETBYTE (command argument -> A). EEA9: C5 C8 cmp $c8 EEAB: 90 BB bcc $ee68 ; Branch if A < TXTNDX. ; (Trigger "*** RANGE ERR".)
; If A >= TXTNDX: EEAD: 85 sta ... ; (Zero-page STA opcode) ; From here, the code has been overwritten by the HIMEM handler ; immediately following, so it becomes garbage.
; HIMEM function handler for numeric expressions: ; ; (HIMEM is undocumented and doesn't work right. It's supposed to return the value ; of HIMEM, which is set with the "HIMEM=" command.) ; ; This also completely overwrites the entry point $EEB0 of HLIN's "AT" token handler.)
EEAE: A5 4D lda $4d EEB0: 48 pha EEB1: A5 4C lda $4c ; Push HIMEM_H on stack, A = HIMEM_L. EEB3: 20 08 E7 jsr $e708 ; Call a noun-stack routine. EEB6: 68 pla EEB7: 95 A0 sta $a0, x ; Store the result? EEB9: 60 rts ; (Perhaps a old calling convention that was later changed? ; In any case, this is broken.)
; LOMEM function handler for numeric expressions: ; ; (LOMEM is undocumented and is even more broken than HIMEM. It's supposed to return ; the value of LOMEM, which is set with the "LOMEM=" command. ; ; This also completely overwrites the entry point $EEBC of VLIN's comma token handler, ; but VLIN is disabled in the parser anyway. The tail end of the disabled handler's ; code can still be seen in the bytes $EEC3-$EEC5 after the first byte of the broken ; COLOR function handler below.)
EEBA: A5 4B lda $4b EEBC: 48 pha EEBD: A5 4A lda $4a ; Push LOMEM_H on stack, A = LOMEM_L. EEBF: 4C B3 EF jmp $efb3 ; Jump off... ; (...into INPUT code. This is broken.)
; COLOR function handler for numeric expressions: ; ; (COLOR is undocumented and totally unimplemented. Presumably it was ; supposed to return the drawing color set with the "COLOR=" command. ; Of course, the Apple I doesn't support this. ; ; Only the starting opcode of the code is present. The rest is the tail end of ; VLIN's comma-token handler, followed by the entire disabled VLIN "AT" token ; handler.)
EEC2: A5 lda ... ; (Zero-page LDA opcode) ; From here, the code is truncated.
; The tail end of VLIN's comma-token handler. The offset perfectly matches ; the $EEBC entry point. ; ; Note it stores to $2D. This is a zero-page variable for the VLINE routine ; at $F828 in the Apple II Monitor.
EEC3: 85 2D sta $2d ; V2 = A EEC5: 60 rts
; VLIN statement's "AT" token handler ("VLIN <num> , <num> AT <num>"): ; ; (This is disabled both in the parser and here, simply returning after loading and ; checking the arguments. The Apple II version in Integer BASIC instead jumps to its ; VLINE Monitor routine at $F828.)
EEC6: 20 34 EE jsr $ee34 ; Call GETBYTE (command argument -> A). EEC9: C9 28 cmp #$28 EECB: B0 9B bcs $ee68 ; Branch if A >= 40. ; (Trigger "*** RANGE ERR".) EECD: A8 tay EECE: A5 C8 lda $c8 ; A = TXTNDX EED0: 60 rts ; ("jmp $f828" in Integer BASIC; EED1: EA nop ; nop's replace the jump address.) EED2: EA nop
Patched $EEA6-$EED2: part 2 of [b]HIMEM=[/b] handler:
; This routine, entered at $EEB6 from the patched HIMEM= handler, moves the ; program upward from the previous value of HIMEM to the new, higher value ; passed in $48,$49. Both HIMEM and $48,$49 are decremented as the move ; proceeds; it stops when HIMEM == PP, at which point the entire program has ; been moved and $48,$49 now has the new value for PP. ; ; The routine then proceeds to the final block.
; MOVELOOP: EEA6: A5 4C lda $4c EEA8: D0 02 bne $eeac
EEAA: C6 4D dec $4d
EEAC: C6 4C dec $4c ; Decrement HIMEM. EEAE: A5 48 lda $48 EEB0: D0 02 bne $eeb4
EEB2: C6 49 dec $49
EEB4: C6 48 dec $48 ; Decrement $48,$49.
; Entry point from $EF93 in modified HIMEM= command handler:
EEB6: A0 00 ldy #$00 ; Y = 0 EEB8: B1 4C lda ($4c), y EEBA: 91 48 sta ($48), y ; *($48,$49) = *HIMEM EEBC: A5 CA lda $ca EEBE: C5 4C cmp $4c EEC0: A5 CB lda $cb EEC2: E5 4D sbc $4d EEC4: 90 E0 bcc $eea6 ; If PP < HIMEM branch to MOVELOOP ; (decrement pointers and repeat)
; If PP >= HIMEM: EEC6: 4C 53 EE jmp $ee53 ; Jump to final block.
; ...Remnant of disabled VLIN statement's "AT" token handler:
EEC9: C9 28 cmp #$28 EECB: B0 9B bcs $ee68
EECD: A8 tay EECE: A5 C8 lda $c8 EED0: 60 rts EED1: EA nop EED2: EA nop
Original Apple $EE53-$EE62: [b]OFF[/b] and (disabled) [b]VTAB[/b] command handlers:
EE53: EA nop
; OFF command handler: ; ; (OFF is undocumented, but it does work, just not very well. It was renamed ; to MAN in Apple II Integer BASIC.)
EE54: 46 F8 lsr $f8 ; Turn off AUTOFLAG. EE56: 60 rts
; VTAB statement handler: ; ; (This is disabled both in the parser and here; it loads and checks its argument, ; stores it to $25 (CV), a zero-page variable used by the Apple II's VTAB Monitor ; routine, and then returns. The Integer BASIC version instead jumps to the VTAB ; routine at $FC22.)
EE57: 20 34 EE jsr $ee34 ; Call GETBYTE (command argument -> A). EE5A: C9 18 cmp #$18 EE5C: B0 0A bcs $ee68 ; Branch if A >= 24. ; (Trigger "*** RANGE ERR".)
EE5E: 85 25 sta $25 ; CV = A EE60: 60 rts ; ("jmp $fc22" in Integer BASIC; EE61: EA nop ; nop's replace the jump address.) EE62: EA nop
Patched $EE53-$EE62: part 3 of [b]HIMEM=[/b] handler:
; This routine, jumped to from $EEC6 in the move-program block, is the ; modified HIMEM= handler's final phase. The program has been copied upward, ; and the new top of program is now in $48,$49. The new value for HIMEM is ; still present in ACC. All we need to do now is to reset the proper pointers, ; and we're done.
EE53: 8A txa ; Save X in A. EE54: A2 01 ldx #$01 ; X = 1
EE56: B4 CE ldy $ce, x EE58: 94 4C sty $4c, x ; HIMEM = ACC ("HIMEM=" argument) EE5A: B4 48 ldy $48, x EE5C: 94 CA sty $ca, x ; PP = $48,$49 (adjusted PP value) EE5E: CA dex EE5F: F0 F5 beq $ee56
EE61: AA tax ; Restore X from A. EE62: 60 rts ; Return.
I'd greatly appreciate any comments or criticisms on this posting.