; ; 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 = $249 ; yes, the overlay overwrites the loader... SWAPBUFFER = $900 ; (LoadSaveEnd - LoadSaveStart) size ; needed by DiversiDOS, must be page-aligned ; region is preserved across calls *=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 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 #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, ", (LDRBASE + LdrEnd - LdrStart) - $3D0, " bytes too many" } ;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 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 ", (InstallEnd - InstallStart) - $100, " bytes too many" } ;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 03 / Serial number 161117 " !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