Hacking the Color BASIC PRINT command – part 6

See Also: part 1, part 2, part 3, part 4, part 5 and part 6 (and maybe more to come…)

In part 5, I presented an update to the “PRINT can move the cursor” hack which would turn that off when you were typing from outside a running program. It did this by checking a Color BASIC “variable” that contained the current line being processed. When the program is not running, that value is set to 65535 (&HFFFF in hex). My simple check should have been enough to skip processing the special characters when in this direct mode:

* Do this only if NOT in Direct mode. Problem: After a BREAK, CURLIN
* has not been updated yet, so the very first line you type will be
* processing the special characters. Lines after that will not. Trying
* to find a different way to detect this.
pshs a save A
lda CURLIN GET CURRENT LINE NUMBER (CURLIN)
inca TEST FOR DIRECT MODE
puls a restore A
beq continue if 0, in direct mode.

I quickly learned that when a program stops running, this value is not updated to &HFFFF until AFTER the next line is entered. This snippet is from the Github archive of tomctomc:

https://github.com/tomctomc/coco_roms/blob/master/bas.asm

; THIS IS THE MAIN LOOP OF BASIC WHEN IN DIRECT MODE
LAC73 JSR >LB95C ; MOVE CURSOR TO START OF LINE
LAC76 LDX #LABEE-1 ; POINT X TO OK, CR MESSAGE
JSR >LB99C ; PRINT OK, CR
LAC7C JSR >LA390 ; GO GET AN INPUT LINE
LDU #$FFFF ; THE LINE NUMBER FOR DIRECT MODE IS $FFFF
STU CURLIN ; SAVE IT IN CURLIN

BASIC does not update the value until after the first line is entered, which means my attempt to ignore cursor movements when typing would not work for the first line you typed after a program stopped (BREAK, END, STOP, ?SN ERROR, etc.).

William “Lost Wizard” Astle pointed me to another vector I could use to determine when a program stopped running: RVEC12. This is called the “line input” routine, which confused me at first since LINE INPUT did not exist until Extended Color BASIC ROMs were added. But, the naming intent appears to just be “input a line” versus “for the LINE INPUT command”.

It looks like this (again, from the tomctomc disassembly):

; THIS IS THE ROUTINE THAT GETS AN INPUT LINE FOR BASIC
; EXIT WITH BREAK KEY: CARRY = 1
; EXIT WITH ENTER KEY: CARRY = 0
LA38D JSR >CLRSCRN ; CLEAR SCREEN
LA390 JSR >RVEC12 ; HOOK INTO RAM
CLR IKEYIM ; RESET BREAK CHECK KEY TEMP KEY STORAGE
LDX #LINBUF+1 ; INPUT LINE BUFFER
LDB #1 ; ACCB CHAR COUNTER: SET TO 1 TO ALLOW A
; BACKSPACE AS FIRST CHARACTER
LA39A JSR >LA171 ; GO GET A CHARACTER FROM CONSOLE IN

The code at LA390 is called when BASIC wants to input a line. That code jumps out to a RAM hook RVEC12 so that code could run anything it needed to first, such as new code that changes CURLIN to FFFF right then.

I added a new bit of code to my program to save whatever is in RVEC12, then make it point to my new code:

* Hijack the BASIC line input routine.
lda RVEC12 get op code
sta savedrvec12 save it
ldx RVEC12+1 get address
stx savedrvec12+1 save it

lda #$7e op code for JMP
sta RVEC12 store it in RAM hook
ldx #newcode2 address of new code
stx RVEC12+1 store it in RAM hook

Then, in my program, I added a “newcode2” routine:

* William Astle:
* "RVEC12 would be right. You can clobber X in this case. You would check 4,s
* to see if it's $AC7F. If it is, you just set CURLIN to $FFFF. This works
* around the unfortunate ordering of the instructions in the immediate mode
* loop."
newcode2
ldx 2,s get what called us
cmpx #$ac7f
bne continue2
ldx #$ffff
stx CURLIN

continue2
savedrvec12 rmb 3 call regular RAM hook
rts just in case...

The “lda 2,s” retrieves whatever is on the stack which would be the return address we go back to at an rts. (I think the 4 in William’s comment may be a typo; I checked there and did not get an address match, but I do at 2,s.)

AC7F is this bit in BASIC:

; THIS IS THE MAIN LOOP OF BASIC WHEN IN DIRECT MODE
LAC73 JSR >LB95C ; MOVE CURSOR TO START OF LINE
LAC76 LDX #LABEE-1 ; POINT X TO OK, CR MESSAGE
JSR >LB99C ; PRINT OK, CR
LAC7C JSR >LA390 ; GO GET AN INPUT LINE
LDU #$FFFF ; THE LINE NUMBER FOR DIRECT MODE IS $FFFF
STU CURLIN ; SAVE IT IN CURLIN
BCS LAC7C ; BRANCH IF LINE INPUT TERMINATED BY BREAK
TST CINBFL ; CHECK CONSOLE INPUT BUFFER STATUS

At label LAC7C is “jsr >LA390”. This does a jump subroutine to code that calls the RAM hook:

