;###############################################################################
;#
;# ACME cross-assembler:     acme -f plain -o T64Loader_V2.prg T64Loader_V2.asm
;#
;###############################################################################


					RAMSTART        = $0110	; the RAM part of the loader goes here
					COMPILEFORBANK  = $00	; change here for the loader to live in a different bank
					
					; switches to assemble
					
					ENABLEAUTOSTART = 1		; compile for autostart
					ENABLEDIRECTORY = 0		; compile directory function
					COLORRAMFIX     = 0		; reroute all loads that are only for $d800 to $dbff to colorram
					FLICKER         = 0		; if you want slow border flicker (only every 2K so visible only in long loads)

					LOAD_UNDER_0400 = 1		; Load under $0400 ; FIX: cmp #$04 , bcs ... 
					SECOND_ADDR     = 0		; 0 = load address always from file
					ZP_END_ADDRESS  = 1     ; write end-address after load to zeropage $ae/$af 
					SAVE_STATUS     = 0     ; 1 = save process-status to stack and restore it after load --> Interrupt-Flag
					ALT_CRT_INIT    = 1     ; alternative cartrige init (1 = yes ; 0 = original)
					ALT_CALCS       = 1		; alternative calculations  (1 = yes ; 0 = original --> has a bug)
					
					
					; Zeropage Vectors and Addresses, all Kernal LOAD compatible
					
					ZPLOADVERIFY1   = $0A	; 00 for Load, all others Verify

					ZPLOADVERIFY2   = $93	; 00 for Load, all others Verify, USABLE because we never verify and it is set by ACC at kernal load entry
					CURRENTBANK     = $93	; reusing the Verify byte to store the current bank before and during copy

					ZPENDPROGRAM    = $2D	; End of loaded program plus 1

					ZPDEVICENR      = $49	; Device Number for LOAD and SAVE

					ZPNAMELENGTH    = $b7	; length of filename

					ZPFILENAME      = $bb	; pointer to filename
					ZPDIRENTRY      = $bb	; pointer to our directory entry after we finished the search

					ZPSECONDADDR    = $b9	; Secondary Adress, use $c3 if not Zero
					ZPLOADADRESS2   = $c3	; adress to load to when secondary is 0 ($0801 or set by code)

					ZPDEVICENR2     = $ba	; Device Number for current file
					BUFFERBYTE      = $ba	; We ignore the device number and catch them all, this allows use as $01 buffer

					ZPMOTORSWITCH   = $c0	; 00 Datasette Motor is off, others on

					ZPLOADADRESS    = $ae	; Load Adress; rolling with the load
					ZPDIRSEARCH     = $ae	; BEFORE actual load re-use for directory search

					ZPLOADADRESS_L  = $ae
					ZPLOADADRESS_H  = $af

					; Vectors And Adresses in the $300 space
					OPENFILE		= $031A
					CLOSEFILE		= $031C
					LOADVECTOR		= $0330
					SAVEVECTOR		= $0332

					; Kernal routines
					PRINTSTRING			= $AB1E
					KERNALFILENOTFOUND	= $F704

					; Needed offsets in the T64 Directory

					DIRECTORYSTART	= TAPESTART+$40	; t64 starts at $8x00, first 32 bytes are header
					NUMBEROFFILES	= TAPESTART+$22	; Number of directory entries
					OFFSETLOADADDR	= $02			; Where to load the file unless secondary adress!
					OFFSETENDADDR	= $04			; EndAdress
					OFFSETFILENAME	= $10			; Filename starts here padded with Space
					OFFSETT64		= $08			; Start of ROM in file (PRG 2-byte Header has been chopped off!)

