7/27/2022 NOTE: William Astle left a comment pointing out that my RAM hook example should be using a JSR instead of a JMP. This has not been updated or corrected yet, but I will.
When the CoCo was released in 1980, it came with an 8K ROM containing Color BASIC. If the CoCo was expanded to at least 16K of RAM, a second 8K ROM could be added which contained Extended Color BASIC. If a disk controller were plugged in, that controller added a third 8K ROM containing Disk Extended Color BASIC.
Each ROM provided additional features and commands to what was provided in the original 8K Color BASIC.
To allow this, Color BASIC has a series of RAM hooks that initially point to routines inside Color BASIC, but can be modified by additional ROM code to point somewhere else. For example, Extended Color BASIC would modify these RAM hooks to point to new routines provided by that ROM.
According to the disassembly in Color BASIC Unravelled, there are 25 RAM hooks starting in memory at $15E (350). Each hook is 3 bytes long, which allows it to contain a JMP instruction followed by a 16-bit address.
As an example, there is a vector for “CONSOLE OUT” at $167 (359). On a Color BASIC system, that RAM hook contains $39 $39 $39 (57 57 57). That is the opcode for an RTS instuction, so effectively it looks like this:
rts rts rts
In Color BASIC is a subroutine called PUTCHR. Any time BASIC wants to output a character to the device (such as the screen), it loads the character in register A then calls this routine. Here is an example that outputs a question mark:
LB9AF LDA #'? QUESTION MARK TO CONSOLE OUT LB9B1 JMP PUTCHR JUMP TO CONSOLE OUT
The first thing this PUTCHR routine does is JMP to the RAM hook location, which is named RVEC3 (RAM hook vector 3) to run any extra code that might be needed. It looks like this:
* CONSOLE OUT PUTCHR JSR RVEC3 ...rest of routine... RTS
Since Color BASIC just had “RTS” there, PUTCHR would JMP to the RAM hook bytes and immediately return back, then continue with the output.
The code in Color BASIC knows about outputting to several device numbers. Device #0 (default) is the screen. Device #-1 is the cassette. Device #-2 is the printer.
When Extended Color BASIC came along, it added device #-3 for use with the DLOAD command. (I don’t think I ever knew this, but I did use DLOAD to download my first CoCo terminal program.) Since Color BASIC knew nothing about this device number, these RAM hooks were used to add the new functionality,
Extended Color BASIC modifies the three bytes of this RAM hook to be $7e $82 $73. That represents:
This jumps to a routine in the Extended Color BASIC ROM called XVEC3 (Extended Vector 3?). This is new code to check to see if we are using the DLOAD command (which outputs over the serial port, but not as a printer).
* CONSOLE OUT RAM HOOK XVEC3 TST DEVNUM CHECK DEVICE NUMBER LBEQ L95AC BRANCH IF SCREEN PSHS B SAVE CHARACTER LDB DEVNUM *GET DEVICE NUMBER AND CMPB #-3 *CHECK FOR DLOAD PULS B GET CHARACTER BACK BNE L8285 RETURN IF NOT DLOAD LEAS $02,S *TAKE RETURN OFF STACK & GO BACK TO ROUTINE *THAT CALLED CONSOLE OUT RTS
When Disk Extended Color BASIC is added, the vector is modified to point to a new routine called DVEC3 (Disk Vector 3?) located at $cc1c. (For Disk BASIC 1.0, it is at $cb4a.) That code will test to see if we are outputting to a disk device and, if not, it will long branch to XVEC3 in the Extended ROM. I find this curious since it would seem to imply that this location could never change, else Disk BASIC would break.
DVEC3 TST DEVNUM CHECK DEVICE NUMBER LBLE XVEC3 BRANCH TO EX BASIC IF NOT A DISK FILE ...rest of routine... RTS
Thus, with Color, Extended and Disk BASIC ROMs installed, a program wanting to output a character, such as “?”, is doing something like this:
LDA #'? JMP PUTCHR (Color BASIC ROM) \ PUTCHR JSR RVEC3 (RAM hook) \ RVEC3 JMP DVEC3 (in Disk BASIC) \ DVEC3 TST DEVNUM LBLE XVEC3 (in Extended BASIC) \ XVEC3 ...handle ECB / ...handle rest of DECB... / / ...handle rest of CB...
…or something like that. There’s alot of branching and jumping going on, to be sure.
This means we should also be able to make use of these RAM hooks and patch our own code in to the process. Since the only thing we can alter is the hook itself, our code has to save the original RAM hook, then point the hook to our new code. At the end of our new code, we jump to where the original RAM hook went, allowing normal operations to continue. (Or, we could have made it jump to the ROM hook first, and after the normal ROM things are done, then run our new code…)
To test this, I made a simple assembly routine to hijack the CONSOLE OUT RAM hook located at $167.
RVEC3 equ $167 console out RAM hook org $3f00 init: lda RVEC3 get op code sta saved save it ldx RVEC3+1 get address stx saved+1 save it lda #$7e op code for JMP sta RVEC3 store it in RAM hook ldx #new address of new code stx RVEC3+1 store it in RAM hook rts done new: inc $400 saved rmb 3
In the init routine, the first thing we do is load the first byte (which would be an RTS or a JMP) from the RAM hook and save it in a 3-byte buffer.
We then load the next two bytes (which would be a 16-bit address, or RTS RTS for Color BASIC).
Next we load A with the value of a JMP op code ($7e). We store it in the first byte of the RAM hook vector.
We then do the same thing for the two byte address.
The RTS is the end of the code which hijacked the RAM hook. We have now pointed the RAM hook to our “new” routine.
At new, all we do is increment whatever is in memory location $400 (the top left character of the 32-column screen). Right after that INC is our 3-byte “saved” buffer where the three bytes that used to be in the RAM hook are saved. This make our code do the INC and then the same three bytes that would have been done by the original RAM hook before we hijacked it.
On Color BASIC, the RAM hook starts out with 57 57 57 (RTS RTS RTS), so the new routine would appear as:
new: inc $4000 rts rts rts
For Extended Color BASIC, where the RAM hook is turned in to JMP $827e, it would become:
new: inc $4000 jmp $827e
When we EXEC the init code, out INC routine is patched in. From that point on, any output causes the top left character of the screen to increment. This will happen for ANY output, even to a printer or cassette file, since this code does not bother checking for the device type.
Here is a BASIC loader program, generated by LWTOOLS:
10 READ A,B 20 IF A=-1 THEN 70 30 FOR C = A TO B 40 READ D:POKE C,D 50 NEXT C 60 GOTO 10 70 END 80 DATA 16128,16154,182,1,103,183,63,27,190,1,104,191,63,28,134,126,183,1,103,142,63,24,191,1,104,57,124,4,0,-1,-1
Load that in to a CoCo (or emulator), and RUN it to get the code in memory starting at $3f00.
After RUN, you should be able to EXEC &H3f00 and the hook will be installed.
Now, for something real, you’d want to find a safe place to store the new hook code. At the very least, we should have a CLEAR 200,&H3f00 or similar in this program to ensure BASIC doesn’t overwrite the assembly code. This should be enough for a simple proof-of-concept.
The RAM hooks we have available include:
- OPEN COMMAND
- DEVICE NUMBER VALIDITY CHECK
- SET PRINT PARAMETERS
- CONSOLE OUT
- CONSOLE IN
- INPUT DEVICE NUMBER CHECK
- PRINT DEVICE NUMBER CHECK
- CLOSE ALL FILES
- CLOSE ONE FILE
- BREAK CHECK
- INPUTTING A BASIC LINE
- TERMINATING BASIC LINE INPUT
- EOF COMMAND
- EVALUATE AN EXPRESSION
- RESERVED FOR ON ERROR GOTO CMD
- ERROR DRIVER
- ASCII TO FLOATING POINT CONV.
- BASIC’S COMMAND INTERP. LOOP
- RESET/SET/POINT COMMANDS
- SECONDARY TOKEN HANDLER
- RENUM TOKEN CHECK
- EXBAS’ GET/PUT
- CRUNCH BASIC LINE
- UNCRUNCH BASIC LINE
There are many possibilities here, including adding new commands.
Until next time…