REM > EURO414/S
REM Source for Euro-Beeb Terminal 4.14
:
REM EuroBeeb Terminal allows the CUBE-65 or EuroBEEB to be used
REM as a 6502 second processor for the BBC/Master. The communication
REM is via the BBC's and the CUBE's serial ports.
:
REM NOTE: This is a bug-fixed version of 4.13.
REM EURO 4.13 stores a configuration value in osbyte 249, corrupting
REM the Break Intercept Vector. This crashes the system on BREAK if
REM the BIV is in use. This patched version uses osbyte 247, disabling
REM the BIV as a side effect.
REM EURO 4.13 text printing corrupts the error pointer. Do *HELP
REM then REPORT and a nonsense message is displayed. This patched
REM version uses &F6/7.
:
OS_CLI=&FFF7:OSBYTE=&FFF4:OSWORD=&FFF1:OSWRCH=&FFEE
OSWRCR=&FFEC:OSNEWL=&FFE7:OSASCI=&FFE3:OSRDCH=&FFE0
 
load%=&8000
DIM mcode% &2014
FOR P=0 TO 1
P%=load%:O%=mcode%
[OPT P*3+4
JMP L81E1               :\ Language Entry
JMP L8047               :\ Service Entry
EQUB &C2                :\ ROM type=Lang+Serv+6502
EQUB Copyright-Start
.L8008
EQUB &04                :\ Default *EURO parameter
.L8009
EQUS "EURO-Beeb Terminal"
EQUB 0
EQUS "21 FEB 1986"
.Copyright
EQUB 0
EQUS "(C)Control Universal Ltd"
EQUB 0

.L8042
EQUS "EURO"
EQUB &FF

\ SERVICE HANDLER
\ ===============
.L8047
PHP:PHA:TXA:PHA:TYA:PHA :\ Save all registers
TSX:LDA &0103,X         :\ Get stacked A
JSR L80AD               :\ Call service hander
TAX:BEQ L805E           :\ If call claimed, jump to claim call
PLA:TAY:PLA:TAX:PLA:PLP :\ Restore all registers
RTS                     :\ Return unclaimed
 
.L805E
PLA:PLA:TAX:PLA:ROR A
PLP:ROL A:LDA #&00      :\ Claim call
RTS


\ Print inline text
\ =================
.L8068
PLA:STA &F6:PLA:STA &F7   :\ Pop return address to &F6/7 (was &FD/E)
TYA:PHA:LDY #&00:BEQ L8077
.L8074
JSR OSASCI
.L8077
INC &F6:BNE L807D:INC &F7 :\ Increment address (was &FD/E)
.L807D
LDA (&F6),Y               :\ Get character (was &FD)
CMP #&EA:BNE L8074        :\ If not 'NOP' opcode, loop to print it
PLA:TAY:JMP (&00F6)       :\ Pop Y and jump back to code (was &FD)


\ Check for 'EURO' string
\ =======================
.L8088
STY &E6                   :\ Save Y, corrupting &E6
.L808A
LDA L8042,X:BMI L80A3     :\ End of match string
LDA (&F2),Y
CMP #&2E:BEQ L80A2        :\ Match found with '.'
CMP L8042,X:BNE L809E     :\ Exit with no match found
INY:INX:BNE L808A         :\ Increment and check next chars
.L809E
LDY &E6:SEC:RTS           :\ Restore Y, set carry to indicate no match

\ Skip spaces
\ ===========
.L80A2
INY
.L80A3
LDA (&F2),Y               :\ Fetch character
CMP #&20:BEQ L80A2        :\ Skip any spaces
CMP #&0D:CLC:RTS          :\ Clear carry, set Z if <cr> found


\ CHECK SERVICE CALLS
\ ===================
.L80AD
CMP #&04:BEQ L80CD  :\ *COMMAND
CMP #&09:BEQ L80ED  :\ *HELP
CMP #&27:BEQ L80BD  :\
CMP #&03:BNE L80CC  :\ Exit if not Boot FS


\ SERVICE 3 - BOOT FS or SERVICE &27 - RESET
\ ==========================================
.L80BD
PHA:LDA #&FD:JSR L82D4 :\ *FX253,0,255 - read last BREAK type
BEQ L80CB              :\ If zero, soft BREAK, skip past and return
LDA L8008:STA &0287    :\ *BUG* Copy default *EURO parameter to *FX247 location,
                       :\ (was *FX249, corrupting Break Intercept Vector)
PLA
.L80CC
RTS


\ SERVICE 4 - *COMMAND
\ ==================== 
.L80CD
LDX #&00:JSR L8088     :\ Check for 'EURO' command
BCC L80D7              :\ Jump to *EURO routine
LDA #&04:RTS           :\ Return unclaimed


\ *EURO (<n>)
\ ===========
.L80D7
BEQ L80E6              :\ Jump if no parameters
CMP #&31:BCC L80E6     :\ If <'1', treat as no parameters
CMP #&35:BCS L80E6     :\ If >'4', treat as no parameters
AND #&07:STA &0287     :\ Store parameter in *FX247 location (was
                       :\ *FX249, corrupting Break Intercept Vector)
.L80E6
LDX &F4:LDA #&8E
JMP OSBYTE             :\ Enter this ROM as a language


