4live/src/4live.a
2016-11-16 21:32:28 -08:00

859 lines
21 KiB
Plaintext

;
; 4LIVE
; Copyright (c) 2016 by 4am && qkumba
; 100% open source, see LICENSE file
;
!cpu 6502
!ct "lcase.ct"
;user-defined
HOTKEY = $80 ; CTRL-@
IMPORTKEY = $89 ; CTRL-I
CLEARKEY = $8E ; CTRL-N
SAVEKEY = $93 ; CTRL-S
;
!addr KSWL = $38
!addr KSWH = $39
!addr FCBFOP = $40
!addr GETIOB = $3E3
!addr WARMDOS = $3D0
!addr DOSBASE = $3D2
!addr FILEMAN = $3D6
!addr GETPARM = $3DC
!addr KBD = $C000
!addr STROBE = $C010
!addr LCBANK2 = $C083
!addr ROMIN1 = $C089
!addr LCBANK1 = $C08B
!addr APLDETECT = $FBB3 ;#06 if IIe or later
!addr KEYIN = $FD1B
!addr RDCHAR = $FD35
!addr COUT = $FDED
;private arbitrary addresses, shared with DOS to avoid conflict
!addr OPSRC1L = $40
!addr OPSRC1H = $41
!addr OPDST1L = $42
!addr OPDST1H = $43
!addr OPSRC2L = $44
!addr OPSRC2H = $45
!addr OPDST2L = $46
!addr OPDST2H = $47
!addr SCROLLLINE = $48
;constants
A2E = $06
INVSPACE = $20
LTARROW = $88
DNARROW = $8A
UPARROW = $8B
RETURN = $8D
RTARROW = $95
ESC = $9B
DELETE = $FF
IOBSLOT = 1
IOBDRIVE = 2
NAMELEN = 30
CREATEFILE = 0
OPENEXISTING = 1
SPECIAL = 8
CMDOPEN = 1
CMDCLOSE = 2
CMDREAD = 3
READRANGE = 2
POSREADRANGE = 4
WIDTH = 40
HEIGHT = 24
LDRBASE = $2d9
INSTALLBUFFER = $800 ; max 256 bytes, not preserved
SWAPBUFFER = $900 ; (LoadSaveEnd - LoadSaveStart) size
; needed by DiversiDOS, must be page-aligned
; region is preserved across calls
*=LDRBASE-4
LdrHeader
!word LDRBASE, LdrEnd - LdrStart
;main loader routine loads discardable install code to $800,
;since DOS startup will clobber this page anyway
;also carries proxy routines including file manager and banked RAM exchange for DiversiDOS
LdrStart
;set filename pointer in MLI request packet
lda FCBFOP
sta FileName
lda FCBFOP + 1
sta FileName + 1
;set slot and drive in MLI request packet
jsr GETIOB
pha ;save for later
sty OPSRC1L
sta OPSRC1H
ldy #IOBDRIVE
lda (OPSRC1L), y
sta FileDrive
dey ;ldy #IOBSLOT
lda (OPSRC1L), y
lsr
lsr
lsr
lsr
sta FileSlot
dey ;ldy #00
;open self and read first overlay
jsr OpenMainFile
jsr DOSMLI
!byte LdrReadMLI_e - LdrReadMLI_b
LdrReadMLI_b
!byte CMDREAD
!byte POSREADRANGE
!word 0 ;record number
!word LdrEnd - LdrHeader ;offset
!word InstallEnd - InstallStart
;number of bytes
!word INSTALLBUFFER ;buffer
LdrReadMLI_e
;no need to close read-only files
;DOS simply returns in that case
;written files are the important ones
;but we're not writing anything here
jmp INSTALLBUFFER
;set input name
OpenDataFile
ldy #(NAMELEN - 1)
OpenMainFile
lda FileName
sta OPDST1L
lda FileName + 1
sta OPDST1H
lda LCBANK2
lda LCBANK2
;set input name
lda #$A0
- cpy #(FileName_e - FileName_b)
bcs +
lda FileName_b, y
+ sta (OPDST1L), y
dey
bpl -
OpenFile
jsr DOSMLI
!byte OpenMLI_e - OpenMLI_b
OpenMLI_b
!byte CMDOPEN
!byte 0 ;unused
!word 0 ;variable records
!byte 0 ;any volume
FileDrive
!text "Q" ;self-modified
FileSlot
!text "Q" ;self-modified
!byte SPECIAL ;file type for when we save
FileName
!text "4s" ;self-modified
OpenMLI_e
rts
FileName_b
!text "_4LIVE DATA"
FileName_e
;first byte fetched is number of byte to follow,
;copy parameters to file manager parameter list,
;and then dispatch the request
DOSMLI
pla
tay
pla
tax
iny
sty OPSRC1L
bne +
inx
+ stx OPSRC1H
;get file manager parameter list
jsr GETPARM
sty OPDST1L
sta OPDST1H
ldy #0
lda (OPSRC1L), y
tay
clc
adc OPSRC1L
tax
lda OPSRC1H
adc #0
pha
txa
pha
- lda (OPSRC1L), y
dey
sta (OPDST1L), y
bne -
XREG
ldx #OPENEXISTING
SwapFILEMAN
jmp FILEMAN ;self-modified from JMP to JSR
BankInRAM1
bit LCBANK1
bit LCBANK1
ReuseRts
rts
;support enhanced flashing cursor while waiting for key, if available.
;it works by passing to the routine the character under the cursor.
;the ROM toggles between that character and the Delete character.
;on an Apple II+, the cursor is an inverse space in DOS mode.
;our cursor flashes.
GlobalKeyboardHook
jsr KEYIN
GetKey
;are we on?
cmp #HOTKEY
;no -> return to DOS
bne ReuseRts
jsr BankInRAM1
jsr RunFromBankedRAM
lda ROMIN1
;lather, rinse, repeat
jsr RDCHAR ;returns non-zero
bne GetKey
ReadFile
WriteFile
jsr ExchangeSwapBanked1
jsr DOSMLI
!byte ReadMLI_e - ReadMLI_b
ReadMLI_b
ReadWriteCmd
!byte CMDREAD
!byte READRANGE
!word 0 ;record number
!word 0 ;offset
ReadSize
WriteSize
!word (LoadSaveEnd - LoadSaveStart)
;number of bytes
ReadBuffer
WriteBuffer
!word LoadSaveStart ;buffer, self-modified in DiversiDOS
ReadMLI_e
;fall through to ExchangeBanked1
;sorry, I couldn't find a simpler way...
ExchangeSwapBanked1
rts ;self-modified to LDA in DiversiDOS environment
!byte (>LoadSaveStart) - >((<(LoadSaveEnd - LoadSaveStart)) + 255)
sta OPDST1H
ldx #>(LoadSaveEnd - LoadSaveStart)
ldy #<(LoadSaveStart - LoadSaveEnd)
lda #<(LoadSaveEnd - LoadSaveStart)
sta OPSRC1L
lda #<LoadSaveEnd
ExchangeSwapSet
sta OPDST1L
lda #(>SWAPBUFFER) - >((<(LoadSaveEnd - LoadSaveStart)) + 255)
sta OPSRC1H
- lda (OPSRC1L), y
pha
lda (OPDST1L), y
sta (OPSRC1L), y
pla
sta (OPDST1L), y
iny
bne -
inc OPSRC1H
inc OPDST1H
dex
bpl -
rts
LdrEnd
!if ((LDRBASE + LdrEnd - LdrStart) > $3d0) {
!error "swap code too large, ends at ",LDRBASE + LdrEnd - LdrStart
}
;install routine is an overlay that is loaded by loader routine above
;loads editor code and data file into banked RAM either directly for DOS 3.3
;or via RAM exchange for DiversiDOS
InstallStart
!pseudopc INSTALLBUFFER {
;check for DiversiDOS
pla
eor #$BF
bne + ;regular DOS
sta ReadBuffer
lda #>SWAPBUFFER
sta ReadBuffer + 1
sta InstallReadBuffer + 1
lda #$A9 ;LDA
sta ExchangeSwapBanked1
sta ExchangeSwapBanked2
+
;enable reading directly into banked RAM
jsr BankInRAM1
;read second overlay to banked RAM
jsr ReadEditor
;open source file and read it if available
jsr OpenDataFile
bcs +
jsr ReadFile
+
;set to true if no data file found (carry is set)
rol ClearOnFirstKeypress + 1
ldx SaveCH
stx MyCH + 1
ldy SaveCV
sty MyCV + 1
jsr MyBASCALC
;switch to write mode for future accesses
inc ReadWriteCmd ;lda WRITECMD / sta ReadWriteCmd
dec XREG + 1 ;lda #CREATEFILE / sta XREG + 1
lda ROMIN1
;switch cursor type depending on Apple revision
lda APLDETECT
cmp #A2E
beq +
lda #INVSPACE
sta CharDel + 1
lda #("z" + 1)
sta CharMap1 + 1
sta CharMap2 + 1
+
;display the welcome message, now that we're finally done
ldy #0
beq ++
CharMap1
cmp #0 ;self-modified
bcs +
cmp #"a"
bcc +
and #$DF
+ jsr COUT
++ iny
lda _WelcomeMessage-1,y
bne CharMap1
lda #<GlobalKeyboardHook
sta KSWL
lda #>GlobalKeyboardHook
sta KSWH
jmp WARMDOS
ReadEditor
jsr ExchangeSwapBanked2
lda #$20
sta SwapFILEMAN
jsr DOSMLI
!byte InstallReadMLI_e - InstallReadMLI_b
InstallReadMLI_b
!byte CMDREAD
!byte POSREADRANGE
!word 0 ;record number
!word BankedCopyStart - LdrHeader
;offset
!word BankedCopyEnd - BankedCopyStart
;number of bytes
InstallReadBuffer
!word $D000 ;buffer
InstallReadMLI_e
;fall through to ExchangeBanked2
ExchangeSwapBanked2
rts ;self-modified to LDA in DiversiDOS environment
!byte ((>$D000) - >((<(BankedCopyEnd - BankedCopyStart)) + 255))
sta OPDST1H
ldx #>(BankedCopyEnd - BankedCopyStart)
ldy #<(BankedCopyStart - BankedCopyEnd)
lda #<(BankedCopyEnd - BankedCopyStart)
sta OPSRC1L
jmp ExchangeSwapSet
_WelcomeMessage
!text $8D, "4LIVE ready. Press Ctrl+", HOTKEY + $40, " to activate.", $8D, 0
}
InstallEnd
!if >(InstallEnd - InstallStart) {
!error "swap code too large, ends at ",INSTALLBUFFER + InstallEnd - InstallStart
}
;editor code is an overlay that is loaded by install routine above
BankedCopyStart
!pseudopc $D000 {
RunFromBankedRAM
;preserve X across calls
;it's important for DOS
txa
pha
;first, scroll the edit buffer onto the screen
;(this also swaps the current screen contents out so we can restore it later)
jsr ScrollEditBufferIn ;returns Y=FF
;do the thing
iny
sty IsDirty + 1 ;0=false, non-0=true
;do the thing
jsr EditorMode
;scroll the edit buffer out and the original screen back in
jsr ScrollEditBufferOut
;save to disk (if necessary)
IsDirty
lda #"Q" ;self-modified
beq +
;save to disk
jsr SaveFile
+ pla
tax
rts
ScrollEditBufferIn
ldy #(<(ScrollParms2 - ScrollParms1) - 1)
jsr SetScrollLines
;copy last line on screen to temporary buffer
-- ldy #(WIDTH - 1)
- lda $07D0, y
sta (OPSRC1L), y
dey
bpl -
;copy each line on screen down to next line (leaves top line untouched)
jsr ScrollTextScreenDown
;copy last line of edit buffer to top line on screen
ldy #(WIDTH - 1)
- lda (OPDST1L), y
CharMap2
cmp #0 ;self-modified
bcs +
cmp #"a"
bcc +
and #$DF
+ sta $0400, y
dey
bpl -
;animation delay
jsr Delay
lda #-WIDTH
ldy #$FF
jsr UpdateVirtualOff ;returns zr/nz state of SCROLLLINE
bne --
rts
ScrollEditBufferOut
ldy #((<(ScrollParms2 - ScrollParms1) * 2) - 1)
jsr SetScrollLines
;copy first line on screen to temporary buffer
-- ldy #(WIDTH - 1)
- lda $0400, y
sta (OPSRC1L), y
dey
bpl -
;copy each line on screen up to next line (leaves bottom line untouched)
jsr ScrollTextScreenUp
;copy first line of edit buffer to last line on screen
ldy #(WIDTH - 1)
- lda (OPDST1L), y
sta $07D0, y
dey
bpl -
;animation delay
jsr Delay ;returns A=0
tay
lda #WIDTH
jsr UpdateVirtualOff ;returns zr/nz state of SCROLLLINE
bne --
rts
SetScrollLines
ldx #3
- lda ScrollParms1, y
sta OPSRC1L, x
dey
dex
bpl -
lda #HEIGHT
sta SCROLLLINE
rts
ScrollParms1
!word LastLine, LastLine - WIDTH
ScrollParms2
!word FirstLine, FirstLine + WIDTH
UpdateVirtualOff
;set up copy addresses for next line
ldx #2
- pha
clc
adc OPSRC1L, x
sta OPSRC1L, x
tya
adc OPSRC1H, x
sta OPSRC1H, x
pla
dex
dex
bpl -
dec SCROLLLINE
rts
ScrollTextScreenDown
ldx #(HEIGHT - 2)
- jsr SetTextCalc
lda TextCalcLo+1, x
sta OPDST2L
lda TextCalcHi+1, x
sta OPDST2H
jsr CopyLine
dex
bpl -
rts
SetTextCalc
lda TextCalcLo, x
sta OPSRC2L
lda TextCalcHi, x
sta OPSRC2H
rts
ScrollTextScreenUp
ldx #(1 - HEIGHT)
- lda (TextCalcLo + HEIGHT - $101) +1, x
sta OPSRC2L
lda TextCalcLo + HEIGHT - $101, x
sta OPDST2L
lda (TextCalcHi + HEIGHT - $101) +1, x
sta OPSRC2H
lda TextCalcHi + HEIGHT - $101, x
sta OPDST2H
jsr CopyLine
inx
bne -
rts
CopyLine
ldy #(WIDTH - 1)
- lda (OPSRC2L), y
sta (OPDST2L), y
dey
bpl -
rts
Delay
lda #1
MyWAIT
sec
-- pha
- sbc #1
bne -
pla
sbc #1
bne --
rts
ClearScreen ; does not clear status line
pha
clc
HandleKeyImportScreen ;called with carry set
lda #<(LastLine - WIDTH)
sta OPSRC1L
lda #>(LastLine - WIDTH)
sta OPSRC1H
ldx #(HEIGHT - 2)
-- jsr SetTextCalc
ldy #(WIDTH - 1)
lda #$A0
- bcc + ;ClearScreen path
lda (OPSRC1L), y
+ sta (OPSRC2L), y
dey
bpl -
bcc + ;ClearScreen path
sec
lda OPSRC1L
sbc #WIDTH
sta OPSRC1L
bcs +
dec OPSRC1H
sec
+ dex
bpl --
bcc ClearScreenRet ;ClearScreen path
;print anything that isn't a special key
;wrap around screen position when we hit edges
EditorMode
jsr MyKEYIN
cmp #HOTKEY
beq _doneEditorMode
cmp #ESC
beq _doneEditorMode
ClearOnFirstKeypress
ldx #0 ;self-modified
beq +
dec ClearOnFirstKeypress + 1
jsr ClearScreen
+
cmp #LTARROW
beq HandleKeyLineLeft
cmp #DNARROW
beq HandleKeyLineDown
cmp #UPARROW
beq HandleKeyLineUp
cmp #RTARROW
beq HandleKeyLineRight
cmp #RETURN
beq HandleKeyReturn
ldx #1
stx IsDirty + 1
cmp #IMPORTKEY
beq HandleKeyImportScreen
cmp #CLEARKEY
beq HandleKeyClearScreen
jsr MyCOUT
jmp EditorMode ;always
ClearScreenRet
pla
_doneEditorMode
rts
HandleKeyLineLeft
dec MyCH + 1
bpl EditorMode
lda #(WIDTH - 1)
SetColumn
sta MyCH + 1
bpl EditorMode ;always
HandleKeyLineRight
inc MyCH + 1
lda MyCH + 1
eor #WIDTH ;zero on match
bne EditorMode
beq SetColumn ;always
HandleKeyLineUp
ldy MyCV + 1
dey
bpl SetRow
ldy #(HEIGHT - 2)
SetRow
sty MyCV + 1
ldx MyCH + 1
jsr MyBASCALC ;returns flag=plus
bpl EditorMode ;always
HandleKeyClearScreen
jsr ClearScreen
dey ;-2
sty MyCV + 1 ;use big store to trigger wraparound
;fall through
HandleKeyReturn
lda #0
sta MyCH + 1
;fall through
HandleKeyLineDown
ldy MyCV + 1
iny
cpy #(HEIGHT - 1) ;zero on match
bcc SetRow ;not BNE to allow big store use
ldy #0
beq SetRow ;always
MyKEYIN
ldx MyCH + 1
ScreenBuff2
lda $34f3, x ;self-modified
sta ToggleChar + 1
CharDel
lda #DELETE
sec
!byte $24 ;mask CLC
ToggleCarry
clc
bcs +
ToggleChar
lda #"Q" ;self-modified
+ ldx MyCH + 1
ScreenBuff3
sta $34f3, x ;self-modified
;timing is taken from extended ROM
;code is optimised for size
ldx #$31
ldy #0
- lda KBD
bmi +
dey
bne -
dex
bne -
bcc CharDel
+ bcs ToggleCarry ;restore original character, also on exit
bit STROBE
rts
MyCOUT
MyCH
ldx #"Q" ;self-modified
ScreenBuff4
sta $34f3, x ;self-modified
inx
cpx #WIDTH
bne ++
ldx #0
MyCV
ldy #"Q" ;self-modified
iny
cpy #(HEIGHT - 1)
bne MyBASCALC
ldy #0
MyBASCALC
sty MyCV + 1
lda TextCalcLo, y
sta ScreenBuff2 + 1
sta ScreenBuff3 + 1
sta ScreenBuff4 + 1
lda TextCalcHi, y
sta ScreenBuff2 + 2
sta ScreenBuff3 + 2
sta ScreenBuff4 + 2
++ stx MyCH + 1
rts
SaveFile
lda MyCH + 1
sta SaveCH
lda MyCV + 1
sta SaveCV
CreateWriteFile
jsr OpenDataFile
bcs OpenRet
jsr WriteFile
jsr DOSMLI
!byte CloseMLI_e - CloseMLI_b
CloseMLI_b
!byte CMDCLOSE
CloseMLI_e
OpenRet
rts
TextCalcHi
!byte $04, $04, $05, $05, $06, $06, $07, $07
!byte $04, $04, $05, $05, $06, $06, $07, $07
!byte $04, $04, $05, $05, $06, $06, $07, $07
TextCalcLo
!byte $00, $80, $00, $80, $00, $80, $00, $80
!byte $28, $A8, $28, $A8, $28, $A8, $28, $A8
!byte $50, $D0, $50, $D0, $50, $D0, $50, $D0
LoadSaveStart
SaveCH
!byte 0 ;loaded from file if exists
SaveCV
!byte 0 ;loaded from file if exists
FirstLine ;lines are stored sequentially, not like text page in memory
!fill WIDTH * 8, $A0
!text " 4LIVE by 4am && qkumba "
!fill WIDTH, $A0
!text " Revision 02 / Serial number 161116 "
!fill WIDTH * 2, $A0
!text " https://github.com/a2-4am/4live "
!fill WIDTH * 9, $A0
LoadSaveEnd
;in the current version, the status line is not saved
;however, when we move to multi-screen support,
;both the status line and the spill line must be saved
;for scrolling to work properly
!fill (WIDTH - 5), $20
!text "4LIVE"
LastLine
!fill WIDTH, $A0
}
BankedCopyEnd