; ----- Cartridge start -----

					*=$8000				; ROM starts at $8000

					; Boot Vectors
					!word COLDSTART
					!word WARMSTART 	; points to Loader Install - can be used as entry point for other software

					
					!byte $c3,$c2,$cd
					!byte $38,$30		; Cartridge Signature

				!if ENABLEDIRECTORY {
TEXTSTRING:			!text "T64CRT"    
					!byte $00
				}

				!if ALT_CRT_INIT {
VICINIT:            !byte $00,$00,$00,$00,$00,$00,$00,$00   ; $d000-$d007
                    !byte $00,$00,$00,$00,$00,$00,$00,$00   ; $d008-$d00f
                    !byte $00,$0b,$00,$00,$00,$00,$c8,$00   ; $d010-$d017
                    !byte $15,$71,$f0,$00,$00,$00,$00,$00   ; $d018-$d01f
                    !byte $00,$00,$01,$02,$03,$f4,$f0,$f1   ; $d020-$d027
                    !byte $f2,$f3,$f4,$f5,$f6,$f7,$fc       ; $d028-$d02e
				
COLDSTART:          sei
                    ldx #$ff
                    txs                 ; reset stackpointer
                    cld                 ; clear D-flag
                    
                    lda #8              ; allow VIC access
                    sta $d016
                    
                    lda #$0            ; refresh RAM (see RAM-datasheet)
					tax
-                   sta $0002, x	   ; $0002-$03ff = 0
                    sta $0100, x
                    sta $0200, x
                    sta $0300, x
					dex
                    bne -
                    
                    ldx #$2f			; VIC init from table
-                   lda VICINIT, x
                    sta $d000, x
                    dex
                    bpl -

                    lda #$37            ; init $01 and then! $00
                    sta $01
                    lda #$2f
                    sta $00
                    
                    jsr $ff84           ; cia init und $00/$01 init. ($fda3)
                   					
                    ldy #$a0
                    jsr $fd8c           ; set vectors $0282-$0284, $0288 
					jsr $e453           ; set vectors $0300-$030b
					jsr $e3bf           ; set vectors $0310-$0312
					jsr $fd15           ; set vectors $0314-$0333
                    jsr $e51b           ; init zp for screen and keyboard ($cc-$f5 without $ce, $d0, $d2, $d4, $d7, $d8) 
                    
                    lda #3              ; current output device = screen
                    sta $9a               
				
				} else {
				
COLDSTART:			; Kernal Reset Routine

					sei
					stx $d016			; Turn on VIC for PAL / NTSC check
					jsr $fda3			; IOINIT - Init CIA chips
					jsr $fd50			; RANTAM - Clear/test system	 RAM
					lda #$a0
					sta $0284			; ignore cartridge ROM for end of detected RAM for BASIC
					jsr $fd15			; RESTOR - Init KERNAL RAM vectors
					jsr $ff5b			; CINT   - Init VIC and screen editor
  
					lda #$69			; make sure that BRK does not clear all vectors by jumping one ahead
					sta $0316
				}

					; We need to patch this code back to normal because Autoload has changed this adress, making subsequent loads fail!

					jsr COPYLOADER
  
				!if ENABLEAUTOSTART {

					; Autostart the first file when Filename starts with !
					lda DIRECTORYSTART + OFFSETFILENAME   
					cmp #$21
					bne BASIC                           ;go to BASIC 
					lda #$00
					sta ZPNAMELENGTH                    ;if name length is zero, loader will load the first file (shortcut)
  
					lda DIRECTORYSTART + OFFSETFILENAME + 4
					jsr PETSCIITOBYTE  
					sta JUMPAWAY + 1
					lda DIRECTORYSTART + OFFSETFILENAME + 3
					jsr PETSCIITOBYTE
					asl
					asl
					asl
					asl
					adc JUMPAWAY + 1
					sta JUMPAWAY + 1
					
					lda DIRECTORYSTART + OFFSETFILENAME + 2
					jsr PETSCIITOBYTE
					sta JUMPAWAY + 2
					lda DIRECTORYSTART + OFFSETFILENAME + 1
					jsr PETSCIITOBYTE
					asl
					asl
					asl
					asl
					adc JUMPAWAY + 2
					sta JUMPAWAY + 2
					
					jsr $f4a2			;Load the file with our routine JMP ($033C)
										;has been patched to JMP into the filename adress

PETSCIITOBYTE:		sec
					sbc #$30
					cmp #$0a
					bcc +
					sbc #$07
+					rts
				}