; THIS IS THE ROUTINE THAT GETS AN INPUT LINE FOR BASIC
; EXIT WITH BREAK KEY: CARRY = 1
; EXIT WITH ENTER KEY: CARRY = 0
LA38D JSR >CLRSCRN ; CLEAR SCREEN
LA390 JSR >RVEC12 ; HOOK INTO RAM
CLR IKEYIM ; RESET BREAK CHECK KEY TEMP KEY STORAGE
LDX #LINBUF+1 ; INPUT LINE BUFFER
LDB #1 ; ACCB CHAR COUNTER: SET TO 1 TO ALLOW A

My “newcode2” at RVEC12 looks like it should expect the rts value on the stack of be after LA390, which I think would be at “2,s” on the stack (?), making the “2,s” be the address that called LA390, matching William’s “4,s” to get to it. Not sure if I understand this, but that didn’t work so I did some debug code to put the stack values on the 32 column screen bytes and PEEKed them out to see what was there. That is how I found it at “2,s”.

But I digress… The point seems to be when I am running my code, IF I can tell it was called from this block:

LAC7C           JSR         >LA390          ; GO GET AN INPUT LINE
LDU #$FFFF ; THE LINE NUMBER FOR DIRECT MODE IS
STU CURLIN ; SAVE IT IN CURLIN

…then I know it is the correct spot where I can safely (?) store FFFF in CURLIN, then call whatever code was in the original RAM hook to do the actual line input (which is now running with FFFF in CURLIN). Then it returns from that and sets CURLIN to FFFF in the ROM (which has already been done by my newcode2).

This seems to work, but perhaps William can chime in and explain what I missed with my stack stuff.

Here is the new version of my program:

* lwasm consmove4.asm -fbasic -oconsmove4.bas --map

* Allow embedded characters to move the cursor in a PRINT.

UP equ 'u character for up
DOWN equ 'd character for down
LEFT equ 'l character for left
RIGHT equ 'r character for right

CURLIN equ $68 *PV CURRENT LINE # OF BASIC PROGRAM, $FFFF = DIRECT
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
RVEC12 equ $182 inputting a BASIC line
VIDRAM equ $400 VIDEO DISPLAY AREA

org $7f00

init
* Hijack the CONOUT routine.
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

* Hijack the BASIC line input routine.
lda RVEC12 get op code
sta savedrvec12 save it
ldx RVEC12+1 get address
stx savedrvec12+1 save it

lda #$7e op code for JMP
sta RVEC12 store it in RAM hook
ldx #newcode2 address of new code
stx RVEC12+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)

* Do this only if NOT in Direct mode. Problem: After a BREAK, CURLIN
* has not been updated yet, so the very first line you type will be
* processing the special characters. Lines after that will not. Trying
* to find a different way to detect this.
pshs a save A
lda CURLIN GET CURRENT LINE NUMBER (CURLIN)
inca TEST FOR DIRECT MODE
puls a restore A
beq continue if 0, in direct mode.

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...


* William Astle:
* "RVEC12 would be right. You can clobber X in this case. You would check 4,s
* to see if it's $AC7F. If it is, you just set CURLIN to $FFFF. This works
* around the unfortunate ordering of the instructions in the immediate mode
* loop."
newcode2
ldx 2,s get what called us
cmpx #$ac7f
bne continue2
ldx #$ffff
stx CURLIN

continue2
savedrvec12 rmb 3 call regular RAM hook
rts just in case...

end

And this now lets me hit BREAK (or whatever) in my program and then type those “u”, “d”, “l” and “r” characters and see them as lowercase as I type them:

But there are still issues…

But there are still issues. One thing I did not consider is that now I cannot “test” an embedded PRINT from the command line. Typing this:

PRINT "XXXlllYYY";

…should print “XXX” then move left three times and print “YYY” so it only shows YYY. But with the PRINT hack not displaying cursor moves in direct mode, you just get:

So, depending on your preference, you may want to NOT have this extra code active so you just see cursor movements even when you are typing in the program.

Thoughts? Let me know in the comments.

Here is the current BASIC loader:

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,32639,182,1,103,183,127,128,190,1,104,191,127,129,134,126,183,1,103,142,127,47,191,1,104,182,1,130,183,127,144,190,1,131,191,127,145,134,126,183,1,130,142,127,132,191,1,131,57,13,111,38,77,52,2,150,104,76,53,2,39,68,50,98,52,22
90 DATA 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,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,32643,32655,57,174,98,140,172
100 DATA 127,38,5,142,255,255,159,104,32659,32659,57,-1,-1

I added the CLEAR 200,&H7F00 at the top. Just load this, RUN it, then EXEC &H7F00 and then you have the new PRINT stuff with cursor movements.

What next? I’d like to add the ability to assign which characters it uses by making the routine work with DEF USR so you could do something like:

X=USR0("udlr")

Then you could pass in whatever four characters you wanted for the cursor movements. Maybe this could also be used to disable it with something like X=USR0(“”) that did not specify anything to use.

Again, thoughts?

Until next time…

2 thoughts on “Hacking the Color BASIC PRINT command – part 6

  1. William Astle

    You are correct. The 4,s was a “mailer code” typo thing.

    It will be interesting to see how you fare with DEF USR if you go down that path. There are some Mysterious Traps For The Unwary down that path.

    Reply

Leave a Reply

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