\ SERVICE 9 - *HELP
\ ================= 
.L80ED
LDA (&F2),Y            :\ Get character from *HELP command
CMP #&0D:BNE L8119     :\ Jump if parameters exist
JSR L8105              :\ Print ROM title and *EURO parameter
JSR L8068              :\ Print inline text
EQUB 13:EQUS "EURO":EQUB 13
NOP
.L8102
LDA #&09:RTS           :\ Return unclaimed

\ Print ROM title
\ ---------------
.L8105
LDX #&FF:LDA #&0D      :\ Prepare to print newline
.L8109
JSR OSASCI:INX
LDA L8009,X:BNE L8109  :\ Get character, loop until &00 byte
LDA &0287:JSR L852F:RTS:\ Print *FX247 value (set by *EURO)
                       :\ (was *FX249 value)

\ *HELP with parameters
\ ---------------------
.L8119
LDX #&00
JSR L8088:BCS L8102    :\ If 'EURO' no present, exit unclaimed
JSR L8105              :\ Print ROM title
JSR L8068              :\ Print inline text
EQUB 13:EQUS "shift"
NOP
LDA &0289              :\ Get *EURO parameter
CMP #&04:BNE L813F     :\ Skip past if not 4
JSR L8068              :\ Print inline text
EQUS "/ctrl"           :\ If *EURO 4, then print "shift/ctrl", else "shift"
NOP
.L813F
JSR L8068              :\ Print inline text
EQUS " functions keys:":EQUB 13
EQUS "  f0  *command":EQUB 13
NOP
LDA &0289              :\ Get *EURO parameter
CMP #&03:BNE L81C5     :\ Skip past if not 3
JSR L8068              :\ Print inline text
EQUS "  f1  Select new station":EQUB 13
EQUS "  f2  Poll all stations":EQUB 13
EQUS "  f8  software RESET current station":EQUB 13
NOP
.L81C5
JSR L8068              :\ Print inline text
EQUW "  f9  hardware RESET":EQUB 13
NOP
LDA #0:RTS             :\ Claim and return