BASIC:			!if ENABLEDIRECTORY {

					; BASIC RESET  Routine
					jsr $e453			; Init BASIC RAM vectors
					jsr $e3bf			; Main BASIC RAM Init routine
					jsr $e422			; Power-up message / NEW command

					lda #<TEXTSTRING
					ldy #>TEXTSTRING
					jsr PRINTSTRING		; add short message to show loader is activated

					lda #$37
					sta BUFFERBYTE		; make ROM ON the default
 
					jmp JUMPTOBASIC
				}

					;;; jmp $ffd5

WARMSTART:

COPYLOADER:			LDY #ENDOFLOADER-RAMSTART+1
-					LDA LOADERROMADDR-1,Y
					STA RAMSTART-1,Y
					DEY
					BNE -

					; Set Loadvector to our routine and Savevector to CLC and Return (do nothing)
					lda #<LOADENTRY
					sta LOADVECTOR
					lda #>LOADENTRY
					sta LOADVECTOR + 1
					
					lda #<COPYENDED
					sta SAVEVECTOR
					lda #>COPYENDED
					sta SAVEVECTOR + 1
					rts


; ----- ROM-Loader ----

T64CODE:			; We are coming here from the Low memory loader (see below). To keep loader short, code that does not need to be modified stays in ROM.


					lda ZPNAMELENGTH	; when name has length zero, replace with first file
					bne +
					inc ZPNAMELENGTH
					jmp REPLACEFIRSTLETTER
+					lda #<STAYHERE		; need to fix the JMP back to do nothing if we changed it for Autoload!
					sta JUMPAWAY + 1	; Autoload works with Filename Zero, so if there is a file name we patch back
					lda #>STAYHERE		; Does work in edge case too because if we manually load, we never patched
					sta JUMPAWAY + 2

					lda FILENAMECOPY

				!if ENABLEDIRECTORY {  
					cmp #"$"			; first character $ then go to dirprint
					beq DIRPRINT
				}
  
					cmp #"*"      		; replace * in Pos 0 with first filename in Pos 0 to fool Directory Search
					bne NOTFIRST
  
REPLACEFIRSTLETTER:	lda DIRECTORYSTART + OFFSETFILENAME
					sta FILENAMECOPY

NOTFIRST:			jmp NOTDIR

				!if ENABLEDIRECTORY {
 
DIRPRINT:			; Print first 7 directory Entries direct to screen
					ldy #$00
LOOPDIR:			ldx #$00
-
					lda DIRECTORYSTART + OFFSETFILENAME, Y
					sta DIRECTORYSTRING, X
					inx
					iny
					tya
					cmp #$e0
					bcs DIRDONE
					pha
					txa 
					cmp #$0f
					bne -
					lda #<DIRECTORYSTRING
					ldy #>DIRECTORYSTRING
					jsr PRINTSTRING
					pla
					clc
					adc #$11
					tay
					bne LOOPDIR
DIRDONE:			jmp JUMPTOBASIC
				}  

NOTDIR:				ldx NUMBEROFFILES ; counting down with X for "file not found"
					lda #>DIRECTORYSTART
					sta ZPDIRSEARCH + 1
					lda #<DIRECTORYSTART
					clc
					adc #$10
					sta ZPDIRSEARCH
					bcc +
					inc ZPDIRSEARCH + 1
+ 
NAMECHECK:			; check filenames starting at last character as T64 pads with spaces
					ldy ZPNAMELENGTH
					dey
-					lda (ZPDIRSEARCH), Y
					cmp FILENAMECOPY, Y
					bne NEXTFILE
					dey
					bpl -
					jmp FOUNDFILE
  
NEXTFILE:			dex
					beq FILENOTFOUND
					lda ZPDIRSEARCH
					clc
					adc #$20
					sta ZPDIRSEARCH
					bcc +
					inc ZPDIRSEARCH + 1
+					jmp NAMECHECK

FILENOTFOUND: 		; need to jump out to file not found!
					jmp FILENOTFOUNDEXIT
  
