; ; 4LIVE ; Copyright (c) 2016-7 by 4am && qkumba ; 100% open source, see LICENSE file ; !cpu 6502 !ct "lcase.ct" ;user-defined HOTKEY = $80 ; CTRL-@ 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 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 ; CTRL-H DNARROW = $8A ; CTRL-J UPARROW = $8B ; CTRL-K RETURN = $8D ; CTRL-M RTARROW = $95 ; CTRL-U ESC = $9B SPACE = $A0 DELETE = $FF 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 = $2E9 INSTALLBUFFER = $23B ; yes, the overlay overwrites the loader... ; see also "warning!" below 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 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 ;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 ;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 #(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 jsr LoadSaveHeader bcs + lda SaveCH sta Pages + 1 ldx SaveCV stx CurPage + 1 jsr SeekReadWrite + ;set to true if no data file found (carry is set) rol ClearOnFirstKeypress + 1 rol PreventAddPage + 1 ;tri-state flag because Diversi-DOS 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 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 ;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 ;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 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 #0 ;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 - WIDTH) sta OPSRC1L lda #>(LoadSaveEnd - WIDTH) 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 #0 ;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 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 ;update header to specify new page count and current page +++ 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 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 SaveCV 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 - ;animation delay jsr Delay ;returns A=0 plp ;restore carry bcc + ;the ScrollRight path tay 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 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 + 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 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 04 / Serial number 170123 " !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