\ LANGUAGE STARTUP
\ ================
.L81E1
CMP #&01:BEQ L81E6:RTS       :\ Exit if not A=1 language startup
.L81E6
LDX #&FF:TXS:CLI             :\ Clear stack, enable IRQs
LDA #L88A0 AND 255:STA &0202 :\ Set up error handler
LDA #L88A0 DIV 256:STA &0203
LDA #&F7:JSR L82D4:STX &60   :\ Fetch byte stored by *EURO command
                             :\ (was LDA #&F9)
LDA #&02:LDX #&01:JSR OSBYTE :\ *FX2,1 - select serial input
LDA #&B5:LDX #&01:LDY #&00
JSR OSBYTE                   :\ *FX181,1 - select raw serial input
JSR L8068                    :\ Print inline text
EQUS "EURO"
NOP
LDA &60:JSR L852F            :\ Print passed parameter in decimal
JSR L8068                    :\ Print inline text
EQUS "   Issue X4.14":EQUB 13
NOP
.L822B
JSR L8262:JSR L83F9

\ Main loop
\ ---------
.L8231
JSR L82AB                    :\ Select keyboard input, enable serial
LDA #&D8:JSR L82D4:BNE L8252 :\ If function key pending, jump to read input
LDA #&80:LDX #&FE:JSR OSBYTE :\ Read ADVAL(-2) - serial input buffer
TXA:BEQ L8248                :\ Buffer empty, check for keyboard input
JSR L82DD                    :\ Deal with serial input
.L8248
LDA #&80:LDX #&FF:JSR OSBYTE :\ Read ADVAL(-1) - keyboard buffer
TXA:BEQ L8231                :\ Buffer empty, loop back

\ Keyboard input pending
\ ----------------------
.L8252
LDA #&80:LDX #&FD:JSR OSBYTE :\ Read ADVAL(-3) - serial output buffer
TXA:BEQ L8231                :\ No free space, loop back until space appears
JSR L8351:JMP L8231          :\ Deal with keyboard input and loop back

\ Set up function keys
\ --------------------
.L8262
LDA &60:CMP #&04:BNE L828F   :\ If *EURO 4, jump forward to set up
LDA #&E1:LDX #&80
LDY #&00:JSR OSBYTE          :\ *FX225,128 - fkey base=128
LDA #&E2:LDX #&90
LDY #&00:JSR OSBYTE          :\ *FX226,144 - shift-fkey base=144
LDA #&E3:LDX #&A0
LDY #&00:JSR OSBYTE          :\ *FX227,160 - ctrl-fkey base=160
LDA #&E4:LDX #&F0
LDY #&00:JSR OSBYTE          :\ *FX228,224 - sh-ct-fkey base=224
JMP L8298                    :\ Jump forward to set cursor keys, etc.

\ *EURO 4 function key setup
\ --------------------------
.L828F
LDA #&E2:LDX #&F0
LDY #&00:JSR OSBYTE          :\ *FX226,224 - shift-fkey base=224
.L8298
JSR L82C4                    :\ Set cursor keys
LDA #&0F:LDX #&00:JSR OSBYTE :\ Clear all buffers
.L82A2
LDA #&E5:LDX #&01
LDY #&00:JSR OSBYTE          :\ *FX229,1 - Escape key gives ASCII
.L82AB
LDA #&02:LDX #&02:JSR OSBYTE :\ *FX2,2 - enable serial input, read from keyboard
RTS

\ Set cursor and escape keys to normal
\ ------------------------------------
.L82B3
LDA #&E5:LDX #&00
LDY #&00:JSR OSBYTE          :\ *FX229,0 - Escape generates Escape state
.L82BC
LDA #&04:LDX #&00:JSR OSBYTE :\ Enable cursor keys
.L82C3
RTS

\ Set cursor keys according to *EURO parameter
\ --------------------------------------------
.L82C4
LDA &60:BEQ L82C3            :\ If &00, exit
LDX #&01:CMP #&04:BNE L82CF  :\ If not 4, cursor keys are &87..&8B
INX                          :\ *EURO 4, cursor keys are f11-f15
.L82CF
LDA #&04:JMP OSBYTE

\ Read OSBYTE value to A
\ ----------------------
.L82D4
LDX #&00:LDY #&FF:JSR OSBYTE :\ Read OSBYTE value
TXA:RTS                      :\ Pass to A and return


\ Deal with serial input
\ ======================
.L82DD
LDA #&02:LDX #&01:JSR OSBYTE :\ *FX 2,1 - select serial input
LDA #&03:LDX #&00:JSR OSBYTE :\ *FX 3,0 - Output to VDU, etc.
JSR L8844:BCS L82F3          :\ Get character, jump if escaped command
JMP OSWRCH                   :\ Output to VDU and return

\ Escaped command received
\ ------------------------
.L82F3
LDX #&FF:STA &64             :\ Store command byte in &64
.L82F7
INX:LDA L831C,X              :\ Get byte from command table
CMP &64:BEQ L8304            :\ If it matches, jump to call code
CMP #&FF:BNE L82F7           :\ If not end of table, loop to check next entry
RTS                          :\ Command not found, return silently
.L8304
JSR L830E:JMP L82A2          :\ Call code, return via Escape off, serial on
 
\ Perform action on Ctrl-Shift-fkey
\ =================================
.L830A
SEC:SBC #&E5:TAX             :\ Index into address table
.L830E
LSR &61:LSR &62              :\ Clear &61 bit7 and &62 bit 7 - not in command
LDA L833C,X:PHA              :\ Stack address byte
LDA L8327,X:PHA              :\ Stack address byte
SEC:RTS                      :\ Jump to routine with Carry set

\ Recognised serial link command bytes
\ ------------------------------------ 
.L831C
EQUB &07
EQUB &08
EQUB &FD
EQUB &FE
EQUB &FC
EQUB &FB
EQUB &FA
EQUB &05
EQUB &03
EQUB &00
EQUB &FF

\ Serial link and function key command address low bytes
\ ------------------------------------------------------
.L8327
EQUB (L86EC-1)AND255     :\ Link command 07 - OSBYTE
EQUB (L86B5-1)AND255     :\ Link command 08 - OSWORD
EQUB (L8688-1)AND255     :\ Link command FD - OSBGET
EQUB (L869D-1)AND255     :\ Link command FE - OSBPUT
EQUB (L8667-1)AND255     :\ Link command FC - OSFIND
EQUB (L863A-1)AND255     :\ Link command FB - OSARGS
EQUB (L8564-1)AND255     :\ Link command FA - OSGBPB
EQUB (L8733-1)AND255     :\ Link command 05 - *CAT
EQUB (L8739-1)AND255     :\ Link command 03 - *command
EQUB (L879B-1)AND255     :\ Link command 00 - SAVE file
EQUB (L874D-1)AND255     :\ Link command FF - LOAD file
EQUB (L8396-1)AND255     :\ Ctrl-Shift-f0 - Enter *command
EQUB (L83C8-1)AND255     :\ Ctrl-Shift-f1 - Select new station
EQUB (L8410-1)AND255     :\ Ctrl-Shift-f2 - Poll all stations
EQUB (L84CF-1)AND255     :\ Ctrl-Shift-f3 - do nothing
EQUB (L84CF-1)AND255     :\ Ctrl-Shift-f4 - do nothing
EQUB (L84CF-1)AND255     :\ Ctrl-Shift-f5 - do nothing
EQUB (L84CF-1)AND255     :\ Ctrl-Shift-f6 - do nothing
EQUB (L84CF-1)AND255     :\ Ctrl-Shift-f7 - do nothing
EQUB (L84D1-1)AND255     :\ Ctrl-Shift-f8 - Software reset
EQUB (L84D8-1)AND255     :\ Ctrl-Shift-f9 - Hardware reset

\ Serial link and function key command address high bytes
\ -------------------------------------------------------
.L833C
EQUB (L86EC-1)DIV256
EQUB (L86B5-1)DIV256
EQUB (L8688-1)DIV256
EQUB (L869D-1)DIV256
EQUB (L8667-1)DIV256
EQUB (L863A-1)DIV256
EQUB (L8564-1)DIV256
EQUB (L8733-1)DIV256
EQUB (L8739-1)DIV256
EQUB (L879B-1)DIV256
EQUB (L874D-1)DIV256
EQUB (L8396-1)DIV256
EQUB (L83C8-1)DIV256
EQUB (L8410-1)DIV256
EQUB (L84CF-1)DIV256
EQUB (L84CF-1)DIV256
EQUB (L84CF-1)DIV256
EQUB (L84CF-1)DIV256
EQUB (L84CF-1)DIV256
EQUB (L84D1-1)DIV256
EQUB (L84D8-1)DIV256


\ Key pressed on keyboard
\ =======================
.L8351
LDA #&02:LDX #&02:JSR OSBYTE :\ *FX2,2 - select keybaord input
JSR OSRDCH                   :\ Get character from input
CMP #&0D:BNE L8364           :\ If not <cr>, jump past
JSR L82C4:LDA #&0D
.L8364
CMP #&F0:BEQ L830A           :\ If Ctrl-Shift-f0, jump to fkey commands
BCC L8376                    :\ If key<&F0, jump to send to serial
CMP #&F9:BEQ L830A           :\ If Ctrl-Shift-f9, jump to fkey commands
BCS L8376                    :\ If key>&F9, jump to send to serial
LDX &60:CPX #&03:BEQ L830A   :\ If flag=3, then Ctrl-Shift-f1 to f8 jump to fkey commands

\ Send byte to serial
\ -------------------
.L8376
PHA:TXA:PHA:TYA:PHA          :\ Save registers
LDA #&03:LDX #&07:JSR OSBYTE :\ *FX3,7 - Output to serial only
TSX:LDA &0103,X:JSR OSWRCH   :\ Output stacked character
LDA #&03:LDX #&00:JSR OSBYTE :\ *FX3,0 - Output to VDU, etc.
PLA:TAY:PLA:TAX:PLA:RTS      :\ Restore registers and return


\ Ctrl-Shift-f0 - Prompt for *command
\ ===================================
.L8396
JSR OSNEWL
LDA #&2A:JSR OSWRCH          :\ Print '*' prompt
JSR L82B3:JSR L83AF          :\ Escape on, cursors keys normal, input line
JSR OS_CLI:JMP L83F9         :\ Do *command, jump to check response and return

\ OSWORD 0 control block
\ ----------------------
.L83AA
EQUW &0700:EQUB &50          :\ Input buffer at &0700, length &50 bytes
EQUB &20:EQUB &7E            :\ Acceptable characters &20 to &7E

.L83AF
LDX #&04
.L83B1
LDA L83AA,X:STA &0780,X      :\ Copy OSWORD 0 control block to &0780
DEX:BPL L83B1
LDX #&80:LDY #&07:LDA #&00   :\ Point to control block
JSR OSWORD:LDX #&00:LDY #&07 :\ Call OSWORD and return XY=>input text
.L83C7
RTS


\ Ctrl-Shift-f1 - Select new station
\ ===================================
\ Prompt for new number.
\ If 0..31, sends &9A,&80,&9A,num, if 128, sends &9A,&80
\ If no response after a bit, sends <cr>
.L83C8
JSR L8068                    :\ Print inline text
EQUB 13:EQUS "Station "
NOP
JSR L83AF                    :\ Input line to &700
STX &F2:STY &F3
LDY #&00:JSR L84F3           :\ Scan for decimal value
BCC L8403:BNE L8403          :\ Give error for 0 or no number
TXA:CMP #&80:BEQ L83F4       :\ If 128, jump to send with no prefix
CMP #&20:BCS L8403           :\ If >31, jump to give error
LDA #&80:JSR L885E:TXA       :\ Send &9A,&80 to serial port
.L83F4
STA &63:JSR L885E            :\ Save station in &63 and send &9A,num to serial port
.L83F9
JSR L84B9:BCC L83C7          :\ Wait a bit, if serial replies, jump to return
LDA #&0D:JMP L8376           :\ Send <cr> to serial and return
 
.L8403
BRK:EQUB &FC
EQUS "Bad number":BRK


\ Ctrl-Shift-f2 - Poll all stations
\ =================================
.L8410
JSR L8068                    :\ Print inline text
EQUB 13:EQUS "Poll all stations":EQUB 13
NOP
LDX #&80:LDY #&07            :\ Point to &0780
LDA #&1F:STA &0780           :\ End at station 31
LDA #&14:JSR L8482           :\ Poll stations 0 to 31, store results at &0781 onwards
LDA &63:JSR L885E            :\ Send &9A,(current station) to serial
LDX #&00                     :\ Start with station 0
.L843C
TXA:AND #&07:BNE &8444       :\ Print a newline every 8 items
JSR OSNEWL
.L8444
LDA &0781,X                  :\ Get response for this station
CMP #&FF:BEQ L8478           :\ If &FF (no response), print a white dot
STA &64:AND #&1F:TAY:LDA #&82:\ Save top 2 bits, prepare green text
BIT &64:BPL L845C            :\ If b7=0, jump to do green or yellow
LDA #&81:BVS L8460:LDA #&86  :\ If %10, cyan text; if %11 red text
.L845C
BVC L8460:LDA #&83           :\ If %00, green text; if %01, yellow text
.L8460
CPY &63:BNE L8466:LDA #&87   :\ If current station, override with white text
\
\ Print response b0-b5 as coloured text
\ b76 - %00 - green
\       %01 - yellow
\       %10 - cyan
\       %11 - red
\ Changes to white if current station
\ Prints white dot if no response
\
.L8466
JSR OSWRCH:TYA:JSR L852F     :\ Print response in decimal
.L846D
INX:CPX #&20:BNE L843C       :\ Increment station, loop until all done
JSR OSNEWL:JMP L83F9         :\ Print newline, check serial response and return

.L8478
JSR L8068                    :\ Print inline text
EQUB &87:EQUB &20:EQUB &2E   :\ White text, space, dot
NOP
JMP L846D                    :\ Jump back to step to next station


\ Poll stations 0 to (XY), store results at (XY+1) onwards
\ ========================================================
\ For each station, sends &9A,&80,&9A,&40+stn
.L8482
STX &64:STY &65              :\ Store pointer in XY
LDA #&80:JSR &885E           :\ Send &9A,&80 to serial
LDA #&15:LDX #&01:JSR OSBYTE :\ *FX21,1 - Clear serial input buffer
LDY #&00:STY &66             :\ Start with station 0
.L8496
LDA &66:ORA #&40             :\ Get &66 value OR &40
JSR &885E                    :\ Send &9A,num OR &40 to serial
JSR &84B9:BCS &84AA          :\ Wait for input, skip past with A=&FF if no response
LDA #&91:LDX #&01:JSR OSBYTE :\ *FX145,1 - Read from serial input buffer
.L84A9
TYA
.L84AA
INC &66:LDY &66:STA (&64),Y  :\ Get station and store response or &FF
DEY:TYA:LDY #&00             :\ Pass station number to A
CMP (&64),Y:BNE L8496        :\ Loop until done station at first byte in block
RTS


\ Wait a short while to for serial input
\ ======================================
.L84B9
LDA #&C8:STA &67             :\ Loop 200 times
.L84BD
LDA #&80:LDX #&FE:JSR OSBYTE :\ Read ADVAL(-2) - serial input buffer
CLC:TXA:BNE L84CF            :\ If data present, exit with carry clear
DEC &67:BNE L84BD            :\ Loop back to keep waiting
LDA #&FF:SEC                 :\ Set carry - no data present
.L84CF
RTS

.L84D0
RTS

\ Ctrl-Shift-f8 - Software reset
\ ==============================
.L84D1 
LDA &63:ORA #&E0:JMP L885E   :\ Send &9A,station OR &E0


\ Ctrl-Shift-f9 - Hardware reset - send serial break signal
\ =========================================================
.L84D8
LDX #&77:JSR L84EC           :\ Set ACIA control - transmit serial break and reset ACIA
LDY #&00
.L84DF
LDA #&FF:SEC                 :\ Delay for 65536*(8 opcodes)
.L84E2
SBC #&01:JSR L84D0:BCS L84E2 :\ Loop for 256 JSR/RTSs
DEY:BNE L84DF                :\ Loop for 65536 JSR/RTSs
.L84EC
LDA #&9C:LDY #&00:JMP OSBYTE :\ Restore ACIA to previous setting


\ Scan string at (&F2),Y for a decimal number
\ ===========================================
.L84F3
JSR L80A3:JSR L8522          :\ Skip spaces, check if character is a digit
BCC L851A                    :\ Not a digit, return with carry clear
.L84FB
STA &64:INY:JSR L8522        :\ Store current value, get and check next character
BCC L851C                    :\ End of digits, return with carry set
TAX:LDA &64:ASL A:BCS L851A  :\ Multiply Current value by 10, exit with carry clear
ASL A:BCS L851A              :\ if overflow
ADC &64:BCS L851A
ASL A:BCS L851A:STA &64
TXA:ADC &64:BCC L84FB        :\ Add digit to current value, loop if no overflow
.L851A
CLC:RTS                      :\ Clear carry to indicate invalid number
.L851C
LDX &64:CMP #&0D:SEC:RTS     :\ Return value in X, set carry for OK, check any more parameters 

\ Check if character in A is a digit
\ ==================================
.L8522
LDA (&F2),Y
CMP #&3A:BCS L851A           :\ >'9', return with carry clear
CMP #&30:BCC L851A           :\ <'0', return with carry clear
AND #&0F:RTS                 :\ A=digit value, return with carry set

\ Print A in decimal
\ ==================
.L852F
LDY #&FF:SEC
.L8532
INY:SBC #&0A:BCS L8532       :\ Subtract tens
ADC #&0A:PHA:TYA:BNE L853F   :\ Save units, convert 0*10 to space
LDA #&F0
.L853F
JSR L855E:PLA:JMP L855E      :\ Print tens, units

\ Print A in hex - nothing seems to call these
\ ============================================
.L8546
PHA:LDA #&20:JSR OSWRCH:PLA  :\ Print a space
.L854D
PHA:LSR A:LSR A:LSR A:LSR A
JSR L8556:PLA                :\ Print top nybble
.L8556
AND #&0F:CMP #&0A
BCC L855E:ADC #&06
.L855E
CLC:ADC #&30:JMP OSWRCH


\ Link command FA - OSGBPB
\ ========================
.L8564
ROR &62                      :\ Set b7 of &62 - within command
JSR L8844:STA &68            :\ Get GBPB action from serial, store in &68
LDY #&0C                     :\ Collect 12+1 bytes for control block
.L856D
JSR L8844:STA &0780,Y        :\ Get byte, store in control block at &0780
DEY:BPL L856D                :\ Loop for high down to low
JSR L8618                    :\ Set &66/7 to
LDA #&00:STA &0781:STA &64   :\ Replace address with &1900, so corrupting
LDA #&19:STA &0782:STA &65   :\ sideways workspace. Point &64/5 to address
LDA &68:CMP #&03:BCS L85BC   :\ If GBPB 3+, jump to do data read

\ GBPB 1,2 - write data
\ ---------------------
.L858D
JSR L8844:BCS L859C          :\ Get byte, jump when command byte received
LDY #&00:STA (&64),Y         :\ Store byte in buffer
JSR L8611:JMP L858D          :\ Increment address and loop for next byte
.L859C
LDA &68:LDX #&80:LDY #&07    :\ Get GBPB action, point to local control block
JSR OSGBPB:PHP               :\ Call OSGBPB, save carry flag
.L85A6
JSR L8868                    :\ Clear &62.b7, send &9B,&00
JSR L8628:LDY #&0C           :\ Update address in local control block
.L85AE
LDA &0780,Y:JSR L887C        :\ Send byte from control block at &0780
DEY:BPL L85AE                :\ Loop from byte down to low
PLP:ROR A:JMP L887C          :\ Get carry, send it and return

\ GBPB 3+ - read data
\ -------------------
.L85BC
LDX #&80:LDY #&07:JSR OSGBPB :\ Read data to local buffer
PHP:JSR L8868                :\ Clear &62 bit 7, send &9B,&00
LDY #&00:LDA &68             :\ Initialise Y to 0, get GBPB action
CMP #&05:BCS L85E8           :\ Jump if action was read info
.L85CF
LDA &64:CMP &0781:BNE L85DD
LDA &65:CMP &0782:BEQ L85A6  :\ If address=end, jump to send control block and return
.L85DD
LDA (&64),Y:JSR L8611        :\ Get byte, increment address
JSR L887C:JMP L85CF          :\ Send byte, loop for next byte

\ GBPB 5+ - read info
\ -------------------
\ Grrr - Shouldn't impose expectations, should just send whatever is received
.L85E8
BEQ L85F9                    :\ Jump with A=5 - read disk title
CMP #&08:BEQ L85CF           :\ A=8 - read directory entries - jump to return read data
BCS L85A6                    :\ If >8, jump to do nothing and return control block
                             :\ Why? Interface should never impose restrictions on calls
\ GBPB 6,7 - read directory/library name
JSR L8604:JSR L8604          :\ Send length,string twice
JMP L85A6                    :\ Jump to return control block and finish

\ GBPB 5 - Read disk title
\ ------------------------
.L85F9
JSR L8604                    ;\ Send length,string
LDA (&64),Y:JSR L887C        :\ Send boot option after string to serial
JMP L85A6                    :\ Jump to return control block and finish

\ Send length,string from received data
\ -------------------------------------
.L8604
LDA (&64),Y:TAX              :\ Get first byte of data
.L8607
LDA (&64),Y:INY:JSR L887C    :\ Get byte and send it
DEX:BPL L8607:RTS            :\ Loop for count specified in first byte

\ Update transfer address
\ -----------------------
.L8611
INC &64:BNE L8617:INC &65
.L8617
RTS

\ Calculate offset from &1900 to update control block later
\ ---------------------------------------------------------
.L8618
SEC:LDA &0781:SBC #&00:STA &66
LDA &0782:SBC #&19:STA &67:RTS

\ Update control block address with resultant final address
\ ---------------------------------------------------------
.L8628
CLC:LDA &66:ADC &0781:STA &0781
LDA &67:ADC &0782:STA &0782:RTS


\ Link command FB - OSARGS
\ ========================
.L863A
ROR &62                      :\ Set bit7 - with command
JSR L8844:TAY                :\ Get ARGS handle
JSR L8844:PHA:LDX #&00       :\ Get ARGS action
.L8646
JSR L8844:STA &64,X          :\ Get ARGS data, low to high
INX:CPX #&04:BNE L8646
PLA:LDX #&64:JSR OSARGS      :\ Do the OSARGS call
JSR L8868                    :\ Send acknowledge, &9B,&00
JSR L887C:LDX #&03           :\ Send ARGS result
.L865E
LDA &64,X:JSR L887C          :\ Send ARGS data, high to low
DEX:BPL L865E:RTS


\ Link command FC - OSFIND
\ ======================== 
.L8667
JSR L8844:TAY:BEQ L867F      :\ Get FIND action, jump with CLOSE
SEC:ROR &62                  :\ Set bit7 - within command
PHA:SEC:JSR L8824            :\ Save action, fetch string
PLA:JSR OSFIND               :\ Do the OSFIND call
JSR L8868:JMP L887C          :\ Send acknowledge, send result and return

\ OSFIND 0 - CLOSE
\ ----------------
.L867F
JSR L8844:TAY:LDA #&00       :\ Get handle
JMP OSFIND                   :\ Do the CLOSE call and return


\ Link command FD - OSBGET
\ ======================== 
.L8688
ROR &62:JSR L8844:TAY        :\ Set bit 7 - withing command, fetch handle
JSR OSBGET:PHP               :\ Do the OSBGET call, save carry
JSR L8868:JSR L887C          :\ Send acknowledge, then read byte
PLP:ROR A:JMP L887C          :\ Send carry and return


\ Link command FE - OSBPUT
\ ======================== 
.L869D
JSR L8844:TAY:JSR L8844      :\ Get handle to Y, get byte to write
JMP OSBPUT                   :\ Do the OSBPUT call and return


\ OSWORD reply block length
\ ------------------------- 
.L86A7
EQUB &00 :\ Read input line
EQUB &06 :\ Read TIME
EQUB &00 :\ Write TIME
EQUB &06 :\ Read interval
EQUB &00 :\ Write interval
EQUB &05 :\ Read from memory
EQUB &00 :\ Write to memory
EQUB &00 :\ SOUND
EQUB &00 :\ ENVELOPE
EQUB &05 :\ Read POINT
EQUB &09 :\ Read character definition
EQUB &05 :\ Read palette
EQUB &00 :\ Write palette
EQUB &08 :\ Read graphics coordinates


\ Link command 08 - OSWORD
\ ==============================
.L86B5
JSR L8844:PHA              :\ Get OSWORD number from serial link
LDY #&00
.L86BB
JSR L8844:BCS L86C6
STA &0780,Y:INY:BCC L86BB  :\ Read incoming control block
.L86C6
LDX #&80:LDY #&07:PLA      :\ Point to local control block
JSR OSWORD:TAY             :\ Do the OSWORD call
LDA L86A7,Y:STA &64        :\ Index into reply length table
LDA #&00:JSR L8872         :\ Send ESC,&00
LDY #&00:BEQ L86E4         :\ Jump to send reply block
.L86DD
LDA &0780,Y:JSR L887C:INY  :\ Send byte from control block
.L86E4
CPY &64:BNE L86DD          :\ Not at end of block, jump to send byte
LDA #&04:BNE L871D         :\ Send ESC,&04 to terminate reply


\ Link command 07 - OSBYTE A,X,Y
\ ==============================
.L86EC
JSR L8844:PHA              :\ Get byte from serial link
JSR L8844:TAX              :\ Get X byte from serial link
JSR L8844:TAY:PLA          :\ Get Y byte from serial link
CMP #&87:BNE L8705         :\ A<>&87, jump to do OSBYTE
CPX #&8C:BCS L8705         :\ A=&87 with X>=&8C, do OSBYTE
CPX #&87:BCS L8720         :\ A=&87 with X=&87..&8B, special call
.L8705
JSR OSBYTE                 :\ Do OSBYTE call
.L8708
PHP
LDA #&00:JSR L8872         :\ Send ESC,&00 to serial link
TXA:JSR L887C              :\ Send X
TYA:JSR L887C              :\ Send Y
PLP:ROR A:JMP L887C        :\ Send Carry and return
 
.L871B
LDA #&FF
.L871D
JMP L8872

\ OSBYTE &87,&87..&8B - Insert cursor key
\ ---------------------------------------
.L8720
TXA:CLC:ADC #&04:TAY       :\ Y=&8B..&8F - cursor keypress bytes
LDA #&8A:LDX #&00:JSR OSBYTE  :\ Insert cursor keypress into keyboard buffer
JSR L82BC                  :\ Set cursor keys to normal
LDX #&00:BEQ L8708         :\ Jump to return OSBYTE results


\ Link command 05 - FSC 5 - *CAT
\ ==============================
.L8733
JSR L880E:JMP L873C        :\ Fetch CAT string and jump to do OSCLI


\ Link command 03 - FSC 3 - *command
\ ==================================
.L8739
JSR L8824                  :\ Read string to text buffer
.L873C
TXA:PHA:TYA:PHA:JSR L82B3  :\ Save all regs, set cursor keys to normal
JSR L82AB:PLA:TAY:PLA:TAX  :\ Enable serial, select keyboard input
JMP OS_CLI                 :\ Do OSCLI command and return


\ Link command FF - LOAD
\ ======================
.L874D
ROR &62:JSR L8823          :\ Set 'within command', read string to &0700
LDA #&05:JSR OSFILE        :\ See if object present
TAY:BEQ L871B              :\ Object not found, jump to return &9B,&FF
JSR L8868                  :\ Send acknowledge
LDA #&40:LDX #&00:LDY #&07
JSR OSFIND:TAY             :\ Open the specified file for input
LDX #&64:LDA #&02:JSR OSARGS  :\ Read file extent
LDA &00,X:STA &078A        :\ Save extent b0-b15
LDA &01,X:STA &078B
STY &64:LDY #&0F           :\ Save handle
.L877C
LDA &0782,Y:JSR L887C      :\ Send control block back
DEY:BPL L877C              :\ Loop from high down to low
BMI L878A                  :\ Jump ahead to start transfering
.L8787
JSR L887C                  :\ Send byte to serial
.L878A
LDY &64:JSR OSBGET         :\ Get byte from open file
BCC L8787                  :\ If not at EOF, loop back to send it
LDA #&00:JSR OSFIND        :\ Close file
LDA #&04:JMP L8872         :\ Send ESC,&04 to end data, and return


\ Link command 00 - SAVE
\ ======================
.L879B
ROR &61:JSR L8823         :\ Set 'within SAVE', read string to &0700
LDA #&06:JSR OSFILE       :\ Delete any existing file
LDX #&00:LDY #&07:LDA #&80
JSR OSFIND:PHA:LDY #&0F   :\ Open the specified file for output
.L87B1
JSR L8844:STA &0782,Y     :\ Read control block for SAVE
DEY:BPL L87B1             :\ Loop from high to low
LDA &078A:STA &64         :\ Save &64/5 to start address
LDA &078B:STA &65
PLA:TAY                   :\ Get file handle back
.L87C6
JSR L8844:BCS L87D7       :\ Wait for byte, jump if command received
JSR OSBPUT                :\ Send byte to open file
INC &64:BNE L87C6:INC &65 :\ Increment address
JMP L87C6                 :\ Loop until all received
.L87D7
LSR &61:LDA #&00:JSR OSFIND  :\ Clear 'within SAVE', close file
LDA &64:CMP &078E:BNE L87F5  :\ Compare final address with expected end address
LDA &65:CMP &078F:BNE L87F5  :\ If different, jump to error
LDA #&02                  :\ Write file information
.L87EE
LDX #&80:LDY #&07         :\ Point to control block
JMP OSFILE                :\ Call OSFILE to write info or delete file

.L87F5
JSR L8068                 :\ Print inline text
EQUB 13:EQUS "TFS input error":EQUB 13
NOP
LDA #&06:BNE L87EE        :\ Jump back to delete file


\ Store 'CAT' followed by string from serial
\ ==========================================
\ No space, so causes, eg, 'CATFRED' instead of 'CAT FRED'
\ Fixed to store '.' to cause, eg '.FRED'
.L880E
LDA #ASC".":\LDA #ASC"C":STA &0700  :\ Store '.' in text buffer
LDA #ASC" ":\LDA #ASC"A":STA &0701
LDA #ASC" ":\LDA #ASC"T":STA &0702
\SEC:\PHP:\LDY #&02:\BNE L8827      :\ Read string to after 'CAT'
SEC:PHP:LDY #&00:BEQ L8827          :\ Read string to after '.'


\ Read string from serial link
\ ============================
.L8823
CLC
.L8824
PHP:LDY #&FF
.L8827
INY:JSR L8844:STA &0700,Y  :\ Get byte, store in text buffer
CMP #&0D:BNE L8827         :\ Loop until <cr> received
LDX #&00:LDY #&07          :\ Point XY to string
STX &0780:STY &0781        :\ Point OSFILE block to string
PLP:BCS L8843              :\ If carry set, return pointing to string
LDX #&80:LDY #&07          :\ If carry clear, return point to control block
.L8843
RTS

\ Get character from input
\ ------------------------
.L8844
JSR OSRDCH:PHA        :\ Get character from input
LDA &60:CMP #&02      :\ If EURO <2, return raw character
PLA:BCC L885D         :\ Return with carry clear
CMP #&9B:BNE L885C    :\ If not &9B, return with carry clear
JSR OSRDCH            :\ Get escaped character
CMP #&9B:BEQ L885C    :\ If ESC-ESC, return ESC with carry clear
SEC:RTS               :\ Return carry set to indicate escaped character
.L885C
CLC
.L885D
RTS


\ Send &9A escaped byte to serial
\ ===============================
.L885E
PHA:LDA #&9A:JSR L8376     :\ Send &9A prefix
PLA:JMP L8376              :\ Send data byte


\ Clear 'within command' flag and send acknowledge
\ ================================================
.L8868
PHA:LSR &62                :\ Clear 'within command' flag
LDA #&00:JSR L8872         :\ Send &9B,&00
PLA:RTS


\ Send &9B escaped byte to serial
\ ===============================
.L8872
PHA:LDA #&9B:JSR L8376     :\ Send &9B prefix
PLA:JMP L8376              :\ Send data byte


\ Send data byte, escaping if needed
\ ==================================
.L887C
CMP #&9A:BEQ L8894         :\ Jump with &9A
CMP #&9B:BNE L8891         :\ Not &9B, send it
LDA &60:CMP #&02:PHP       :\ Get EURO setting
LDA #&9B:PLP:BCC L8891     :\ If <2, send raw byte
.L888E
JSR L8376                  :\ Send double-escape byte
.L8891
JMP L8376                  :\ Send byte

.L8894
LDA &60:CMP #&03:PHP       :\ Get EURO setting
LDA #&9A:PLP:BNE L8891     :\ If EURO<>3, send raw
BEQ L888E                  :\ Send double-byte sequence


\ Error handler
\ =============
.L88A0
LDX #&FF:TXS:CLI           :\ Clear stack, enable IRQs
JSR OSNEWL                 :\ Print newline
LDY #&00:BEQ L88AE         :\ Jump to print error message
.L88AB
JSR OSASCI                 :\ Print error character
.L88AE
INY:LDA (&FD),Y:BNE L88AB  :\ Get byte, not zero, loop to print it
JSR OSNEWL                 :\ Print another newline
BIT &62:BMI L88C6          :\ If 'within command', jump to...
BIT &61:BPL L88C3          :\ If not 'within SAVE',
.L88BE
JSR L8844:BCC L88BE        :\ Loop to swallow all data from aborted SAVE
.L88C3
JMP L822B                  :\ Return to main system loop

.L88C6
JSR L871B:JMP L822B        :\ Send ESC,&FF to abort, return to main system loop

]NEXT
PRINT "*SAVE <file> ";~mcode%;" ";~O%;" ";~exec%;" ";~load%
