Color BASIC RAM hooks – part 1

See Also: part 1 and part 2.

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:

jmp $8273

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.

So what?

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
  • PRINT
  • INPUT
  • BREAK CHECK
  • INPUTTING A BASIC LINE
  • TERMINATING BASIC LINE INPUT
  • EOF COMMAND
  • EVALUATE AN EXPRESSION
  • RESERVED FOR ON ERROR GOTO CMD
  • ERROR DRIVER
  • RUN
  • ASCII TO FLOATING POINT CONV.
  • BASIC’S COMMAND INTERP. LOOP
  • RESET/SET/POINT COMMANDS
  • CLS
  • 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…

3 thoughts on “Color BASIC RAM hooks – part 1

  1. William Astle

    For accuracy, you should write “JSR” to the RAM hook rather than JMP. The key is that it has to be a JSR so the RTS returns to the original code flow after the call to the RAM hook.

    Reply
    1. Allen Huffman Post author

      I as following how the ROMs did it. I noticed that somewhere they just JMP back out to another ROM, so I guess that’s why they had JMP there.

      Reply
    2. Darren A

      No, you don’t want a JSR in the RAM hook becasue then the code it calls would return to the next RAM hook in the table. The ROM code JSRs to the RAM hook which JMPs to the hook routine.

      Reply

Leave a Reply to Darren ACancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.