basic loading and saving

This commit is contained in:
Joppe Blondel
2026-10-10 12:34:48 +02:00
parent 6374a070ea
commit 7d7ab196c3
2 changed files with 340 additions and 169 deletions
+331 -165
View File
@@ -4616,6 +4616,14 @@ FULLSETTOP:
FULLMSG:
.BYTE "[FULL MEMORY]",CR,LF,0,0
; Every tape block starts with a type byte followed by 128 data bytes.
; Header data contains a little-endian block count, byte length and filename.
TAPE_TYPE_RAW .equ 0
TAPE_TYPE_BINARY .equ 1
TAPE_TYPE_BAS_PROG .equ 2
TAPE_TYPE_BAS_ARR .equ 3
TAPE_DATA_SIZE .equ 128
CLOAD: ld a,(hl) ; get byte after "CLOAD"
cp ZTIMES ; "*" token? ("CLOAD*")
jp z,FCERR ; yes - array load (ARRLD1)
@@ -4624,8 +4632,54 @@ CLOAD: ld a,(hl) ; get byte after "CLOAD"
cp '#' ; 'cload#'?
jp z,CLOADB ; yes - load binary
; just cload - load basic
jp FCERR
or a ; no arguments allowed
jp nz,FCERR
ld hl,CLSTARTMSG ; ask the user to start the tape
call PRS ; print the prompt
ld b,TAPE_TYPE_BAS_PROG ; expect a BASIC program header
call CLREADHEADER ; bc = blocks, de = length, hl = filename
call CLPRINTHEADER ; print block count and filename
ld b,d ; move exact length to bc
ld c,e ; bc = exact program length
ld a,b ; check the high byte of the length
or a ; 256 bytes or more is always valid here
jr nz,1$ ; skip the minimum-length check
ld a,c ; check a short program's low byte
cp 2 ; an empty program still has its end marker
jp c,FCERR ; less than two bytes is not a program
1$: ld hl,(BASTXT) ; get the program load address
add hl,bc ; calculate the exact program end
jp c,OMERR ; reject a wrapped end address
ld de,40 ; keep BASIC's minimum working space free
add hl,de ; include the working-space margin
jp c,OMERR ; reject a wrapped limit address
ld de,(STRSPC) ; get the bottom of string/stack space
call CPDEHL ; compare required memory with available RAM
jp nc,OMERR ; program would enter BASIC's working area
ld hl,(BASTXT) ; calculate the exact end once more
add hl,bc ; hl = address following the program
push hl ; preserve exact program end
ld de,(BASTXT) ; load the program at the BASIC text area
call CLLOADBLOCKS ; copy only the exact number of bytes
pop hl ; restore the exact program end
dec hl ; point to the last program byte
ld a,(hl) ; get the last end-marker byte
or a ; it must be zero
jp nz,FCERR ; reject an invalid program terminator
dec hl ; point to the first end-marker byte
ld a,(hl) ; get the first end-marker byte
or a ; it must also be zero
jp nz,FCERR ; reject an invalid program terminator
push hl
ld hl,CLCLRF ; print newline
call PRS
ld hl,OKMSG
call PRS ; print ok message
pop hl
inc hl ; restore the address after the program
inc hl
ld (PROGND),hl ; install the new program end
jp SETPTR ; clear variables, relink and return to prompt
CLOADB: inc hl ; skip #
call GETNUM ; evaluate numeric expression
call DEINT ; convert FPREG to de
@@ -4634,71 +4688,124 @@ CLOADB: inc hl ; skip #
push hl
push bc
push de
ld hl,CLSTARTMSG
call PRS
ld b,TAPE_TYPE_BINARY
call CLREADHEADER ; bc = blocks, de = length, hl = filename
call CLPRINTHEADER
ld b,d
ld c,e ; bc = exact file length
pop de ; destination address
call CLLOADBLOCKS
pop bc
pop hl
ret
; Load block
; -------------------------------------------------------
; Read and decode a tape header
; needs b (type of header to expect)
; returns bc (number of following blocks), de (byte length), hl (filename)
; errors do not return
; uses a
; -------------------------------------------------------
CLREADHEADER:
push bc
call CLREADBLOCK
; hl contains block, check block type
ld a,(hl)
cp 1 ; first block needs to be a header
jp nz,CLNOHDR ; First block is not a header
; first block is a header, get nr of blocks
pop bc
cp b
jp nz,CLNOHDR
inc hl
ld de,(hl)
; print number of blocks to load
ld c,(hl) ; block count is little-endian
inc hl
ld b,(hl)
inc hl
ld e,(hl) ; byte length is little-endian
inc hl
ld d,(hl)
inc hl ; point to filename
ret
; -------------------------------------------------------
; Print the block count and filename from a tape header
; needs bc (number of blocks), hl (filename)
; preserves bc,de
; uses a,h,l
; -------------------------------------------------------
CLPRINTHEADER:
push bc
push de
push hl
ex de,hl
ld h,b
ld l,c
call PRNTHL
ld hl,CLBLOCKSMSG
call PRS
pop hl
inc hl
inc hl ; get filename
; TODO filename always null terminated?
call PRS ; print filename
; de contains the nr of blocks to load
; TODO check de>0
pop de
1$: push de ; save repeat counter
call CLREADBLOCK ; hl = new data
; TODO check block type
inc hl ; goto start of data
pop de ; get repeat counter
push hl ; save stored data
exx
pop hl ; get stored data back
pop de ; get destination
ld bc,128 ; block size
ldir ; copy data to memory
push de ; store destination address
exx
dec de
ld a,e
cp 0
jr nz,1$ ; repeat if not done yet
ld a,d
cp 0
jr nz,1$ ; repeat if not done yet
call PRS
pop de
pop bc
pop hl
pop bc
ret
; -------------------------------------------------------
; Load tape data into memory, including a partial final block
; needs bc (file length in bytes), de (destination address)
; returns de (first address after loaded data)
; returns bc (zero)
; uses a,h,l
; -------------------------------------------------------
CLLOADBLOCKS:
ld a,b
or c
ret z
1$: push bc
push de
call CLREADBLOCK
inc hl ; skip block type
pop de
pop bc ; remaining byte count
ld a,b
or a
jr nz,2$ ; at least 256 bytes remain
ld a,c
cp TAPE_DATA_SIZE
jr nc,2$
ldir ; copy the partial final block
ret
2$: push bc
ld bc,TAPE_DATA_SIZE
ldir
pop bc
ld a,c ; subtract one full block
sub TAPE_DATA_SIZE
ld c,a
ld a,b
sbc a,0
ld b,a
jr 1$
; -------------------------------------------------------
; Read one of two redundant copies of a tape block
; returns hl (block buffer)
; errors do not return
; uses a,b,c,d,e,h,l
; -------------------------------------------------------
CLREADBLOCK:
call BIOS_CAS_READ_BLOCK
jr c,3$
push hl ; preserve the first copy
ld a,'.' ; block read correctly
call OUTC
jr 1$
ld a,'o' ; skip redundant copy
call OUTC
call BIOS_CAS_READ_BLOCK_DRY
jr 2$
3$: ld a,'x' ; block failed, try redundant block
call OUTC
call BIOS_CAS_WAIT_SILENCE
call BIOS_CAS_READ_BLOCK
push hl
jr c,4$
push hl ; preserve the redundant copy
ld a,'.' ; redundant block read correctly
call OUTC
jr 2$
@@ -4710,12 +4817,7 @@ CLREADBLOCK:
call BIOS_CAS_WAIT_SILENCE
pop bc
pop af
jr CLERR
1$: push hl ; save hl
ld a,'o' ; skip redundant block
call OUTC
call BIOS_CAS_READ_BLOCK_DRY
; Ignore errors on this block
jp CLERR
2$: ; but make sure it is silent again
call BIOS_CAS_WAIT_SILENCE
pop hl
@@ -4731,30 +4833,24 @@ CLERR: cp 1
cp 4
jr z,CLECHS
jp FCERR ; Unknown error
CLECAL: ld h,0
ld l,b ; get specific error code
call PRNTHL ; print specific error code
ld hl, CLECALMSG
CLECAL: ld hl,CLECALMSG
jr CLDETAIL
CLESYN: ld hl,CLESYNMSG
jr CLDETAIL
CLERED: ld hl,CLEREDMSG
CLDETAIL:
push hl
ld h,0
ld l,b ; print specific error code
call PRNTHL
pop hl
jr CLEPRINT
CLESYN: ld h,0
ld l,b ; get specific error code
call PRNTHL ; print specific error code
ld hl, CLESYNMSG
jr CLEPRINT
CLERED: ld h,0
ld l,b ; get specific error code
call PRNTHL ; print specific error code
ld hl, CLEREDMSG
jr CLEPRINT
CLECHS: ld hl, CLECHSMSG
CLECHS: ld hl,CLECHSMSG
jr CLEPRINT
CLNOHDR:
ld hl,CLNOHDRMSG
CLEPRINT:
call PRS
pop de
pop bc
pop hl
jp FCERR
CLSTARTMSG: .db "Play tape...",0ah,0dh,00h
@@ -4768,123 +4864,193 @@ CLCLRF: .db 0ah,0dh,00h
CSAVE: ld a,(hl) ; get byte after "CSAVE"
cp ZTIMES ; "*" token? ("CSAVE*")
jp z,FCERR ; yes - array load (ARRLD1)
jp z,FCERR ; yes - array save
cp ZPRINT ; "?" ("PRINT token") verify
jp z,FCERR ; yes - flag "verify"
cp '#' ; 'csave#'?
jp z,CSAVEB ; yes - save binary
; just cload - load basic
jp FCERR
CSAVEB: inc hl ; skip #
push bc ; save bc
push de ; save de
CALL GETNUM ; Get start address
CALL DEINT ; Convert it to 16 bits
PUSH DE ; Save start address
CALL CHKSYN ; Make sure "," follows
.BYTE ','
CALL GETNUM ; Get length
CALL DEINT ; Convert it to 16 bits
PUSH DE ; Save length
CALL CHKSYN ; Make sure "," follows
.BYTE ','
; hl points to the first character of the string expression
call EVAL
push hl ; save command buffer pos
; just csave - save basic program
CSAVE1: call EVAL ; evaluate filename expression
push bc
push de ; preserve bc and de
push hl ; preserve command buffer position
exx
call GETSTR
call LOADFP ; bc' = addr, e' = length
call LOADFP ; bc' = filename, e'=length
exx
pop hl ; get back command buffer pos
pop bc ; get length
pop de ; get start address
push hl
; get the program length
ld de,(BASTXT) ; program source address
ld hl,(PROGND) ; address after prog mem
or a ; clear carry
sbc hl,de ; hl = prog length
ld b,h
ld c,l ; prog length in bc
ld a,TAPE_TYPE_BAS_PROG
jp CSOUT ; Write the file to tape
CSAVEB: inc hl ; skip #
push bc ; save BASIC registers
push de
call BIOS_CAS_GET_BUFFER
; hl=buffer, bc=lenght, de=start address
; Write header block
ld a,1 ; block type header
ld (hl),a
inc hl
call GETNUM ; get start address
call DEINT ; convert FPREG to de
push de ; save start address
call CHKSYN ; require a comma
.BYTE ','
call GETNUM ; get length
call DEINT ; convert FPREG to de
push de ; save length
call CHKSYN ; require a comma
.BYTE ','
call EVAL ; evaluate filename expression
push hl ; preserve command buffer position
exx
call GETSTR
call LOADFP ; bc' = filename, e' = length
exx
pop hl
pop bc ; length in bytes
pop de ; start address
push hl
ld a,TAPE_TYPE_BINARY
CSOUT: call CSWRITEHEADER ; bc = block count, preserves de
call CSWRITEBLOCKS
ld hl,CLCLRF
call PRS
pop hl
pop de
pop bc
ret
; -------------------------------------------------------
; Build and write a header and its redundant copy
; needs bc (file length in bytes)
; needs bc' (filename address), e' (filename length)
; returns bc (number of data blocks)
; preserves de
; uses a,h,l and the alternate register set
; -------------------------------------------------------
CSWRITEHEADER:
push de
push af ; preserve header type
push bc ; preserve exact byte length
call BYTESTOBLOCKS
push bc
ld a,c ; nr of blocks low byte
ld (hl),a
inc hl
ld a,b ; nr of blocks high byte
ld (hl),a
inc hl
push hl
exx
1$: push bc
pop hl ; get str addr to hl
ld a,(hl)
inc hl
push hl
pop bc ; get str addr+1 back into bc
pop hl ; get hl to hl'
ld (hl),a ; save character to buffer
inc hl
push hl
dec e ; count down str len
ld a,e
cp 0
jr nz,1$ ; repeat for the whole string
exx
pop hl ; get back buffer addr
ld a,0 ; NULL terminate string
ld (hl),a
inc hl
; Header block complete (rest of block is accepted garbage)
call BIOS_CAS_WRITE_BLOCK
ld a,'.'
call OUTC
call BIOS_CAS_WRITE_BLOCK
ld a,'.'
call OUTC
ld a,250
call BIOS_DELAY_MS
ld a,250
call BIOS_DELAY_MS
ld a,250
call BIOS_DELAY_MS ; add a 750ms delay
pop bc ; get back block count
pop de ; get back start address
pop hl ; exact byte length for the header
pop af
call CSMAKEHEADER
push bc ; preserve block count over delay
call CSWRITEPAIR
call CSHEADERGAP
pop bc
pop de
ret
; -------------------------------------------------------
; Write consecutive raw data blocks
; needs bc (number of blocks), de (source address)
; returns bc (zero), de (first address after written data)
; uses a,h,l
; -------------------------------------------------------
CSWRITEBLOCKS:
ld a,b
or c
jr z,3$ ; no data blocks for a zero length
2$:
; load data into buffer
push bc
call BIOS_CAS_GET_BUFFER
ld bc,128 ; block size
ld a,0 ; raw type
ld (hl),a
inc hl
ex de,hl ; hl=src,de=dest
ldir ; copy the bytes
ex de,hl ; de back to being the src addr
call BIOS_CAS_WRITE_BLOCK
ld a,'.'
call OUTC
call BIOS_CAS_WRITE_BLOCK
ld a,'.'
call OUTC
ret z
1$: push bc
ld a,TAPE_TYPE_RAW
call CSWRITEDATA
pop bc
dec bc
ld a,b
or c
jr nz,2$ ; repeat if not done
3$:
; All blocks are written
jr nz,1$
ret
ld hl,CLCLRF
call PRS
; -------------------------------------------------------
; Build a header in the tape block buffer
; needs bc (number of data blocks)
; needs hl (file length in bytes)
; needs bc' (filename address), e' (filename length)
; needs a (type of header)
; preserves bc,de
; uses a,h,l and the alternate register set
; -------------------------------------------------------
CSMAKEHEADER:
push de ; preserve data source address
push hl ; preserve byte length
push af
call BIOS_CAS_GET_BUFFER
pop af
ld (hl),a ; store header type
inc hl
ld (hl),c ; block count is little-endian
inc hl
ld (hl),b
inc hl
pop de ; byte length
ld (hl),e ; byte length is little-endian
inc hl
ld (hl),d
inc hl ; filename destination
pop hl
pop de
push bc ; preserve block count
push hl
exx
ld h,b ; hl = filename source
ld l,c
ld c,e ; bc = filename length
ld b,0
pop de ; de = filename destination
ld a,b
or c
jr z,1$
ldir
1$: xor a
ld (de),a ; terminate filename
exx
pop bc
pop de ; restore data source address
ret
; -------------------------------------------------------
; Copy and write one 128-byte data block
; needs a (block type), de (source address)
; returns de (source address advanced by 128 bytes)
; uses a,b,c,h,l
; -------------------------------------------------------
CSWRITEDATA:
call BIOS_CAS_GET_BUFFER
ld (hl),a
inc hl
ld bc,TAPE_DATA_SIZE
ex de,hl ; hl = source, de = buffer
ldir
ex de,hl ; de = next source address
call CSWRITEPAIR
ret
; -------------------------------------------------------
; Write the current tape buffer twice and show progress
; preserves bc,de,hl
; uses a
; -------------------------------------------------------
CSWRITEPAIR:
call BIOS_CAS_WRITE_BLOCK
ld a,'.'
call OUTC
call BIOS_CAS_WRITE_BLOCK
ld a,'.'
call OUTC
ret
; -------------------------------------------------------
; Leave 750 ms between a header pair and its data
; uses a,b,c
; -------------------------------------------------------
CSHEADERGAP:
ld a,250
call BIOS_DELAY_MS
ld a,250
call BIOS_DELAY_MS
ld a,250
call BIOS_DELAY_MS
ret
; -------------------------------------------------------