; ; 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 #(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 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 #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 + 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 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