FOUNDFILE:			; We now reuse the pointer to the file name as the pointer to the directory entry and can store the load adress
					lda ZPDIRSEARCH
					sec 
					sbc #$10
					sta ZPDIRENTRY
					lda ZPDIRSEARCH + 1
					sbc #$00
					sta ZPDIRENTRY + 1

					; Get LOADADRESS in RAM (TO:) and store directly in code
					ldy #OFFSETLOADADDR
					lda (ZPDIRENTRY), Y
					sta TORAM + 1
					iny
					lda (ZPDIRENTRY), Y
					sta TORAM + 2

				!if LOAD_UNDER_0400 {  					
				} else {
					cmp #$04			; do not Load below $0400
					bcs +
					jmp ERROR
+
				}	
					; Store End Adresss in the code for the byte copy
					ldy #OFFSETENDADDR
					lda (ZPDIRENTRY), Y
					sta ENDCOUNTLO + 1
					iny
					lda (ZPDIRENTRY), Y
					sta ENDCOUNTHI + 1 


				!if SECOND_ADDR 	{

					; if secondary adress is set, we need to change start and end adress
					; we calculate the new end adress first

					lda ZPSECONDADDR
					beq GETSTART   
					
					sec
					lda ENDCOUNTLO + 1
					sbc TORAM + 1
					tax
					lda ENDCOUNTHI + 1
					sbc TORAM + 2
					tay
					txa
					clc
					adc ZPLOADADRESS2
					sta ENDCOUNTLO + 1
					tya
					adc ZPLOADADRESS2 + 1
					sta ENDCOUNTHI + 1
				
  
					; now we replace the load adress with the one given in by kernal/call
					lda ZPLOADADRESS2
					sta TORAM + 1
					lda ZPLOADADRESS2 + 1
					sta TORAM + 2  
				}

GETSTART:			; Get STARTADRESS in ROM (FROM:) by converting 3-Byte position into bank and position
					; The startadress inside the bank is written directly into the copy routine which is self-modifying

				!if ALT_CALCS 	{

					ldy #OFFSETT64				; write low byte unchanged
					lda (ZPDIRENTRY), Y
					sta FROMCRT + 1	
					
					iny							; get mid byte
					lda (ZPDIRENTRY), Y
					clc
					adc #>(TAPESTART - $8000)	; add loader size
					pha							; A --> stack
					
+					ror                 		; division / 32 with carry
					lsr
					lsr
					lsr
					lsr
					
					clc
					sta CURRENTBANK				; write bank
					
					pla							; A <-- Stack 
					
					and #$1f
					ora #$80
					sta FROMCRT + 2		; write offset in ROM

					iny							; get high byte (0...15)
					lda (ZPDIRENTRY), Y
					asl							; multiplication with 8
					asl
					asl
					
					clc							 
					adc CURRENTBANK				; add banks 
					sta CURRENTBANK
					
				} ELSE {

					ldy #OFFSETT64  
					lda (ZPDIRENTRY), Y
					sta FROMCRT + 1		; low byte of adress stays untouched
					iny
					iny
					lda (ZPDIRENTRY), Y	; High byte as start value for correct bank calculation
					clc
					rol	
					rol					; Multiply with 8 (8 Banks per 64K)
					rol
					adc #COMPILEFORBANK	; Add the bank the loader is compiled for 
					sta CURRENTBANK		; and store it.
					dey
					lda (ZPDIRENTRY), Y	; Get the mid byte
					clc
					adc #>(TAPESTART - $8000)	; add $0x00 as directory starts at $8x00 in the first bank
					bcc +
					inc CURRENTBANK		; if we rolled over (carry set), we need to increment the bank
+
					lsr                 ; divide the mid byte by 32 (as we have 8 banks)
					lsr
					lsr
					lsr
					lsr
					clc
					adc CURRENTBANK		; add this to the current high byte to get to the correct bank
					sta CURRENTBANK
					lda (ZPDIRENTRY), Y	; lets get the mid byte again and put into $8000 to $9FFF range
					clc
					
					adc #>(TAPESTART - $8000)	; add $0x00 as directory starts at $8x00 in the first bank - bank switch happened above
					and #$9f
					ora #$80
					sta FROMCRT + 2		; conversion is done
				}

				!if COLORRAMFIX = 1 {	; if the load start and end adress are only in color ram space, patch the copy routine to NOT write to RAM. It repairs itself at the end.
					lda TORAM + 2		; can be turned off during compile to save a few bytes if you KNOW the game does not load straight into color ram.
					cmp #$D8
					bcc NOCOLORRAM	
					cmp #$DC
					bcs NOCOLORRAM
					lda ENDCOUNTHI + 1
					cmp #$DC
					bcs NOCOLORRAM 
					lda #$37
					sta CRFIX1 + 1
					sta CRFIX2 + 1
NOCOLORRAM:		}
  
					;All done in ROM now switch into RAM for the actual copy

					jmp MOVESTART

					; File not found and load below $0400 go here
