4live/src/4live.a

1733 lines
47 KiB
Plaintext

;
; 4LIVE
; Copyright (c) 2016-8 by 4am && qkumba
; 100% open source, see LICENSE file
;
!cpu 6502
!ct "lcase.ct"
;user-defined
HOTKEY = $80 ; CTRL-@
FINDKEY = $86 ; CTRL-F
HIGHLIGHTKEY = $82 ; CTRL-B
CLEARKEY = $83 ; CTRL-C
PREVPAGEKEY = $92 ; CTRL-R
ADDPAGEKEY = $94 ; CTRL-T
NEXTPAGEKEY = $99 ; CTRL-Y
IMPORTKEY = $9E ; CTRL-^
VERBOSE = $0 ; set to 1 to show memory usage during build
;
!addr PROMPT = $33
!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 SCRN2P2 = $F87B ;SCRN2 +2
!addr PRNTYX = $F940
!addr APLDETECT = $FBB3 ;#06 if IIe or later
!addr KEYIN = $FD1B
!addr RDCHAR = $FD35
!addr CROUT = $FD8E
!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 ; CTRL-H
DNARROW = $8A ; CTRL-J
UPARROW = $8B ; CTRL-K
RETURN = $8D ; CTRL-M
RTARROW = $95 ; CTRL-U
ESC = $9B
SPACE = $A0
DELETE = $FF
WILDCARD = $97
IOBSLOT = 1
IOBDRIVE = 2
NAMELEN = 30
CREATEFILE = 0
OPENEXISTING = 1
SPECIAL = 8
CMDOPEN = 1
CMDCLOSE = 2
CMDREAD = 3
CMDWRITE = 4
READRANGE = 2
POSRDWRRANGE = 4
WIDTH = 40
HEIGHT = 24
LDRBASE = $2DB
INSTALLBUFFER = $20E ; yes, the overlay overwrites the loader...
SWAPBUFFER = $900 ; (LoadSaveEnd - LoadSaveStart) size
; needed by Diversi-DOS, must be page-aligned
; region is preserved across calls
; do not set below $900 if *size* of buffer is not page-aligned
SWAPSIZEALIGNED = 0 ; set to 1 if (LoadSaveEnd - LoadSaveStart) size is page-aligned
; an aligned size reduces code size by a bit but increases memory usage by a lot
*=LDRBASE-4
LdrHeader
!word LDRBASE, LdrEnd - LdrStart
;loader loads to pages 2-3, loads discardable install code also to pages 2-3
;and carries proxy routines including file manager and banked RAM exchange for Diversi-DOS
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
jsr SCRN2P2
sta FileSlot
;open self and read first overlay
jsr OpenMainFile
jsr DOSMLI
!byte LdrReadMLI_e - LdrReadMLI_b
LdrReadMLI_b
!byte CMDREAD ;command
!byte POSRDWRRANGE ;subcommand
!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
JmpInstall
jmp INSTALLBUFFER
;set input name
OpenDataFile
ldy #(NAMELEN - 1)
OpenMainFile ;called with Y=0 for main file (copies one character)
lda FileName
sta OPDST1L
lda FileName + 1
sta OPDST1H
lda LCBANK2
lda LCBANK2
;set input name
lda #SPACE
- 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
ReuseRts
rts
FileName_b
!text "_4LIVE DATA" ;first character must match first character of binary name
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
;to allow fall-through to bank-in RAM
BankInRAM1
bit LCBANK1
bit LCBANK1
rts
PrintAddr
bit ROMIN1
jsr PRNTYX
PrintCR
lda #RETURN
PrintChar
bit ROMIN1
jsr COUT
bne BankInRAM1
;read file content into memory
;with swapping of the memory contents in Diversi-DOS,
;in order to preserve the original memory region
ReadFile
jsr ExchangeBanked1
ReadFileNoXchg
WriteFile
jsr DOSMLI
!byte ReadMLI_e - ReadMLI_b
ReadMLI_b
ReadWriteCmd
!byte CMDREAD ;command
!byte POSRDWRRANGE ;sub-command
!word 0 ;record number
ReadOffset
WriteOffset
!word 0 ;offset, self-modified according to page number
ReadSize
WriteSize
!text "4s" ;number of bytes, self-modified
ReadBuffer
WriteBuffer
!word LoadSaveStart ;buffer, self-modified in Diversi-DOS
ReadMLI_e
;fall through to ExchangeBanked1
;swap banked memory region with main memory region
;call again to restore the main memory region
;sorry, I couldn't find a simpler way...
ExchangeBanked1
rts ;self-modified to CLC in Diversi-DOS environment
;fill banked dynamic region with spaces
ClearVirtualBuffer ;called with carry set
lda #>(LoadSaveStart - <(LoadSaveStart - LastLine))
sta OPDST1H
ldx #>(LastLine - LoadSaveStart)
ldy #<(LoadSaveStart - LastLine)
lda #<(LastLine - LoadSaveStart)
sta OPSRC1L
lda #<LastLine
;can also swap banked dynamic region with spare region also in banked memory
ExchangeBankedSet ;called with A=buffer offset, carry clear
sta OPDST1L
ExchangeBankedPatch
lda #>(SWAPBUFFER - <(LoadSaveStart - LastLine))
sta OPSRC1H
lda #SPACE
- bcs + ;the ClearVirtualBuffer path
lda (OPSRC1L), y
pha
lda (OPDST1L), y
sta (OPSRC1L), y
pla
+ sta (OPDST1L), y
iny
bne -
inc OPSRC1H
inc OPDST1H
dex
bpl -
rts
LdrEnd
!ifdef PASS2 {
!if (LdrEnd > $3D0) {
!error "LDRBASE too high, change to ", LdrStart + $3D0 - LdrEnd
} else {
!if (LdrEnd < $3D0) {
!error "LDRBASE too low, change to ", LdrStart + $3D0 - LdrEnd
}
}
}
;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 Diversi-DOS
!pseudopc INSTALLBUFFER {
InstallStart
;check for Diversi-DOS
pla
eor #$BF
pha
bne + ;regular DOS
sta ReadBuffer
lda #>SWAPBUFFER
sta ReadBuffer + 1
sta InstallReadBuffer + 1
lda #$18 ;CLC
sta ExchangeBanked2
sta ExchangeBanked1
+
;enable reading directly into banked RAM
jsr BankInRAM1
;read second overlay to banked RAM
jsr ReadEditor
pla
bne +
lda #$90
sta PatchDiversi
+
;open source file and read header if available
lda SWAPBUFFER
pha
lda SWAPBUFFER+1
pha
jsr LoadSaveHeader
bcs +
lda SaveCH
sta Pages + 1
sta OrgPages + 1
ldx SaveCV
stx CurPage + 1
stx OrgPage + 1
jsr SeekReadWrite
dec ClearOnFirstKeypress + 1
dec PreventAddPage + 1 ;tri-state flag because Diversi-DOS
inc HeaderExists + 1
+
pla
sta SWAPBUFFER+1
pla
sta SWAPBUFFER
jsr SetTextCoords1
;switch to write mode for future accesses
inc ReadWriteCmd ;lda #CMDWRITE / 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 #$DF
sta CharMap1 + 1
sta CharMap2 + 1
+
;display the welcome message, now that we're finally done
ldy #0
beq ++
- cmp #$E0
bcc +
CharMap1
and #$FF ;self-modified in Apple II+ environment
+ jsr COUT
++ iny
lda _WelcomeMessage-1,y
bne -
lda #<GlobalKeyboardHook
sta KSWL
lda #>GlobalKeyboardHook
sta KSWH
jmp WARMDOS ;DOS will reconnect the vector itself
;read the main code into banked RAM
;uses the memory swapping technique in Diversi-DOS
ReadEditor
jsr ExchangeBanked2
lda #$20 ;JSR
sta SwapFILEMAN
jsr DOSMLI
!byte InstallReadMLI_e - InstallReadMLI_b
InstallReadMLI_b
!byte CMDREAD ;command
!byte POSRDWRRANGE ;subcommand
!word 0 ;record number
!word BankedCopyStart - LdrHeader
;offset
!word BankedCopyEnd - BankedCopyStart
;number of bytes
InstallReadBuffer
!word $D000 ;buffer
InstallReadMLI_e
;fall through to restore buffer
ExchangeBanked2
rts ;self-modified to CLC (for compatibility) in Diversi-DOS environment
lda #>($D000 - <(BankedCopyStart - BankedCopyEnd))
sta OPDST1H
ldx #>(BankedCopyEnd - BankedCopyStart)
ldy #<(BankedCopyStart - BankedCopyEnd)
lda #<(BankedCopyEnd - BankedCopyStart)
sta OPSRC1L
jmp ExchangeBankedSet
_WelcomeMessage
!text $8D, "4LIVE ready. Press Ctrl+", HOTKEY + $40, " to activate.", $8D, 0
;show original DOS cursor first, whatever it was
GlobalKeyboardHook
jsr KEYIN
GetKey
;are we on?
cmp #HOTKEY
;yes -> let's do that
beq +
;memory search?
cmp #FINDKEY
;no -> return to DOS
bne ReuseRts
+ jsr BankInRAM1
jsr RunFromBankedRAM
lda ROMIN1
;lather, rinse, repeat
jsr RDCHAR ;returns non-zero
bne GetKey ;always
InstallEnd
!ifdef PASS2 {
!if (LdrEnd = $3D0) {
!if (InstallEnd > JmpInstall) {
!error "INSTALLBUFFER too large, change to ", InstallStart + JmpInstall - InstallEnd
} else {
!if (InstallEnd < JmpInstall) {
!error "INSTALLBUFFER too low, change to ", InstallStart + JmpInstall - InstallEnd
}
}
}
}
}
;editor code is an overlay that is loaded by install routine above
BankedCopyStart
!pseudopc $D000 {
RunFromBankedRAM
cmp #FINDKEY
beq FindChars
;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 ;Y=0
sty WriteIfDirty2 + 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)
jsr WriteIfDirty1
pla
tax
rts
FindChars
sty CharCount + 1
ldx #0
ldy #0
ConvertChars
cmp #$10
bcs NextChar
asl
asl
asl
asl
sta OPSRC1L
jsr Char2Nib
ora OPSRC1L
sta SECTORBUFFER, y
iny
NextChar
jsr Char2Nib
CharCount
cpx #0 ;self-modified
bcc ConvertChars
jsr PrintCR
dex
beq CancelLine
jsr SearchMemory
CancelLine
ldx #0
lda PROMPT
tay ;in case Y was zero
jmp PrintChar
SearchMemory
lda DOSBASE
sta .endvalue+1
lda #8
sta search+2
sty match_size1+1
sty match_size2+1
; fetch last byte to improve search speed
dey
match_buffer1
lda SECTORBUFFER,y
sta check_byte1+1
sta check_byte2+1
; set low part of initial search position
sty cont_search+1
lda #<cont_search-branch_cont-2
sta branch_cont+1
; search...
cont_search
ldy #$d1 ; modified at runtime
search
lda $d100,y ; modified at runtime
iny
beq check_end
check_byte1
cmp #$d1 ; modified at runtime
bne search
; point to first byte
sty cont_search+1
check_match
tya
match_size1
sbc #$d1 ; modified at runtime
sta match_buffer2+1
lda search+2
sbc #$00
sta match_buffer2+2
ldy #$00
match_all
lda SECTORBUFFER,y
cmp #WILDCARD
beq found_wild
match_buffer2
cmp $d1d1,y ; modified at runtime
branch_cont
bne cont_search
found_wild
iny
match_size2
cpy #$d1 ; modified at runtime
bne match_all
; point to start of match
ldx match_buffer2+1
ldy match_buffer2+2
jsr PrintAddr
tya
bne branch_cont
; cold path
check_end
inc search+2
ldx search+2
.endvalue
cpx #$D1
bne check_byte1
ldx #<all_done_set-branch_cont-2
stx branch_cont+1
check_byte2
cmp #$d1 ; modified at runtime
beq check_match
all_done_set
;fall through
Char2Nib
lda $0200, x
inx
sec
sbc #$B0
cmp #$0A
bcc +
sbc #7
+ rts
ScrollEditBufferIn
ldy #(<(ScrollParmsDown - ScrollParms) - 1)
jsr SetScrollLine
;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
jsr MapCase
sta $0400, y
dey
bpl -
;animation delay
jsr Delay
lda #-WIDTH
ldy #$FF
jsr UpdateVirtualOff ;returns Z/NZ state of SCROLLLINE
bne --
;display current/total page numbering
;99 pages should be enough for everyone
PrintPage
ldx #(HEIGHT - 1)
jsr SetTextCalc
ldy #4
ldx Pages + 1
inx
jsr HexToDec
lda #"/"
sta (OPSRC2L), y
dey
ldx CurPage + 1
inx
HexToDec ;called with X=hex value to print
txa
ldx #0
- cmp #$0A
bcc +
sbc #$0A
inx
bne -
+ jsr AToScr
txa
AToScr ;called with A=dec value to print
ora #"0"
sta (OPSRC2L), y
dey
rts
MapCase
cmp #$E0
bcc +
CharMap2
and #$FF ;self-modified in Apple II+ environment
+ rts
ScrollEditBufferOut
ldy #(<(ScrollParmsUp - ScrollParms) - 1)
jsr SetScrollLine
;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 Z/NZ state of SCROLLLINE
bne --
rts
SetScrollLine ;called with Y=offset of scroll parameter
lda #HEIGHT
sta SCROLLLINE
SetScrollParms ;called with Y=offset of scroll parameter
ldx #3
- lda ScrollParms, y
sta OPSRC1L, x
dey
dex
bpl -
rts
;set up copy addresses for next line
UpdateVirtualOff ;called with A=delta to apply to offsets
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 CopyRow
dex
bpl -
rts
SetTextCalc ;called with X=line number to resolve
lda TextCalcLo, x
sta OPSRC2L
lda TextCalcHi, x
sta OPSRC2H
rts
ScrollTextScreenUp
ldx #(1 - HEIGHT) ;negative indexing to avoid a compare
- 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 CopyRow
inx
bne -
rts
CopyRow
ldy #(WIDTH - 1)
- lda (OPSRC2L), y
sta (OPDST2L), y
dey
bpl -
rts
Delay
lda #1
;straight from ROM
MyWAIT
sec
-- pha
- sbc #1
bne -
pla
sbc #1
bne --
rts
MyKEYIN
ldx MyCH + 1
;fetch the character at the current cursor position
ScreenBuff2
lda $34f3, x ;self-modified
sta ToggleChar + 1
;since we flash the cursor manually by toggling with an inverse space,
;detect if cursor is over an inverse space and use normal space instead
;otherwise cursor won't be visible
CharDel
ldy #DELETE ;self-modified to INVSPACE in Apple II+ environment
bmi +
cmp #INVSPACE
bne +
ldy #SPACE
+ tya
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
- bit KBD
bmi +
dey
bne -
dex
bne -
bcc CharDel
+ bcs ToggleCarry ;restore original character, also on exit
lda KBD
bit STROBE
rts
;print anything that isn't a special key
;wrap around screen position when we hit edges
EditorMode
jsr MyKEYIN
ldx #(KeyTable_e - KeyTable_b)
- ldy DispatchTableLow, x
sty DispatchCommand + 1
ldy DispatchTableHigh, x
sty DispatchCommand + 2
dex
bmi +
cmp KeyTable_b, x
bne -
cpx #(IgnoreClear - KeyTable_b)
bcc DispatchCommand
+ cpx #(IgnoreDirty - KeyTable_b)
;save result in carry
;the first keypress that clears the screen is also a trigger that the first page is dirty
;we require this trigger to allow us to work around a bug in Diversi-DOS
ClearOnFirstKeypress
ldy #1 ;self-modified
beq +
dec ClearOnFirstKeypress + 1
php ;save carry (don't care about the rest)
pha
jsr ClearScreen ;returns Y=FF
sty PreventAddPage + 1 ;trigger first write
pla
plp ;restore carry
+ bcc DispatchCommand
sta WriteIfDirty2 + 1 ;remember if page was modified in the general case
DispatchCommand
jsr $34f3 ;self-modified, currently both bytes
;but could be just one if all routines begin in the same page...
jmp EditorMode
SetTextCoords1
ldx SaveCH
ldy SaveCV
SetTextCoords2 ;called with X/Y=horizontal/vertical coords to set
stx MyCH + 1
sty MyCV + 1
bpl MyBASCALC ;always
_doneEditorMode
pla
pla
DispatchReturn
rts
MyCOUT ;called with A=character to print
MyCH
ldx #"Q" ;self-modified
ScreenBuff4
sta $34f3, x ;self-modified
inx
cpx #WIDTH
bne SetCH
ldx #0
MyCV
ldy #"Q" ;self-modified
iny ;move to next line on horizontal typed wraparound
cpy #(HEIGHT - 1)
bne MyBASCALC
ldy #0 ;move to top left on vertical typed wraparound
beq MyBASCALC ;always
HandleKeyLineLeft
dec MyCH + 1
bpl DispatchReturn
lda #(WIDTH - 1) ;wrap to same line on horizontal arrow wraparound
SetColumn
sta MyCH + 1
bpl DispatchReturn ;always
HandleKeyLineRight
inc MyCH + 1
lda MyCH + 1
eor #WIDTH ;zero on match
bne DispatchReturn
beq SetColumn ;always, wrap to same line on horizontal arrow wraparound
HandleKeyLineUp
ldy MyCV + 1
dey
bpl SetRow
ldy #(HEIGHT - 2)
SetRow
ldx MyCH + 1
MyBASCALC ;called with Y=line number to resolve
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
SetCH
stx MyCH + 1
rts
HandleKeyClearScreen
jsr ClearScreen
dey ;-2
sty MyCV + 1 ;use big store to trigger wraparound
;fall through
HandleKeyReturn
lda #0
sta MyCH + 1 ;move to left of next line on return
;fall through
HandleKeyLineDown
ldy MyCV + 1
iny
cpy #(HEIGHT - 1)
bcc SetRow ;not BNE to allow big store use
ldy #0
beq SetRow ;always
;clear real screen
ClearScreen ; does not clear status line
clc
;copy from virtual buffer to real screen
;;todo: add camera flash and click effect ;-)
HandleKeyImportScreen ;called with carry set
lda #<LoadSaveEnd
sta OPSRC1L
lda #>LoadSaveEnd
sta OPSRC1H
ldx #(HEIGHT - 2)
-- jsr SetTextCalc
ldy #(WIDTH - 1)
lda #SPACE
- 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 ;maintain carry for ImportScreen path
+ dex
bpl --
PageReturn
rts
;write an empty page to disk
;then select that page
HandleKeyAddPage
PreventAddPage
lda #1 ;self-modified
beq + ;page 1 exists already, go directly to add
bpl PageReturn ;page 1 doesn't exist and nothing to write
;Diversi-DOS (not DOS 3.3) has a critical bug when writing to a newly-created file
;if the first write occurs beyond the first sector, then no preceding entries are created in the TS list!
;instead, it leaves the entries blank, causing read errors when attempting to read earlier content
;the disk space is also not reclaimed when deleting the file because the TS list looks empty
;to work around this, we detect when we are writing to the file for the first time,
;and just force the write to occur before the page is added
jsr WriteIfDirty2 ;page 1 is ready to write for the first time
;the problem is that the wrong screen was active at the time that we wrote the initial content
;so we have to perform a second write after the screen is scrolled properly
;performance is bad, but fortunately it's a one-time thing
inc WriteIfDirty2 + 1 ;force it to write again
+ lda Pages + 1
cmp #(99 - 1) ;maximum 99 pages
beq PageReturn
jsr ExchangeVirtualBuffer
sec
jsr ClearVirtualBuffer
jsr OpenDataFile
inc Pages + 1
jsr SetPrevPage ;returns A=Pages
sta CurPage + 1
tax
lda MyCH + 1
pha
lda MyCV + 1
pha
lda #0
sta PreventAddPage + 1
sta MyCH + 1
sta MyCV + 1
jsr SeekReadWrite
pla
sta MyCV + 1
pla
sta MyCH + 1
sec
php ;save carry (don't care about the rest)
bcs +++ ;always
;move to next page if possible
HandleKeyNextPage
jsr SetPrevPage
Pages
lda #0 ;self-modified
beq PageReturn
inc CurPage + 1
CurPage
cmp #0 ;self-modified
bcs ++
;or wrap to first page
lda #0
sec
bcs + ;always
;move to previous page if possible
HandleKeyPrevPage
jsr SetPrevPage ;returns A=Pages, Z/NZ state of Pages
beq PageReturn
clc
dec CurPage + 1
bpl ++
;or wrap to last page
+ sta CurPage + 1
++ php ;save carry (don't care about the rest)
jsr ExchangeVirtualBuffer
+++ jsr OpenDataFile
dec ReadWriteCmd ;lda #CMDREAD / sta ReadWriteCmd
lda ReadBuffer
pha
adc #WIDTH
sta ReadBuffer
lda ReadBuffer + 1
pha
adc #0
sta ReadBuffer + 1
ldx CurPage + 1
jsr SeekReadWrite
pla
sta ReadBuffer + 1
pla
sta ReadBuffer
inc ReadWriteCmd ;lda #CMDWRITE / sta ReadWriteCmd
plp ;restore carry
lda SaveCH + WIDTH
pha
lda SaveCV + WIDTH
pha
bcc + ;the PrevPage path
jsr ScrollEditBufferLeft ;returns Z
beq ++ ;always
+ jsr ScrollEditBufferRight
++ jsr WriteIfDirty2
ldy CurPage + 1
jsr PrintPage
pla
tay
pla
tax
jsr SetTextCoords2
;fall through to restore buffer
;copy virtual buffer to another virtual buffer
;hack existing buffer copy to redirect target,
;since it copies exactly the size that we want
ExchangeVirtualBuffer
lda #>(TEMPBUFFER - <(LoadSaveStart - LastLine))
sta ExchangeBankedPatch + 1
clc ;enable full path even if not Diversi-DOS
jsr ExchangeBanked1 + 1
lda #>(SWAPBUFFER - <(LoadSaveStart - LastLine))
sta ExchangeBankedPatch + 1
rts
SetPrevPage
lda CurPage + 1
sta PrevPage + 1
lda Pages + 1
rts
;turn everything to inverse or normal
HandleKeyHighlight
ldx MyCV + 1
jsr SetTextCalc
ldy #(WIDTH - 1)
- lda (OPSRC2L), y
;lowercase to uppercase
;otherwise it's unreadable when inverted
;however, the conversion is not reversible
cmp #$E0
bcc +
and #$DF
;normal range to control range
+ and #$BF
;invert character and print if inverse
eor #$80
bpl +
;control range to normal range
cmp #SPACE
bcs +
ora #$40
+ sta (OPSRC2L), y
dey
bpl -
rts
ScrollEditBufferLeft ;called with carry set
lda #-WIDTH
sta SCROLLLINE
ldx #(WIDTH - 1)
ldy #0
lda #(<(ScrollParmsLeft - ScrollParms) - 1)
bcs + ;always
ScrollEditBufferRight ;called with carry clear
ldx #0
ldy #(WIDTH - 1)
sty SCROLLLINE
lda #(<(ScrollParmsRight - ScrollParms) - 1)
+ sta ScrollEditPatch1 + 1
stx ScrollEditPatch2 + 1
;copy first/last column on screen to stack (no temporary buffer available)
-- php ;save carry (don't care about the rest)
ldx #(HEIGHT - 2)
- jsr SetTextCalc
lda (OPSRC2L), y
pha
dex
bpl -
bcc + ;the ScrollRight path
;copy each column on screen to previous column
jsr ScrollTextScreenLeft ;returns carry set
bcs ScrollEditPatch1 ;always
;copy each column on screen to next column
+ jsr ScrollTextScreenRight ;maintains carry (clear)
;copy first column of edit buffer to last column on screen
;or
;copy last column of edit buffer to first column on screen
ScrollEditPatch1
ldy #"Q" ;self-modified
jsr SetScrollParms
ldx #(HEIGHT - 2)
- jsr SetTextCalc
ldy SCROLLLINE
lda (OPSRC1L), y
jsr MapCase
ScrollEditPatch2
ldy #"Q" ;self-modified
sta (OPSRC2L), y
lda OPSRC1L
sec
sbc #WIDTH
sta OPSRC1L
bcs +
dec OPSRC1H
+ dex
bpl -
;place saved column in edit buffer
ldy SCROLLLINE
ldx #(HEIGHT - 2)
- pla
sta (OPDST1L), y
lda OPDST1L
clc
adc #WIDTH
sta OPDST1L
bcc +
inc OPDST1H
+ dex
bpl -
plp ;restore carry
bcc + ;the ScrollRight path
ldy #0
inc SCROLLLINE
bne --
rts
+ ldy #(WIDTH - 1)
dec SCROLLLINE
bpl --
rts
ScrollTextScreenLeft
ldy #1
- lda #$88 ;DEY
jsr CopyColumn
iny
cpy #WIDTH
bne -
rts
ScrollTextScreenRight
ldy #(WIDTH - 2)
- lda #$C8 ;INY
jsr CopyColumn
dey
bpl -
rts
CopyColumn ;called with A=opcode to specify direction
sta CopyColumnPatch1
eor #$40
sta CopyColumnPatch2
ldx #(HEIGHT - 2)
- jsr SetTextCalc
lda (OPSRC2L), y
CopyColumnPatch1
nop ;self-modified to DEY/INY
sta (OPSRC2L), y
CopyColumnPatch2
nop ;self-modified to INY/DEY
dex
bpl -
rts
WriteIfDirty1
jsr SetPrevPage
;update header if number of pages changed, or if current page changed
OrgPages
cmp #0 ;self-modified
bne UpdateHeader
lda CurPage + 1
OrgPage
cmp #0 ;self-modified
bne UpdateHeader
;or if the page is dirty and we've never written the header before
lda WriteIfDirty2 + 1
beq OpenReturn
HeaderExists
lda #0 ;self-modified
bne WriteIfDirty2
;update header to specify new page count and current page
UpdateHeader
jsr LoadSaveHeader
;Diversi-DOS has a critical bug when reading from a just-written sector
;IT RETURNS THE ORIGINAL DATA UNLESS YOU CLOSE THE FILE AND RE-OPEN IT
;so that's what we do
jsr CloseFile
WriteIfDirty2
lda #"Q" ;self-modified
beq OpenReturn
lda #0
sta WriteIfDirty2 + 1
jsr OpenDataFile
PrevPage
ldx #0 ;self-modified
SeekReadWrite ;also writes, called with X=page to read/write
lda #<(FirstLine - LoadSaveStart)
ldy #>(FirstLine - LoadSaveStart)
- dex
bmi +
clc
adc #<(LoadSaveEnd - LoadSaveStart)
pha
tya
adc #>(LoadSaveEnd - LoadSaveStart)
tay
pla
bcc - ;always
+ sta WriteOffset ;also ReadOffset
sty WriteOffset + 1 ;also ReadOffset
lda #<(LoadSaveEnd - LoadSaveStart)
sta WriteSize ;also ReadSize
lda #>(LoadSaveEnd - LoadSaveStart)
sta WriteSize + 1 ;also ReadSize
lda MyCH + 1
sta SaveCH
lda MyCV + 1
sta SaveCV
jsr WriteFileBuff ;also used by ReadFile
CloseFile
jsr DOSMLI
!byte CloseMLI_e - CloseMLI_b
CloseMLI_b
!byte CMDCLOSE
CloseMLI_e
OpenReturn
rts
;open the file, load/save the page count and current page
;leaves the file open for additional accesses
LoadSaveHeader
jsr OpenDataFile
bcs OpenReturn
lda #2
sta WriteSize ;also ReadSize
lda #0
sta WriteOffset ;also ReadOffset
sta WriteOffset + 1 ;also ReadOffset
sta WriteSize + 1 ;also ReadSize
lda Pages + 1
sta SaveCH
lda CurPage + 1
sta SaveCV
WriteFileBuff
lda ReadWriteCmd
lsr ;read has bit 0 set, write does not
bcs JumpFileIO
PatchDiversi
bcs InitDiversiWrite ;self-modified to BCC if Diversi-DOS
;write size must be one less than required value because of course it must
AdjustWriteSize
lda WriteSize
bne +
dec WriteSize + 1
+ dec WriteSize
JumpFileIO
jmp WriteFile ;also ReadFile
;Diversi-DOS (not DOS 3.3) has a critical bug when writing within a sector
;the bug is that it does not read the original sector and then replace the data before writing
;instead, it uses the last-read sector to supply the content to replace!
;to work around this, we detect when we are writing within a sector
;that can be either the write offset is non-zero, and/or the low size is non-zero
;in either case, we perform as expected: read the original sector, replace the content, write the sector
InitDiversiWrite
jsr ExchangeSectorBuffer
lda WriteSize
sta WriteSizeLow + 1
lda WriteSize + 1
sta WriteSizeHigh + 1
lda WriteOffset
sta DiversiWriteBuffer + 1
!if (SWAPSIZEALIGNED = 0) {
dec WriteBuffer + 1
}
lda #<LoadSaveStart
sta DiversiReadBuffer + 1
lda #>LoadSaveStart
sta DiversiReadBuffer + 2
;disable swapping temporarily
lda #$60 ;RTS
sta ExchangeBanked1
;read the existing sector
FixDiversiWrite
ldx #0
stx ReadSize
stx ReadOffset
inx
stx ReadSize + 1
dec ReadWriteCmd ;lda #CMDREAD / sta ReadWriteCmd
jsr ReadFileNoXchg
inc ReadWriteCmd ;lda #CMDWRITE / sta ReadWriteCmd
;replace existing content
;slow copy operation, but it works
DiversiReadBuffer
lda $34f3 ;self-modified
inc DiversiReadBuffer + 1
bne DiversiWriteBuffer
inc DiversiReadBuffer + 2
DiversiWriteBuffer
sta (SWAPBUFFER - <(LoadSaveStart - LastLine)) and $FF00
;low byte is self-modified
ldx WriteSizeLow + 1
bne +
dec WriteSizeHigh + 1
+ dex
stx WriteSizeLow + 1
txa
ora WriteSizeHigh + 1
beq +
inc DiversiWriteBuffer + 1
bne DiversiReadBuffer
;reaching this path means that we copy an entire sector on next pass
;but it happens only twice currently, which seems to be acceptable
;and it reduces the complexity of the code
+
;write new sector
jsr AdjustWriteSize
inc WriteOffset + 1
WriteSizeLow
lda #"Q" ;self-modified
WriteSizeHigh
ora #"Q" ;self-modified
bne FixDiversiWrite
;re-enable swapping
!if (SWAPSIZEALIGNED = 0) {
inc WriteBuffer + 1
}
lda #$18 ;CLC
sta ExchangeBanked1
;fall through to restore buffer
ExchangeSectorBuffer
lda #>SECTORBUFFER
sta OPDST1H
lda #0
tax
tay
sta OPSRC1L
jmp ExchangeBankedSet
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
ScrollParms
!word LastLine, LastLine - WIDTH
ScrollParmsDown ;meaningful labels for last character instead of first
!word FirstLine, FirstLine + WIDTH
ScrollParmsUp
!byte <(LoadSaveEnd + WIDTH), >(LoadSaveEnd - <(LoadSaveEnd + WIDTH)), <(FirstLine + WIDTH), >(FirstLine - <(FirstLine + WIDTH))
ScrollParmsLeft
!word LoadSaveEnd, FirstLine
ScrollParmsRight
KeyTable_b
!byte HOTKEY, ESC
IgnoreClear ;everything above this line will not clear title screen
!byte LTARROW, DNARROW, UPARROW, RTARROW, RETURN, ADDPAGEKEY, NEXTPAGEKEY, PREVPAGEKEY
IgnoreDirty ;everything above this line will not trigger a save on their own
;the page keys will save the previous page if it was dirty
!byte IMPORTKEY, CLEARKEY, HIGHLIGHTKEY
KeyTable_e
DispatchTableLow
!byte <MyCOUT ;catch-all entry, the rest follow the KeyTable ordering
!byte <_doneEditorMode, <_doneEditorMode
!byte <HandleKeyLineLeft, <HandleKeyLineDown, <HandleKeyLineUp, <HandleKeyLineRight, <HandleKeyReturn, <HandleKeyAddPage, <HandleKeyNextPage, <HandleKeyPrevPage
!byte <HandleKeyImportScreen, <HandleKeyClearScreen, <HandleKeyHighlight
DispatchTableHigh
!byte >MyCOUT ;catch-all entry, the rest follow the KeyTable ordering
!byte >_doneEditorMode, >_doneEditorMode
!byte >HandleKeyLineLeft, >HandleKeyLineDown, >HandleKeyLineUp, >HandleKeyLineRight, >HandleKeyReturn, >HandleKeyAddPage, >HandleKeyNextPage, >HandleKeyPrevPage
!byte >HandleKeyImportScreen, >HandleKeyClearScreen, >HandleKeyHighlight
LoadSaveStart ;replaced by file content if exists
SaveCH
!byte 0
SaveCV
!byte 0
;our screen contains 23 user-defined lines
;the 24th line is reserved for the status bar
;however, we define an extra line for better vertical scrolling performance
;it works like this to scroll down:
;the last line on the screen is copied into the extra line in this buffer;
;the screen is scrolled down;
;the last line in the virtual buffer is copied into the first line of the screen;
;the last line on the screen (was the second-last line on the original screen) is copied into the last line in this buffer;
;this copy overwrites the line in the virtual buffer that was just copied onto the screen;
;the screen is scrolled down;
;the second-last line in the virtual buffer is copied into the first line of the screen;
;...
;the last line on the screen (was the first line on the original screen) is copied into the second line in this buffer;
;the screen is scrolled down;
;the first line in the virtual buffer is copied into the first line of the screen;
;to scroll up, we reverse the order of the operations
;this allows us to copy whole lines without duplicating the entire screen or performing a content exchange
;...in case you were wondering...
;sigh, but it also introduces a problem with multi-screen support...
;which is that we load directly from file into this buffer but it requires that we load to the second line,
;instead of the first line, which was not the plan originally
;if only we had decided to scroll in the other way...
;is the design flaw in the scrolling or the reading? you decide.
FirstLine ;lines are stored sequentially, not like text page in memory
!fill WIDTH, SPACE
SecondLine
!if (>SecondLine != >LoadSaveStart) {
!error "first two lines of text buffer must not cross a page"
}
!fill WIDTH * 7, SPACE
!text " 4LIVE by 4am && qkumba "
!fill WIDTH, SPACE
!text " Revision 06 / Serial number 190318 "
!fill WIDTH * 2, SPACE
!text " https://github.com/a2-4am/4live "
!fill WIDTH * 9, SPACE
LoadSaveEnd
!fill (WIDTH - 5), $20
!text "4LIVE"
LastLine
!fill WIDTH, SPACE
TEMPBUFFER=(*+255) and not 255
!ifdef PASS2 {
!if (TEMPBUFFER > $DB00) {
!error "banked code is too large, ends at ", *, ", ", *-$DB00, " bytes too many"
}
}
SECTORBUFFER=TEMPBUFFER+$400
!ifdef PASS2 {
!if (VERBOSE = 1) {
!warn "banked code end=", *, ", data end=", SECTORBUFFER+$FF, ", bytes free=", $DB00-*
}
} else {
;define pass-dependent label to reduce output noise
!set PASS2 = 1
}
}
BankedCopyEnd