See Also: part 1, part 2, part 3, part 4 and more to come…
Just because it works, doesn’t mean it’s correct.
Keep this in mind as I continue my exploration of using Color BASIC RAM hooks to change how the PRINT output works.
Previously, I created a small assembly language routine that would intercept BASIC’s output to the 32 column screen and then treat a lowercase “r” (as in “right”) as a transparent space. My proof-of-concept seemed to work.
Today, I present updated code that now supports “u” for up, “d” for down, “l” for left, and “r” for right, semi-simulating how you can embed cursor movements in a PRINT command on a VIC-20 (and, I assume, the C64 and maybe even the PET before it).
Let me walk you through what I came up with:
* lwasm consmove2.asm -fbasic -oconsmove2.bas --map
* Convert any lowercase characters written to the
* screen (device #0) to uppercase.
UP equ 'u character for up
DOWN equ 'd character for down
LEFT equ 'l character for left
RIGHT equ 'r character for right
I start with some definitions of the characters I want to use to move the cursor UP, DOWN, LEFT or RIGHT. I wanted to be able to easily change these characters. In a future installment, I may try to implement this using “DEF USR” so you could do something like X=USR0(“abcd”) and pass in a string containing four characters to use for up, down, left and right. For now, hard-coded.
DEVNUM equ $6f device number being used for I/O
CURPOS equ $88 location of cursor position in RAM
RVEC3 equ $167 console out RAM hook
VIDRAM equ $400 VIDEO DISPLAY AREA
These next defines are things that the Color BASIC Unravelled disassembly listings had defined. The first is the memory location that contains the current device number for output (0 for screen, -2 to printer, etc.). After that is where BASIC tracks the cursor position on the 32 column screen. Neat is the address of the RAM Vector for the console out routine. Lastly, VIDRAM is the start of the 32-column screen in memory (1024, or &H400 in hex).
org $7f00
init
lda RVEC3 get op code
sta savedrvec save it
ldx RVEC3+1 get address
stx savedrvec+1 save it
lda #$7e op code for JMP
sta RVEC3 store it in RAM hook
ldx #newcode address of new code
stx RVEC3+1 store it in RAM hook
rts done
uninstall
* TODO
Here, I changed the start address of this program to &H7F00 instead of &H3F00. If trying this on a 16K machine, you would have to change it back. Once I know how big the final assembly code is, I’ll probably move it as close as possible to the end of the 32K RAM used by BASIC so it can leave as much RAM for the BASIC program as possible.
This init routine pulls out the three bytes at RVEC3 (which are either “rts” three times for Color BASIC, or patched to be a “JMP xxxx” 3-byte instruction if other BASIC ROMs (Extended, Disk, CoCo 3) are installed I save those to 3-bytes of reserved memory at the end of the program.
I then replace it with a JMP (&H73) instruction followed by the two byte address of my “newcode” that I want to run any time a character is output.
For now, there is no way to uninstall this routine, but I left myself a reminder I should create an uninstall routine. I think I may just make it so if you EXEC the first time, it installs, and if you EXEC again, it uninstalls, rather than using two different memory locations the user would need to know. We shall see.
newcode
* Do this only if DEVNUM is 0 (console)
tst DEVNUM is DEVNUM 0?
bne continue not device #0 (console)
leas 2,s remove PC from stack since we won't be returning there.
* Now this is the start of what Color BASIC ROM does for PUTCHR:
* PUT A CHARACTER ON THE SCREEN
LA30A
PSHS X,B,A SAVE REGISTERS
LDX CURPOS POINT X TO CURRENT CHARACTER POSITION
When BASIC needs to output a character, it ends up at the PUTCHR routine in the ROM, starting at address A282. The very first thing it does is “jsr RVEC3” which means it will now jump to this “newcode” routine. The character to output will be in register A.
The first thing I do is check the DEVNUM value to see which device number is being used. I use “tst” to check for zero. If the device is not zero (screen), I branch to a “continue” routine which will be the code that was originally in the RVEC3. (Thus, on Color BASIC it will just branch to an “rts” return, or it will branch to whatever “jmp xxxx” was in the vector before I hijacked it. More on this in a moment.
If I get past that, I will be doing some work and then jumping back into the ROM so the rest of BASICs output routine can run. In that situation, I will not be doing an “rts” from my custom code. I may be wrong on this, but my understanding is that I need to remove the return address off the stack (since I will not be returning), which I do by moving the S stack pointer up by 2. Is this what I need to be doing?
Since BASIC already checks for special characters like backspace and ENTER, I was able to pattern my code after what the ROM does for those and leave out the part where it puts a character on the screen. I take the first two line that BASIC would have done (from LA30A in the Unravelled listing) and put them into my code. This then lets me have my custom blocks checking for special characters the same way BASIC does for the ones it handles.
checkup
cmpa #UP
bne checkdown
CMPX #VIDRAM+32 second line or lower?
blt goLA35D disallow if on top line.
leax -32,x move up one line
bra done
The first check is for the UP character. If not seen, we branch to the “checkdown” routine just after this one. If it was UP, then we check to see if X is 32 bytes past the start of screen memory — the start of the second line. If you are on the top line, moving UP is ignored and we branch to a label that will then JMP back into the Color BASIC ROM to finish the normal checks that BASIC does.
Else, decrement X (cursor position) by 32 to move up one line, then branch to the “done” routine which will get us back into BASIC ROM code.
checkdown
cmpa #DOWN
bne checkleft
cmpx #VIDRAM+512-32
bge goLA35D disallow if on bottom line.
leax 32,X move down one line
bra done
The check for DOWN follows a similar pattern, this time disallowing down if you are on the bottom line of the screen. This is different than how Commodore handles it, since down on the Commodore will cause the screen to scroll up. I plan to fix this later to make it act more like the VIC-20 does.
If we cannot move down, we branch to a label that will then JMP back into the Color BASIC ROM to finish the normal checks that BASIC does. If we can move down, the cursor position in X is increased by 32.
NOTE: During my testing, I found a VIC-20 bug. If you cursor to the bottom right of the screen then press RIGHT, the first time you do this is scrolls up and then the cursor appears one line before the last line. If you then cursor to the bottom right position again and go RIGHT, it scrolls up and now the cursor is on the bottom left position. Odd. I should find a VIC-20 BASIC disassembly and see if I can figure out what causes it do misbehave, but only the first time.
checkleft
cmpa #LEFT
bne checkright
cmpx #VIDRAM top left of screen?
beq goLA35D
leax -1,X move left one character
bra done
The process repeats again for LEFT. This is much like what BASIC does with backspace (BS) where it disallows it if you are in the top left position of the screen. If we cannot move left, we branch to a label that will then JMP back into the Color BASIC ROM to finish the normal checks that BASIC does. If we can, the cursor position is decremented by 1.
checkright
cmpa #RIGHT
bne goLA30E
cmpx #VIDRAM+511 bottom right of screen
beq goLA35D
leax 1,x increment X, skipping that location.
bra done
Lather, rinse, repeat. For RIGHT, I disallow moving right from the bottom right character position. This is another difference from VIC-20 which I plan to fix later. If we cannot, we branch to the same label that gets us back into the BASIC ROM routine. If we can move right, the cursor position is incremented by 1.
And now some of the labels…
goLA30E
jmp $A30E jump back into Color BASIC ROM code.
done
stx CURPOS update cursor position
goLA35D
jmp $A35D jump back into Color BASIC ROM code.
A30E in the disassembly is the first instruction right after the two lines I cloned into my routine at my LA30A label. Thus, I start out with those first two lines, then do my code and either jump back in to let the ROM code continue, OR I modify the cursor position then jump to DONE so it can finish up.
“done” will update the saved cursor position that is in the X register, then jump to A35D which is the final line of the output ROM routine where the registers that were pushed/saved at the top of the routine are pulled/restored (including PC which acts as an rts to whoever called the routine).
continue
savedrvec rmb 3 call regular RAM hook
rts just in case...
end
Lastly, if the original check for console (device 0) was not true, that code jumps to this end part so it can continue on to whatever the vector was pointing to before we hijacked it.
Here is a BASIC loader for this program. I added LINE 5 to reserve memory for the assembly, else BASIC will clobber it with string storage;
5 CLEAR 200,&H7F00
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 32512,32607,182,1,103,183,127,96,190,1,104,191,127,97,134,126,183,1,103,142,127,24,191,1,104,57,13,111,38,68,50,98,52,22,158,136,129,117,38,10,140,4,32,45,50,48,136,224,32,43,129,100,38,10,140,5,224,44,36,48,136,32,32,29,129,108,38,9
90 DATA 140,4,0,39,22,48,31,32,16,129,114,38,9,140,5,255,39,9,48,1,32,3,126,163,14,159,136,126,163,93,32611,32611,57,-1,-1
And here is the complete assembly listing:
* lwasm consmove2.asm -fbasic -oconsmove2.bas --map
* Convert any lowercase characters written to the
* screen (device #0) to uppercase.
UP equ 'u character for up
DOWN equ 'd character for down
LEFT equ 'l character for left
RIGHT equ 'r character for right
DEVNUM equ $6f device number being used for I/O
CURPOS equ $88 location of cursor position in RAM
RVEC3 equ $167 console out RAM hook
VIDRAM equ $400 VIDEO DISPLAY AREA
org $7f00
init
lda RVEC3 get op code
sta savedrvec save it
ldx RVEC3+1 get address
stx savedrvec+1 save it
lda #$7e op code for JMP
sta RVEC3 store it in RAM hook
ldx #newcode address of new code
stx RVEC3+1 store it in RAM hook
rts done
uninstall
* TODO
newcode
* Do this only if DEVNUM is 0 (console)
tst DEVNUM is DEVNUM 0?
bne continue not device #0 (console)
leas 2,s remove PC from stack since we won't be returning there.
* Now this is the start of what Color BASIC ROM does for PUTCHR:
* PUT A CHARACTER ON THE SCREEN
LA30A
PSHS X,B,A SAVE REGISTERS
LDX CURPOS POINT X TO CURRENT CHARACTER POSITION
checkup
cmpa #UP
bne checkdown
CMPX #VIDRAM+32 second line or lower?
blt goLA35D disallow if on top line.
leax -32,x move up one line
bra done
checkdown
cmpa #DOWN
bne checkleft
cmpx #VIDRAM+512-32
bge goLA35D disallow if on bottom line.
leax 32,X move down one line
bra done
checkleft
cmpa #LEFT
bne checkright
cmpx #VIDRAM top left of screen?
beq goLA35D
leax -1,X move left one character
bra done
checkright
cmpa #RIGHT
bne goLA30E
cmpx #VIDRAM+511 bottom right of screen
beq goLA35D
leax 1,x increment X, skipping that location.
bra done
goLA30E
jmp $A30E jump back into Color BASIC ROM code.
done
stx CURPOS update cursor position
goLA35D
jmp $A35D jump back into Color BASIC ROM code.
continue
savedrvec rmb 3 call regular RAM hook
rts just in case...
end
And now I can write a program like this:

As I typed that in, after the four Xs I typed a lowercase “d” for down, then four lowercase “l” for left, then X and two “r” then another X, then “dlll” followed by the four Xs again. It looks weird as it moves the cursor around as I type, but I will be fixing that with more additions to this routine in a future installment.
For now, it lets me put this 4×3 thing on the screen without erasing stuff around or behind it:

Progress!
Please, folks, let me know in the comments if you see something I am doing wrong. Am I handling the stack pointer thing properly? I am just experimenting and … well, “it works for me.”
To be continued…