ERROR:				sec
					jmp GOODBYEERROR


; ----- RAM-Loader -----

					; Start of the CRT2RAM routine, usually at $033c but fully movable to RAMSTART!

LOADERROMADDR:		!PSEUDOPC RAMSTART {
LOADENTRY:

				!if SAVE_STATUS {
					php
				}
				
					SEI
					; Save Memory Status
					LDA $01
					STA BUFFERBYTE

					; Need to copy filename into bufferbytes at the end (doubling up for directory print)
					; because the filename might be under the ROM

					LDY #$00
-					CPY ZPNAMELENGTH
					BEQ +  
					LDA (ZPFILENAME), Y
					STA FILENAMECOPY, Y
					INY
					BNE -
+  					LDA #$37
					STA $01
					LDA #COMPILEFORBANK ; switch to the bank the loader is in
					STA $DE00
					JMP T64CODE			; JMP to all code that does not need to live down here, then we return to the next address

; ----- @CRT2RAM@ -----

MOVESTART: 			; Switch to the first bank our file is in 

					ldx CURRENTBANK
					stx $de00
CRFIX1:				lda #$33
					sta $01

COPYLOOP:			 
FROMCRT:			lda $8000   		; selfmodify
TORAM:				sta $0801   		; selfmodify
  
					inc FROMCRT + 1		; increment from adress, when reaching $A000, bankswitch and back to $8000
					bne +
					inc FROMCRT + 2
					lda FROMCRT + 2
					cmp #$a0
					bne +
					lda #$37
					sta $01
					inx
					stx $DE00

				!if FLICKER=1 {
					inc $D020
				}   
  
CRFIX2:				lda #$33
					sta $01
					lda #$80
					sta FROMCRT + 2
+
					inc TORAM + 1		; increment to adress
					bne +
					inc TORAM + 2
+					lda TORAM + 1   	; compare current adress with T64 end adress (which is +1 so we do not want to copy this byte!), if equal we are done
ENDCOUNTLO      	;selfmodified code, lo byte end adress goes here
					cmp #$00
					bne COPYLOOP
					lda TORAM + 2
ENDCOUNTHI:			;selfmodified code, hi byte end adress goes here
					cmp #$00
					bne COPYLOOP 

COPYENDED:		!if COLORRAMFIX = 1 { 	; This repairs our code always because we MIGHT have patched here to load into colorram
					lda #$33
					sta CRFIX1 + 1
					sta CRFIX2 + 1
				}

					lda #<STAYHERE  	; need to fix this if we changed for Autoload!
					sta STAYHERE + 1
					lda #>STAYHERE
					sta STAYHERE + 2
  
  
				!if ZP_END_ADDRESS = 1 {
					lda TORAM + 1		; Endadresse schreiben
					sta ZPLOADADRESS_L
					lda TORAM + 2
					sta ZPLOADADRESS_H
				}
   
					clc       			; important, carry flag means error

GOODBYEERROR:
CARTOFF:			lda #$37 
					sta $01
					lda #$80			; switch off Magic Cart
					sta $DE00    
					lda BUFFERBYTE
					sta $01

				!if SAVE_STATUS {
					plp
				} else {
					cli
				}
				
JUMPAWAY:			jmp $1000 ;STAYHERE		; will be modified to the real JMP adress for autoload
STAYHERE:			rts

JUMPTOBASIC:		jsr CARTOFF
					jmp $A7AE
  
FILENOTFOUNDEXIT:	jsr CARTOFF
					jmp KERNALFILENOTFOUND
  
FILENAMECOPY: 	!if ENABLEDIRECTORY {
DIRECTORYSTRING:
				}
					!text "                "	;      1234567890123456
					!byte $0D,$00  
ENDOFLOADER:
					}

					!align 255, 0, 0		; auf 256 Byte auffüllen
TAPESTART: