;======================================================
;        CP/M BYTESAVE by Martin Eberhard
;======================================================
; BYTESAVE is a menu-driven EPROM Programmer/Reader/
; Editor program for Cromemco's original ByteSaver,
; Bytesaver II, and 32K Bytesaver Boards, in a CP/M
; 2.X system. See the Help screen for a list of
; supported commands.
;
; Command Line Scripts
;
; An initial list of commands may be specified on the
; command line that invokes BYTESAVE. BYTESAVE commands
; on the command line are separated by commas. These
; commands will be executed before allowing user input
; from the BYTESAVE prompt. Any errors encountered while
; executing a command line script will terminate
; processing the remainder of the script. Example:
;
; BYTESAVE HA MYFILE,EP,QU
;
; This example will load the file MYFILE.HEX into the
; buffer, automatically setting the address offset. It
; will then program the EPROM from the buffer (after
; prompting the user to install the EPROM etc.), and
; then quit to CP/M.
;
; Screen Sizes
;
; BYTESAVE supports both a 16X64 screen and a 24X80
; (or larger) screen, selected with the SS command.
; At 16X64, the DB command does not display the ASCII
; values of the buffer data, since the screen is not
; wide enough.
;
; ==Systems with Cromemco Memory Bank Switching==
;
; To provive CP/M with a full 64K of RAM, this program
; can use the Cromemco bank-switching hardware on the
; Bytesaver II or 32K Bytesaver, together with this
; same feature on Cromemco (or compatible) RAM
; board(s). System RAM must be set for Bank 0. The
; Bytesaver II or 32K Bytesaver must be in the upper
; portion of another bank (defaulting to bank 7). The
; lower portion (at least 16K) of the system RAM
; board(s) must also be assigned to the bank that is
; assigned to the Bytesaver II or 32K Bytesaver.
;
; Note that several Bytesaver IIs or 32K Bytesavers
; (or a combination of these) can be simultaneously
; used, by assigning them to separate banks, or to
; different addresses within the Bytesaver bank.
;
; ==Systems without Cromemco Memory Bank Switching==
;
; This program also works in systems with memory
; boards that do not support the Cromemco bank-
; switching scheme, and/or with the original Bytesaver,
; which does not support bank-switching.
;
; For the Bytesaver and Bytesaver II, Install up to 56K
; of RAM in your system, with the Bytesaver or
; Bytesaver II occupying the highest 8K of memory,
; starting at address E000h. (If this address range is
; unavailable, you can change the Bytesaver address
; with the PA command, to locate it elsewhere.)
;
; For the 32K Bytesaver, install up to 60K of RAM in
; your system, with the 32K Bytesaver occupying the
; upper 32K of the address range, starting at 8000h.
; Set the "Shadow ROM" switches on the 32K Bytesaver to
; "off" for all but the highest-address ROM socket
; pair. Socket 14 is the default programming socket.
; (Socket 14 is the default, rather than socket 15,
; because it is near the top of the board.)
;
; If you set the Bytsaver bank to 0 (with the BB
; command), then this program will never write to port
; 40h, the Bank Select port. This is useful if your
; system uses port 40h for something else.
;
; You will need a version of CP/M that avoids the
; memory space that is occupied by the Bytesaver,
; and still leaves at least 14K of free RAM starting at
; address 100h.
;
;
; * Assemble with Digital Research's ASM assembler
; * Requires about 14K of RAM, starting at 0100h
;
;======================================================
; Revision History:
; 1.00 04Feb2014 M.Eberhard
;   Ported from CDOS BYTESAVE version 2.06.
; 1.01 14Mar2014
;   'BB 0' option to disable bank switching.
;   Never write to BSPORT for original Bytesaver.
; 1.02 27May2019 M. Douglas
;   Fix hex file loading to accept the 01 EOF record
;   type in a record specifying zero data bytes.
; To Do (maybe):
;  DF (directory of hex & bin files) command
;======================================================

;*********
; Defaults
;*********
DEFSS	equ	1	;0 means 64X16 screen,
			;1 means 80x24 screen
DEFAO	equ	0	;default target address offset
DEPMS	equ	60	;default 2704/08 prog time (mS)
			;min 1, max 255
DBTYPE	equ	1	;default bytesaver type
		  ;1=Bytesaver (2704/08s, no bank sel)
		  ;2=Bytesaver II (2704/08s)
		  ;3=32K Bytesaver (2716s)

DBSBNK	equ	7	;default Bytesaver's bank (0-7)
			;(0 means disabled)

DB2ADR	equ	0FC00h	;default Bytesaver, Bytesaver II
			;address (ROM 7 socket address)
DB3ADR	equ	0F000h	;default 32K Bytesaver address
			;(ROM 14 socket address)

;*************************
; Program Equates
;*************************
SZ2704	equ	2	;256-byte pages/2704
SZ2708	equ	4	;256-byte pages/2708
SZ2716	equ	8	;256-byte pages/2716

EBUFSZ	equ	SZ2716	;EPROM buffer size, in pages

LBSIZE	equ	80	;line buffer size
MAXDRV	equ	16	;max CP/M drive

FCBCLR	equ	36	;# of FCB bytes to clear
SECSIZ	equ	128	;sector size (must be 128)
SBUFSZ	equ	48	;Sectors per disk read/write
 ; SECTOR buffer size = SBUFSZ * 128 bytes
 ; multiples of 16 work well with CP/M. MUST be even.

;***************************************
; Cromemco Memory Bank Switching Equates
;***************************************
BSPORT	equ	40h	;Cromemco bank select port
OSBANK	equ	01h	;CP/M's bank bit (bank 0)
MAXBNK	equ	07h	;max allowed bank

;******************************************
; BDOS Function Codes, passed in register C
;******************************************
BCONIN	equ	1	;Read Console to a with echo
BCONOT	equ	2	;Write E to Console
BPRINT	equ	9	;Print $-terminated line at de
BRDCON	equ	10	;Input buffered line at de
BCONST	equ	11	;Console input status
			;a=-1 if chr, 0 if none
BOPEN	equ	15	;Open disk file, FCB at de
			; Aa=-1 if not found
BCLOSE	equ	16	;Close disk file, FCB at de
			; a=-1 if not found
BSERCH	equ	17	;Search dir for file, FCB at de
			; A=-1 if not found
BDELET	equ	19	;delete file, FCB at (de)
BREAD	equ	20	;Read next record. 0=OK, 1=EOF
BWRITE	equ	21	;Write next record, 0=OK, 1=ERR,
		; 2=out of disk space, 0FFH=NO DIR SPC
BMAKE	equ	22	;Create file, 0FFH=BAD
BSTDMA	equ	26	;Set disk buffer to (de)

;*******************************************
; BDOS Entry Points and low-memory locations
;*******************************************
CBASE	equ	0		;CP/M base address
WBOOT	equ	CBASE		;CP/M re-entry
BDOS	equ	CBASE+5		;CP/M System Call
FCB	equ	CBASE+5CH	;CP/M file control blk
FCBDD	equ	FCB		;Drive Descriptor
FCBFN	equ	FCB+1		;File name (8 chrs)
FCBFT	equ	FCB+9		;File Type (3 chrs)
FCBEXT	equ	FCB+12		;File extent within FCB
FCBRCNT	equ	FCB+15		;Record cnt within FCB
FCBSEC	equ	FCB+32		;Sector # within FCB
COMBUF	equ	CBASE+80H	;disk & cmd line buffer
USAREA	equ	CBASE+100H	;User program area

;-------------------------------
;Console line buffer uses COMBUF
;-------------------------------
LINBUF	equ	COMBUF

;*****************
; ASCII Characters
;*****************
BS	equ	08h
LF	equ	0Ah
CR	equ	0Dh
DEL	equ	7Fh

;============================
; Initialization
; Calls here use CP/M's stack
;============================
	ORG	USAREA

; print banner

	lxi	d,BANNER	;Sign-on msg
	call	CRPRNT

; Initialize all RAM variables

	mvi	a,DEFSS		;default screen size
	sta	SCRNSZ

	mvi	e,0FFh		;fill EPROM buffer
	call	FILBUF		;..with unprogrammed

	lxi	h,DEFAO		;Default address offset
	shld	OFFSET

	mvi	a,DEPMS		;default 2704/8 prog time
	sta	EPMS

	mvi	a,DBTYPE	;set initial bytesaver
	call	INISVR		;..and announce it

	mvi	e,DBSBNK	;set bytesaver bank
	call	INIBNK		;..and announce it

;see if there was any command line input other than spaces
;set SCRFLG if so

	lxi	d,LINBUF
	ldax	d		;how many bytes of input?
	sta	SCRFLG
	ora	a		;any input?
	jz	CLI2		;z:no command line script

;copy command line into script buffer

	inx	d		;skip byte count
	lxi	h,SCRBUF	;destination
	shld	SCRPTR		;set pointer too

	mov	b,a		;byte count
	mvi	c,0
	call	BLKMOV

	dad	b		;compute last address
	mov	m,c
CLI2:

;final bit of banner message

	lxi	d,BANR2		;type ? for help
	push	d

; Fall into PCMAIN

;===============================================
; Main loop: re-initialize, wait for command
; from the user & dispatch to process command
;
; Entry at MAIN does not print a string
;
; Entry at PRMAIN prints a $-terminated string
;  at (sp) and restores the stack.
;
; Entry at PCMAIN prints CR,LF before the string
;===============================================
PCMAIN:	call	PCRLF		;print CRLF 1st
PRMAIN:	pop	d
	lxi	sp,STACK	;SP=local stack
	call	PRINTF		;print incoming string

;re-initialize a few things

MAIN:	lxi	h,CLRBEG	;zero out some variables
	lxi	b,CLREND-CLRBEG
	mov	e,b		;e=0: fill value
	call	FILBLK

	lxi	h,FCB		;clear FCB
	mvi	c,FCBCLR	;byte count
	call	FILBLK

	lxi	h,FCBFN		;fill FCB name & ext 
	mvi	c,8+3		;..with spaces
	mvi	e,' '
	call	FILBLK

	lxi	h,SECBUF	;point to beginning of
	shld	SBPNTR		;..disk buffer

;get a line of user input

	lxi	d,PROMPT	;print prompt
	call	CRPRNT

	lxi	h,MAIN		;create return addr
	push	h		;..on the stack

;test for remaining script input

	lda	SCRFLG
	ora	a
	jz	NOSCPT

; get next script item and execute it

	call	TESTQ		;allow script abort

	lhld	SCRPTR		;script pointer
	lxi	d,LINBUF+2	;command buffer

; copy comma-terminated script command into command buffer

SCRLP:	mov	a,m		;get script chr
	ora	a		;end of line?
	jz	EOS
	cpi	','		;seperator?
	jz	EOS

	stax	d	
	call	PRINTA		;echo

	inx	d
	inx	h
	xra	a
	stax	d		;potential eol

	jmp	SCRLP

EOS:	sta	SCRFLG		;remember end
	inx	h		;point past comma
	shld	SCRPTR		;save pointer
	jmp	DOSCPT
	
; no more script input: get user input

NOSCPT:	mvi	c,BRDCON	;get user input
	lxi	d,LINBUF	;give CP/M a buffer
	mvi	a,LBSIZE	;buffer size
	stax	d
	call	GOBDOS		;get input line, which will
				;be 0-terminated because
				;LINBUF was filled with 0s

; put a null in the line buffer at the end of the input

	lxi	h,LINBUF+1	;BDOS put byte count here
	mov	a,m
	inx	h
	add	l		;compute last chr addr+1
	mov	l,a		;will be no carry
	mvi	m,0		;paste in null

; skip leading blanks, and ignore blank lines

DOSCPT:	lxi	h,LINBUF+2	;Beginning of user input
	call	SKIPSP		;skip leading spaces
	
	ora	a		;terminating 0?
	rz			;null line: try again

;-----------------------------------------
; Search cmd table for match
;  go to CMDERR if not in table
; On Entry:
;  hl=address of command 1st chr
; On Exit:
;  de=addr of pointer to execution routine
;  hl=address of next chr on cmd line
;-----------------------------------------
	call	GETUC		;get 2-digit command
	mov	b,a
	call	GETUC
	mov	c,a

	push	h		;LINBUF address on stack

	call	GETUC		;2 chr commands only
	jnz	CMDERR		;..error if more

;search command table for match of b,c

	lxi	h,CMDTAB-1

NXTCMD:	inx	h		;next entry

	mov	a,m		;1st command chr
	inx	h
	ora	a		;table end?
	jz	CMDERR		;cmd not in table

	xra	b		;compare 1st chr
	mov	d,a		;temp save

	mov	a,m		;2nd command chr
	inx	h

	mov	e,m		;address low byte
	inx	h

	xra	c		;compare 2nd chr
	ora	d		;compare both chrs
	jnz	NXTCMD		;no match: keep looking

	mov	d,m		;de=exec address


	pop	h		;1st chr past command

	call	SKIPSP		;skip spaces to 1st parameter
				;z set or hl=address of param	

;-------------------------------------------
; Command found. Dispatch to (de)
; with hl pointing to next command line item
; and Z flag set if end of cmd line
;-------------------------------------------
	push	d		;de has execution addr
	ret			;..go execute there
	
;===========================================
; Bogus command return (PCMAIN resets stack)
; On jump to PCMAIN:
;   SCRFLG=0 (cancels any scripts)
;   'Huh?' string address is on stack
;===========================================
CMDERR:	xra	a		;cancel any scripts
	sta	SCRFLG

	call	PCMAIN		;print msg & go to main
	db	'Huh?$'

;******==================================================
;* MB *  Modify Buffer Command
;******
; allow user to edit buffer contents
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr of 1st command paramter, if any
;========================================================
FEDIT:	call	GETHEX		;get address in de
	mov	a,b		;overflow?
	cpi	5		;4 digits max
	jnc	CMDERR

	mov	a,m		;any more chrs?
	ora	a
	jnz	CMDERR

	lda	EPSIZE		;past buffer end?
	dcr	a
	cmp	d
	jc	CMDERR

FEDIT0:	lxi	h,EBUF		;address
	dad	d		;hl=buff address

FEDIT1:	call	PCRADR		;print de=address

FEDIT2:	mov	a,m		;data
	call	PRHEX

	mvi	a,'.'		;separator
	call	PRINTA

	push	h
	push	d

;Loop to get input from console until
; either a space or a Q to quit

	lxi	h,LINBUF
	push	h
	mvi	b,0		;chr count

GLOOP:	call	CONIUC		;get uppercase
	cpi	'Q'
	jz	DODONE		;Q quits us

	cpi	BS		;any backspaces?
	jz	GLDEL
	cpi	DEL
	jz	GLDEL

	cpi	' '		;space means done
	jz	GLDON
	jc	CMDERR		;below is bogus

	mov	m,a		;save new chr
	inx	h
	inr	b
	jmp	GLOOP

;backspace

GLDEL:	mov	a,b		;can we back up?
	ora	a
	jz	GLOOP		;no: ignore

	mvi	a,BS		;actually BS
	call	PRINTA
	dcr	b		;back up
	dcx	h
	jmp	GLOOP

; Got complete input in LINBUF. Time to parse.

GLDON:	mvi	m,0		;null termination
	pop	h		;point to LINBUF			

	call	GETHEX		;convert to hex
	mov	a,b		;any digits?
	ora	a
	
	mov	a,e		;get low byte

	pop	d
	pop	h

	jz	FEDIT3		;no change?

	mov	m,a		;update w/ low byte

FEDIT3:	inx	h		;next buffer address
	Inx	d		;next absolute address

	mov	a,e		;need an address?
	ani	07h		;every 8
	jnz	FEDIT2

	lda	EPSIZE		;end of buffer?
	cmp	d
	jnz	FEDIT1		;n: keep going

	lxi	d,0		;wrap to beginning
	jmp	FEDIT0

;******==================================================
;* DB *  Display Buffer Command
;******
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr of 1st command paramter, if any
;========================================================
FBDISP:	jnz	CMDERR		;no params allowed

	lxi	d,0		;address count
	lxi	h,EBUF		;buffer addr
	mvi	b,0		;init checksum

FDISP1:	call	PCRADR		;print address

	push	h		;save for ASCII print
	push	d

;print 16 bytes in hex

FDISP2:	mov	a,m		;data
	add	b		;compute buffer checksm
	mov	b,a

	mov	a,m		;data
	call	PRHEX
	inx	d		;next
	inx	h

	mvi	a,' '		;spacer
	call	PRINTA

	mov	a,e
	ani	0Fh		;new line every 16
	jnz	FDISP2

;if wide screen, print them again in ASCII

	pop	d
	pop	h

FDISP3:	mov	a,m
	cpi	' '		;non-printing?
	jc	FDISP4
	cpi	DEL		;more non-printing
	jc	FDISP5
FDISP4:	mvi	a,'.'

FDISP5:	mov	c,a		;temp save
	lda	SCRNSZ		;wide enough screen?
	ora	a		;0 means no
	mov	a,c

	cnz	PRINTA
	inx	d		;next
	inx	h

	mov	a,e
	ani	0Fh		;new line every 16
	jnz	FDISP3

	ora	e		;pause every 256
	jnz	FDISP1

	lda	EPSIZE		;past buffer end?
	cmp	d
	jz	FDDONE

	call	ASKQSP		;space to continue, q to quit

	jmp	FDISP1

FDDONE:	lxi	d,BCSST
	call	CRPRNT
	mov	a,b		;print buffer checksum
	jmp	PRHEX

;******==================================================
;* FB *  Buffer Fill Command
;******
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr of 1st command paramter, if any
;========================================================
FFILL:	call	GETHEX		;get fill value in de
	mov	a,b		;overflow?
	cpi	3		;max 2 digits
	jnc	CMDERR

	call	FILBUF
	jmp	DODONE		;done

;******==================================================
;* SE *  Display settings
;******
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr of 1st command paramter, if any
;========================================================
FSET:	call	PBSVR		;Bytesaver type
	call	PPBANK		;Bytesaver bank
	call	PPADR		;Bytesaver address

	lda	EPSIZE		;32k BYTESAVER?
	cpi	SZ2716		;n: no mS prog time
	cnz	PEMS		;mS prog time

	jmp	PAO		;Targ address offset

;******==================================================
;* AO *  Set file address offset
;******
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr of 1st command paramter, if any
;========================================================
FSAO:	call	GETHEX

	xchg			;result in hl

	shld	OFFSET

; fall into PAO

;===Subroutine=============
; Print File Address Offset
; On exit:
;   de=(OFFSET)
;==========================
PAO:	lxi	d,PAOST
	lhld	OFFSET
	jmp	PSTH16

;******==================================================
;* SS *  Set Screen Size (0 means 16X64, 1 means 24X80)
;******
; On Entry:
;   z set if end of cmd line already
;   hl=address of 1st chr of 1st command paramter, if any
;========================================================
FSCRSZ:	call	GETHEX
	mov	a,e
	cpi	2
	jnc	CMDERR

	sta	SCRNSZ
	jmp	DODONE

;******==================================================
;* BS *  Set Bytesaver Type (1-3)
;******
; On Entry at FBSVR:
;   z set if end of cmd line already
;   hl=address of 1st chr of 1st command paramter, if any
; I=ON Entry at INISVR:
;   a=bytesaver type
;   (called during initialization)
;========================================================
FSBSVR:	call	GETHEX
	mov	a,d		;any high byte?	
	ora	a
	jnz	BSTERR		;y: too big

	ora	e		;0 not allowed
	jz	BSTERR
	cpi	MAXBT+1		;>max type?
	jnc	BSTERR		;y: too big

;save the Bytesaver type, and update related vars

INISVR:	sta	BSTYPE		;save the type

	add	a		;2 bytes/entry
	mov	l,a		;value is 1-3
	mvi	h,0

	lxi	d,BSPTAB-2	;look up params 
	dad	d

	mov	a,m		;1st is pages/EPROM
	sta	EPSIZE
	inx	h
	mov	a,m		;2nd is programmer addr
	sta	BSADDR

; fall into PBSVR

;===Subroutine================
; Print Bytesaver Type
;=============================
PBSVR:	lda	BSTYPE

	add	a		;2 bytes per entry
	mov	l,a		;value is 1-3
	mvi	h,0

	lxi	d,BSSTAB-2	;look up params 
	dad	d

	mov	e,m
	inx	h
	mov	d,m

	call	CRPRNT		;de=this BS's string

	lxi	d,SELST		;selected
	call	PRINTF

	jmp	PETYPE		;print EPROM type too

BSTERR:	call	ERRABT
	db	'Bytesaver type must be 1-'
	db	MAXBT+'0','$'

;******==================================================
;* ET *  Set EPROM type (2704/2708)
;******  (not for 32K Bytesaver)
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr of 1st command paramter, if any
;========================================================
FSETYP:	call	GETHEX		;get user input

	mov	a,d
	cpi	27h		;valid upper byte?
	jnz	FSTERR

	lda	EPSIZE		;32K bytesaver?
	cpi	SZ2716
	mov	a,e		;get low byte
	jnz	NOT16

	cpi	16h		;only 2716 is ok
	jnz	FSTERR		;..for the 32K BS
	jmp	PETYPE		;nothing to change	

NOT16:	rrc
	cpi	SZ2708		;2708 requested?
	jz	T2708
	cpi	SZ2704		;2704 requested?

FSTERR:	jnz	CMDERR		;n:error
T2708:
	sta	EPSIZE

; fall into PETYPE

;===Subroutine====
; Print EPROM type
;=================
PETYPE:	lxi	d,EPSST
	call	CRPRNT
	lda	EPSIZE		;will be 2,4,or 8
	ora	a		;clear carry
	add	a		;make it 04,08, or 16
	daa			;fix 16
	jmp	PRHEX		;print 2 digits

;******==================================================
;* PA *  Set Bytesaver EPROM address
;******
; On Entry:
;   z set if end of cmd line already
;   hl=address of 1st chr of 1st command paramter, if any
;========================================================
FPADR:	call	GETHEX

;adjust to even-EPROM boundary

	lda	EPSIZE		;create page mask
	dcr	a
	cma
	ana	d		;mask now

	sta	BSADDR		;remember base address
	mov	h,a		;for later

;announce if we adjusted the address

	xra	d		;did we adjust address?
	ora	e		;lsb must be 0

	lxi	d,ADJUST	;adjusted...
	cnz	CRPRNT

;warn user if programming will overwrite us

	mov	a,h
	cpi	RAMEND/256+1	;overwrite BYTESAVE?

	lxi	d,OVCST	;caution...
	cc	CRPRNT

; fall into PPADR

;===Subroutine============
; Print EPROM base address
;=========================
PPADR:	lxi	d,PADRST
	lhld	BSADDR-1	;h:=BSRADDR
	mvi	l,0
	jmp	PSTH16

;******==================================================
;* BB *  Set Bytesaver Bank (0-7, 0 means disabled)
;******
; On Entry:
;   z set if end of cmd line already
;   hl=address of 1st chr of 1st command paramter, if any
; On entry at INIBNK:
;   e=bytesaver bank
;   (called during init)
; On Exit:
;   BSBANK has bank select value,
;    calculated from user input
;========================================================
FPBANK:	call	GETHEX
	mov	a,d		;any high byte?	
	ora	a
	jnz	BNKERR		;y: too big

	cpi	MAXBNK+1	;>max bank?
	jnc	BNKERR		;y: too big 

; Loop to calculate BSBANK value. if 0, set BSBANK
; to 0, to indicate disabled bank switching

INIBNK:	mov	a,e
	ora	a		;0 means  disabled
	jz	BNKZRO

	xra	a		;clear carry
	inr	a		;a=1

FDBLUP:	ral
	dcr	e
	jnz	FDBLUP

BNKZRO:	sta	BSBANK		;and save it	

; fall into PPBANK

;===Subroutine====
; Print EPROM bank
;=================
PPBANK:	lxi	d,NOBSST	;error string just in case
	lda	BSTYPE		;does this Bytesaver
	dcr	a		;..support bank switching?
	jz	CRPRNT		;n: error

	lxi	d,NOBST		;no bank string
	lda	BSBANK
	ora	a		;banks disabled?
	jz	CRPRNT		;y: say so

	lxi	d,PBAST		;print the bank string
	call	CRPRNT

; loop to compute bank address

	lda	BSBANK
	mvi	d,0FFh		;-1

PPBLUP:	inr	d
	rar
	jnc	PPBLUP

	mov	a,d	
	jmp	PDEC8

BNKERR:	call	ERRABT
	db	'Bank must be 0-7$'

;******==================================================
;* MS *  Set mS to program
;******
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr of 1st command paramter, if any
;========================================================
FSMS:	call	GETDEC		;result in b

	mov	a,b		;input value
	ora	a		;0?
	jnz	FSMS1		;y:make it the default
	mvi	a,DEPMS

FSMS1:	sta	EPMS

; fall into PEMS

;===Subroutine===================
; Print programming ms in decimal
;================================
PEMS:	lxi	d,PEMSST
	call	CRPRNT
	lda	EPMS
	call	PDEC8
	lxi	d,MSPBST
	jmp	PRINTF

;******======================================
;* EP * Program the EPROM from the buffer
;******
; Not that the 2708 specs say that the entire
; EPROM should be programmed sequentially -
; you shouldn't program only part of a 2708.
;
; On Entry:
;   ERRCNT=0
;   Z set if end of cmd line already
;============================================
FPROG:	call	GETAC		;get address & count
	push	b		;byte count
	push	d		;buffer address
	push	h		;EPROM address

;if this is a 2704 or 2708 then warn the user
;about programming less than the entire EPROM

	lda	EPSIZE
	cpi	SZ2716		;2716?
	jnc	PARTOK		;y: partial EPROM ok

	cmp	b		;entire EPROM?
	push	d
	cnz	PEASK		;n: warn, ask to continue
	pop	d
PARTOK:
;blank-check the EPROM

	call	BSSEL		;select BYTESAVER bank

	mvi	d,0FFh		;erased state

PBCLUP:	mov	a,d		;recover result
	ana	m		;accumulate errors
	mov	d,a		;save result
	inx	h
	dcx	b
	mov	a,b		;done?
	ora	c
	jnz	PBCLUP	

	call	OSSEL		;select OS memory bank

	inr	d		;d=FF?
	cnz	NBASK

;program the EPROM when the user is ready

	lxi	d,RDYST
	call	CRPRNT
	call	ASKYN

	lxi	d,PROGST
	call	CRPRNT

	pop	h		;hl=EPROM address
	pop	d		;de=buffer address
	pop	b		;byte count

	lda	EPSIZE		;2716s?
	cpi	SZ2716
	jz	PONCE		;y: one pass only

;------------------------------------------------
;program a 2708 - requires EPMS mS of programming
; bc = byte coiunt
; de = buffer start address (source)
; hl = EPROM start address (destination)
;------------------------------------------------

	lda	EPMS		;EPMS= requested mS

PROGLP:	push	psw		;save mS counter

	call	BSSEL		;select BYTESAVER bank

;loop for 1 mS EPROM programming
;Each programming pulse on the Bytesaver is 176 uS

	mvi	a,6		;6X176 uS is ~1000 uS

MSLOOP:	push	psw		;save prog cycle count	
	call	BLKMOV		;leaves registers intact

	pop	psw		;prog cycle coint
	dcr	a
	jnz	MSLOOP		;loop for 1 mS total

;loop the specified number of mS

	call	PACIFY		;print dot, check for Q

	pop	psw		;ms counter
	dcr	a
	jnz	PROGLP		;loop specified # of mS

; verify the EPROM. 

	jmp	VERIFY		;go verify

;---------------------------------------
;Program a 2716 -takes just one pass
; Print a pacifier every 64 bytes
; bc = byte count
; de = buffer start address (source)
; hl = EPROM start address (destination)
;---------------------------------------
PONCE:	push	b
	push	d
	push	h

P1LOOP:	call	BSSEL		;select BYTESAVER bank

	ldax	d		;program a byte
	mov	m,a

	inx	d		;bump pointers
	inx	h
	dcx	b

	mov	a,c
	ani	3Fh		;need a pacifier?
	cz	PACIFY		;..every 64 bytes

	mov	a,c
	ora	b		;done?
	jnz	P1LOOP

	pop	h
	pop	d
	pop	b

	jmp	VERIFY		;go verify

;******=============================
;* EC * Compare EPROM to buffer
;******
; On Entry:
;   ERRCNT=0
;   Z set if end of cmd line already
;===================================
FCOMP:	call	GETAC		;get address & count
				;hl = EPROM address
				;de = buffer address
				;bc = byte count
; fall into VERIFY

;===Local Subroutine====
;Verify EPROM
; On Entry:
;  hl = EPROM address
;  de = buffer address
;  bc = byte count
;=======================
VERIFY:	push	d
	lxi	d,VERST		;Verifying...
	call	CRPRNT
	pop	d

FCLUP:	call	BSSEL		;select BYTESAVER bank

	ldax	d		;read from buffer
	cmp	m		;compare to EPROM

	cnz	CMPERR

	inx	h
	inx	d
	dcx	b
	mov	a,c
	ora	b
	jnz	FCLUP
	
; fall into PERCNT

;===Local Subroutine===
; Print error count
;======================
PERCNT:	call	OSSEL		;select OS memory bank

	lxi	d,TOTEST
	call	CRPRNT

	lhld	ERRCNT
	jmp	PDEC16

;******=============================
;* EB * Check for blank EPROM
;******
; On Entry:
;   ERRCNT=0
;   Z set if end of cmd line already
;===================================
FBLANK:	call	GETAC		;get address & count
				;hl = EPROM address
				;de = buffer address
				;bc = byte count

FBLUP:	call	BSSEL		;select BYTESAVER bank

	mov	a,m
	inr	a		;test for ff
	cnz	BLAERR
	inx	h

	dcx	b
	mov	a,c
	ora	b
	jnz	FBLUP

	jmp	PERCNT		;print error count

;******=============================
;* ER * Read EPROM into buffer
;******
; On Entry:
;   Z set if end of cmd line already
;===================================
FREAD:	call	GETAC		;get address & count
				;hl = EPROM address
				;de = buffer address
				;bc = byte count

	call	BSSEL		;select BYTESAVER bank

;move block

	xchg
	call	BLKMOV

	call	OSSEL		;select OS memory bank
	jmp	DODONE

;******===============================
;* ES * Display EPROM Checksum
;******
; On Entry:
;   Z set if end of cmd line already
;=====================================
FCKSUM:	call	GETAC		;get address & count
				;hl = EPROM address
				;de = buffer address
				;bc = byte count

	call	BSSEL		;select BYTESAVER bank

	xra	a		;init checksum

FCKLP:	add	m		;compute EPROM checksum
	inx	h
	dcx	b
	mov	d,a		;save checksum
	mov	a,b
	ora	c
	mov	a,d		;recover checksum
	jnz	FCKLP

	push	psw		;save result

	call	OSSEL		;select OS memory bank

	lxi	d,EPCST
	call	CRPRNT

	pop	psw		;recover checksum
	jmp	PRHEX		;..and print it

;******==================================================
;* HS * Save EBUF contents as Intel hex file on disk
;******
; On Entry:
;   ERRCNT=0
;   SBBCNT=0
;   SBPNTR=SECBUF
;   Z set if end of cmd line already
;   hl=address of 1st chr of 1st command paramter, if any
;========================================================
FHSAVE:	lxi	d,HEXST		;look for a .hex file
	call	GETFN		;get filename from user

;see if file already exists

	lxi	d,FCB		;FCB has file to open
	mvi	c,BSERCH	;FILE Search function
	call	GOBDOS
	inr	a		;-1 means not there
	cnz	FEXIST

; create & open the file

	lxi	d,FCB		;FCB has file to open
	mvi	c,BMAKE		;FILE Create function
	call	GOBDOS
	inr	a		;-1 means open failure
	jz	FOFAIL

; Create & send hex records

	lhld	OFFSET		;file address
	lxi	d,EBUF		;data source
	lda	EPSIZE
	rlc
	rlc
	rlc
	rlc
	mov	b,a		;b=# of 16-byte records

WRECLP:	push	b		;save b=record count
	mvi	a,':'		;record start
	call	WRBYTE

	lxi	b,16*256	;byte cnt, init checksm

	mov	a,b		;byte count
	call	WRHEX

	mov	a,h		;address high byte
	call	WRHEX		;write A as hex
	mov	a,l		;adress low byte
	call	WRHEX

	xra	a		;record type 0
	call	WRHEX

WRLUP:	ldax	d		;write data
	call	WRHEX
	inx	d
	inx	h
	dcr	b
	jnz	WRLUP

	xra	a		;compute checksum
	sub	c
	call	WRHEX

	mvi	a,CR		;CRLF at end
	call	WRBYTE
	mvi	a,LF
	call	WRBYTE

	pop	b		;more records?
	dcr	b
	jnz	WRECLP

; append EOF record

	mvi	a,':'		;EOF record like this
	mvi	b,11		;':0000000000'
WEOFLP:	call	WRBYTE
	mvi	a,'0'
	dcr	b
	jnz	WEOFLP

; flush out tail end of disk buffer

	lhld	SBBCNT
	mov	a,l		;need to flush?
	ora	h
	jz	NOFLSH

; pad to an even sector

	ani	7Fh		;need to pad?
	jz	FLNOW		;not if full sector

PADLP:	mvi	a,' '		;pad with spaces
	call	WRBYTE
	inx	h

	mov	a,l
	ani	7Fh		;even sector?
	jnz	PADLP

FLNOW:	call	WFLUSH

; close the file

NOFLSH:	lxi	d,FCB
	mvi	c,BCLOSE
	call	GOBDOS
	inr	a		;error?
	jnz	DODONE		;no: done

	call	ERRABT
	db	'File close failed$'

;--------------------
; local error routine
;---------------------
FOFAIL:	call	ERRABT
	db	'File open failed$'

;---Local subroutine----------
; The file already exists.
; Ask if it should be deleted.
;-----------------------------
FEXIST:	lxi	d,EXISTS
	call	CRPRNT
	call	ASKYN

	lxi	d,FCB		;delete the old file
	mvi	c,BDELET
	jmp	GOBDOS

;******==================================================
;* BL * Load binary file from disk into EBUF
;****** load address is set by AO
; On Entry:
;   ERRCNT=0
;   SBBCNT=0
;   SBPNTR=SECBUF
;   Z set if end of cmd line already
;   hl=address of 1st chr of 1st command paramter, if any
; On Exit:
;  EBUF has binary file
;  byte count message printed
;========================================================
FBLOAD:	lxi	d,BINST		;file must by type BIN
	call	GETFN		;get filename from user

	call	OPNFIL		; open the file

	call	PAO		;print offset
				;de=OFFSET on ret

	lxi	b,0		;byte count

FBLOOP:	lda	EPSIZE		;overflow?
	dcr	a
	cmp	d
	jc	FBOVER

	call	RDBYTE		;get a data byte
	jc	FBDONE

	lxi	h,EBUF		;compute buffer dest
	dad	d
	mov	m,a		;stash chr

	inx	b		;bump byte count
	inx	d		;next address
	jmp	FBLOOP

FBOVER:	lxi	d,EBOST	;buffer overflow
	call	CRPRNT

FBDONE:	push	b
	jmp	PRBC		;print byte count, done

;******==================================================
;* HA * Load Intel hex file from disk into EBUF
;****** Set address offset according to the 1st record
;       ignore all bytes outside EPROM address range
; On Entry:
;   ERRCNT=0
;   SBBCNT=0
;   SBPNTR=SECBUF
;   Z set if end of cmd line already
;   hl=address of 1st chr of 1st command paramter, if any
; Register usage during load:
;    b: scratch
;    c: Reord byte count
;    d: record checksum
;    e: record type
;   hl: memory address
; (sp): buffer byte count
; On Exit:
;  EBUF has received records
;  byte count message printed
;========================================================
FALOAD:	mvi	a,1		;set auto-offset
	sta	AUTOFS

;fall into FHLOAD

;******==================================================
;* HL * Load Intel hex file from disk into EBUF
;****** ignore all bytes outside EPROM address range
; On Entry:
;   ERRCNT=0
;   SBBCNT=0
;   SBPNTR=SECBUF
;   Z set if end of cmd line already
;   hl=address of 1st chr of 1st command paramter, if any
; Register usage during load:
;    b: scratch
;    c: Record byte count
;    d: record checksum
;    e: record type
;   hl: memory address
; (sp): buffer byte count
; On Exit:
;  EBUF has received records
;  byte count message printed
;========================================================
FHLOAD:	lxi	d,HEXST		;file must by type HEX
	call	GETFN		;get filename from user

	call	OPNFIL		;open the file

;begin loading file

	lxi	h,0		;clear byte count
	push	h

; eat all chrs until record-start colon

GETCO:	call	RDBYTE
	jc	EOFERR		;unexpected EOF?
	cpi	':'
	jnz	GETCO

; record byte count to c, restart checksum

	call	GETBYT
	mov	c,b		;byte count
	mov	d,b		;start checksum

; record address to hl

	call	GETBYT
	mov	h,b		;address high byte
	CALL	GETBYT
	mov	l,b		;address low byte

; auto-offset?

	lda	AUTOFS
	ora	a

	cnz	SETOFF

; offset address: hl:=hl-(OFFSET)

	push	d
	xchg			;de=address from record
	lhld	OFFSET		;user-set addr offset

	mov	a,e		;hl:=de-hl
	sub	l
	mov	l,a
	mov	a,d
	sbb	h
	mov	h,a		;hl=offset address
	pop	d

; record type to e

	call	GETBYT
	mov	e,b

; 0-byte record? this is an alternate EOF.
; don't get data for such records

	mov	a,c		;0-byte record?
	ora	a
	jnz	BYTLP		;N: go get data

	mvi	e,1		;make it type 1

	jmp	GCKSM

; loop to get record data and put it in buffer.
; ignore data outside the buffer.

BYTLP:	call	GETBYT		;result in b

	lda	EPSIZE		;overflow?
	dcr	a
	cmp	h		;beyond buffer?
	jc	NOWRIT		;y: don't write or cnt

	push	h		;compute buffer address
	push	d

	lxi	d,EBUF		;point into the buffer
	dad	d		;hl = address in buffer	
	
	mov	m,b		;write data to buffer

	pop	d
	pop	h

	xthl			;bump written-byte cnt
	inx	h
	xthl	

NOWRIT:	inx	h		;next byte in record
	dcr	c
	jnz	BYTLP

;check checksum

GCKSM:	call	GETBYT		;get checksum byte
	jnz	CSMERR		;checksum error

; record done. Check record type

	mov	a,e
	ora	a		;was it type 0?
	jz	GETCO		;Y: get another record

	dcr	a		;Type 1 is EOF
	jnz	RTERR		;any other is an error

; The file is now in the buffer. (sp)=byte count.

;----------------------------
; report number of bytes read
; Also used by BL command
;----------------------------
PRBC:	call	PCRLF
	pop	h		;written-byte count
	call	PDEC16		;decimal byte count

	lxi	d,LOADST
	jmp	PRINTF		;return from there

CSMERR:	call	ERRABT
	db	'Checksum fail$'

RTERR:	call	ERRABT
	db	'Bad record type$'

;*****=====================
;* ? * Display help screen
;***** 
;==========================
FHELP1:
;    1234567890123456789012345678901234567890123456789012345678901234
 db '            ==BYTESAVE Programmer Commands=='
 db CR,LF
 db 'EPROM COMMANDS                          BUFFER COMMANDS'
 db CR,LF
 db ' EB [<a> [<c>]] Blank-Check EPROM        DB      Display Buffer'
 db CR,LF
 db ' EC [<a> [<c>]] Compare EPROM to Buffer  FB <HH> Fill Buffer'
 db CR,LF
 db ' EP [<a> [<c>]] Prog EPROM from Buffer   MB <a>  Modify Buffer'
 db CR,LF
 db ' ER [<a> [<c>]] Read EPROM into Buffer    (Space skips, Q quits)'
 db CR,LF
 db ' ES <a> <c> Compute EPROM Checksum'
 db CR,LF
 db '                                      PROGRAMMER SETTINGS'
 db CR,LF
 db 'FILE <-> BUFFER TRANSFER COMMANDS      BB <0-7>  Bytesaver Bank'
 db CR,LF
 db ' AO <HHHH>       Set Address Offset              (0 disables)'
 db CR,LF
 db ' BL <file>[.BIN] Load Bin File         BS <T>    Bytesaver type'
 db CR,LF
 db ' HA <file>[.HEX] Load Hex, Auto-Offset PA <HHHH> Programmer Addr'
 db CR,LF
 db ' HL <file>[.HEX] Load Hex File         ET <2704/2708> EPROM Type'
 db CR,LF
 db ' HS <file>[.HEX] Save to Hex File      MS <1-255> mSec Prog Time'
 db '$'

;***************
;* ENTRY POINT *
;***************
FHELP:	lxi	d,FHELP1	;print 1st half of help screen
	call	PRINTF

	lda	SCRNSZ		;will it all fit onscreen?
	ora	a		;0 means no
	cz	ASKQSP		;n: wait for space from user

	call	PCMAIN		;print 2nd half of help screen
 db CR,LF
 db 'OTHER COMMANDS'
 db CR,LF
 db ' SE       Display Settings             QU  Quit to CP/M'
 db CR,LF
 db ' SS <0/1> Screen Size 16X64/24X80       ?  Print Help Screen'
 db CR,LF,CR,LF
 db ' No value for EPROM byte count <c> goes to the end of the EPROM.'
 db CR,LF
 db ' No value for EPROM address <a> starts at EPROM address 0.'
 db CR,LF
 db ' <T>: 1=Original Bytesaver, 2=Bytesaver II, 3=32K Bytesaver.'
 db CR,LF
 db ' ET and MS are for Original Bytesaver and Bytesaver II only.'
 db '$'

;===Subroutine==================
;Fill EBUF
; On Entry:
;    e = fill value
; Trashes a,bc,hl
;===============================
FILBUF:	lxi	b,EBUFSZ*256	;size of buffer
	lxi	h,EBUF

; fall into FILBLK

;===Subroutine==================
;Fill block
; On Entry:
;   bc = number of bytes to fill
;    e = fill value
;   hl = starting address
;
; On Exit:
;   bc=0
; Trashes a,hl
;===============================
FILBLK:	mov	m,e		;loop to fill buffer
	inx	h
	dcx	b
	mov	a,b
	ora	c
	jnz	FILBLK

	ret

;===Subroutine====================================
; Set Bank Select for Bytesaver
; only if BSBANK<>0 and not using an original
; Bytesaver (which doesn't support bank switching)
; trashes A
;=================================================
BSSEL:	lda	BSTYPE		;does this Bytesaver
	dcr	a		;..support bank switching?
	rz			;n: no need to set it

	lda	BSBANK		;select BYTESAVER bank
	ora	a		;no bank selecting?
	rz

	out	BSPORT
	ret

;===Subroutine====================================
; Set Bank Select for Operating System
; only if BSBANK<>0 and not using an original
; Bytesaver (which doesn't support bank switching)
; trashes A
;=================================================
OSSEL:	lda	BSTYPE		;does this Bytesaver
	dcr	a		;..support bank switching?
	rz			;n: no need to set it

	lda	BSBANK		;check BYTESAVER bank
	ora	a		;no bank selecting?
	rz

	mvi	a,OSBANK	;select OS memory bank
	out	BSPORT
	ret

;===Subroutine===========================================
; Set OFFSET according to the address
; in the 1st record.
; On entry:
;   hl=address of 1st chr of 1st command paramter, if any
; On Exit:
;   OFFSET=1K page address of hl
; a trashed, all others preserved
;========================================================
SETOFF:	push	d
	push	h

	lxi	d,FADDST	;announce address
	call	PSTH16		;de=address

	mov	a,d		;compute page address
	ani	0FCh		;1K pages
	mov	h,a
	mvi	l,0
	shld	OFFSET		;save new offset

	call	PAO		;announce file offset

	pop	h
	pop	d

	xra	a		;clear auto-offset mode
	sta	AUTOFS
	ret

;===Subroutine=====================================
; Get 2 hex digits from the disk file, combine them
; into a byte, and add the result to the checksum
; ON ENTRY:
;   d = Checksum
; ON EXIT:
;   b=Data byte
;   a=d=Updated checksum
;   z set if checksum is 0
;   All other registers preserved
;==================================================
GETBYT:	call	RDHEX		;get high nibble
	jnc	ASCERR		;error if invalid hex
	add	a		;do high nibble
	add	a
	add	a
	add	a
	mov	b,a
	call	RDHEX		;get low nibble
	jnc	ASCERR		;error if invalid hex

	ora	b		;combine nibbles
	mov	b,a		;save for return
	add	d		;do checksum
	mov	d,a
	ret

; ASCII chr error - not a valid hex digit

ASCERR:	call	ERRABT
	db	'Invalid hex chr$'

EOFERR:	call	ERRABT		;unexpected EOF
	db	'Unexpected EOF$'	

;===Subroutine==========================
; Read and convert hex digit into A<3:0>
; On Exit:
;   c cleared if bad hex digit
;   a=binary result
;=======================================
RDHEX:	call	RDBYTE
	jc	EOFERR		;unexpected EOF?
; fall into HEXCON

;=====Subroutine=================
; Convert a from hex to binary
; carry cleared if bogus hex
; z cleared and c set if good hex
; All regs preserved
;================================
HEXCON:	sui	'0'		;remove ASCII bias
	cpi	10
	rc			;if 0-9 then we're done

	sui	'A'-'9'-1	;subtract gap to 'A'
	cpi	10		;between ASCII 9 and A?
	cmc			;so carry means good
	rnc			;error exit

	cpi	10H		;above ASCII F?
	ret			;c set if okay

;===Subroutine========================================
;Read one disk byte from SECBUF
; Load from disk if SECBUF is empty
; For speed, this routine buffers up to SBUFSZ blocks
; (sectors) into SECBUF each time it reads from disk. 
; On Entry:
;   SBBCNT = number of remaining bytes in SECBUF
;   SBPNTR = address of next byte to send
; On Exit:
;   SBBCNT, SBPNTR updated
;   carry set if EOF
;   chr in a
;=====================================================
RDBYTE:	push	b
	push	d
	push	h

	lhld	SBBCNT		;decrement byte count
	mov	a,h
	ora	l		;empty? go read disk
				;also clears carry

	cz	RDFILL		;empty? get more
	jc	RDBDON		;eof: done with c set

	dcx	h		;dec the byte counter
	shld	SBBCNT

	lhld	SBPNTR		;now get the byte
	mov	a,m		;result in a for ret

	inx	h		;and bump pointer
	shld	SBPNTR

RDBDON:	pop	h
	pop	d
	pop	b
	ret			;value in a, cy valid

;===Subroutine========================================
; Read as many sectors from the disk as possible into
; SECBUF
; On Entry:
; On Exit:
;   ABORT if EOF
;   hl = number of data bytes in SECBUF
;   SBPNTR = SECBUF
;   carry set if EOF and no data
;=====================================================
RDFILL:	lda	EOFLG		;Have we seen the EOF?
	ora	a
	stc			;carry means no data
	rnz	

	mvi	b,SBUFSZ	;B=free buffs in SECBUF
	lxi	d,SECBUF	;de=address in SECBUF

RSECLP:	mvi	c,BSTDMA	;Set BDOS DMA address
	call	GOBDOS		;trashes no registers

	xchg			;pointer to hl, free de

	lxi	d,FCB		;Disk sect. into SECBUF
	mvi	c,BREAD
	call	GOBDOS		;trashes no registers

	ora	a		;Read ok?
	jnz	RSCOND 		;No: go find out why

	lxi	d,SECSIZ 	;next sector
	dad	d
	xchg			;Result goes in de

	dcr	b
	jnz	RSECLP		;go until all space used

	jmp	RBFULL		;No: done reading

;------------------------------------------------
;Unsuccessful read either because of EOF or error
;------------------------------------------------
RSCOND:	dcr	a		;EOF?
	jnz	DSKERR

;----------------------------------------------
;Encountered the EOF. Set EOFLG so we will stop
;----------------------------------------------
	inr	a		;a=1
	sta	EOFLG		;SET EOF FLAG

;	fall into RBFULL

;---------------------------------------
;Receive buffers are all full, or we got
;an EOF from CP/M. compute & save byte
;count, point SBPNTR to the 1st byte,
;clear carry
; On Entry:
;   b = remaining sector space in SECBUF
; On Exit:
;  hl = # of bytes read
;---------------------------------------
RBFULL:	lxi	h,SECBUF	;Point SBPNTR to start
	shld	SBPNTR		;..of SECBUF

	mvi	a,SBUFSZ	;compute # sectors read
	sub	b		;b=remaining space

	rar			;compute # bytes read
	mov	h,a		;=sec countx128
	mvi	a,0		;don't mess up carry
	rar			;rotate carry in
	mov	l,a		;hl=byte count

	ret			;carry is cleared

DSKERR:	call	ERRABT
	db	'Disk error$'

;=====Subroutine================
;Write A as 2 hex digits to disk
;and compute checksum in c
;On Entry
;  A = value to print
;Trashes A
;===============================
WRHEX:	push	psw		;save digit
	add	c		;compute checksum
	mov	c,a
	pop	psw		;recover digit
	push	psw		;save low nibble
	rrc			;hi nibble 1st
	rrc
	rrc
	rrc
	call	WHXNIB
	pop	psw		;recover low nibble

;fall into PHXNIB

;------------------------------------
;local subroutine to write hex nibble
;On Entry:
;  Nibble in low 4 of A
;Trashes a,C
;------------------------------------
WHXNIB:	ani	0Fh		;get low
	cpi	0Ah		;A-F?
	jc	WHX1
	adi	'A'-'9'-1
WHX1:	adi	'0'

;fall into WRBYTE

;===Subroutine==================
; Write one byte to disk buffer
; flush the buffer if full
;===============================
WRBYTE:	push	h
	push	b
	push	d

	lhld	SBPNTR		;put the byte
	mov	m,a

	inx	h		;bump the pointer
	shld	SBPNTR

	lhld	SBBCNT		;and the count
	inx	h

	mov	a,h		;full?
				;SBUFSZ must be even.
	cpi	SBUFSZ/2
	cz	WFLUSH

	shld	SBBCNT

	pop	d
	pop	b
	pop	h
	ret

;===Subroutine=======================================
; Write all data in SECBUF to disk
; On Entry:
;   hl=number of bytes to transfer, a multiple of 128
;    a=h
; On Exit:
;   hl=0 (new SBBCNT)
;   Trashes all other registers
;====================================================
WFLUSH:	mov	a,l		;compute # of sectors
	ral			;msb into carry
	mov	a,h
	adc	a		;a=sector count
	rz			;Return w/ Z set if end
	mov	b,a		;Sector count in b

	lxi	d,SECBUF	;de=start of sect data

WFLOOP:	mvi	c,BSTDMA	;BDOS SET DMA function
	call	GOBDOS		;de = DMA address

	xchg			;pointer to hl, free de
	shld	SBPNTR		;reset pointer

	lxi	d,FCB		;Write from buf to disk
	mvi	c,BWRITE
	call	GOBDOS

	ora	a		;return 0 if okay
	jnz	WRERR		;oops, error

				;hl = address in SECBUF
	lxi	d,SECSIZ	;de=sector size
	dad	d		;(hl)=next sector data
	xchg			;addr to de for BSTDMA

	dcr	b
	jnz	WFLOOP		;until all sectors sent

	lxi	h,0		;new BYTECNT for ret
	ret

WRERR:	call	ERRABT
	db	'Write fault$'

;===Subroutine============================================
; Get file name from input line and install it in the FCB
; make sure it is the requested type
; On Entry:
;   Z set if end of cmd line already
;   de=address of desired file-type string
;   hl=address of 1st chr of 1st command parameter, if any
; On exit:
; Trashes all regs
;=========================================================
GETFN:	push	d		;desired file type string

	lxi	d,FCBDD		;drive descriptor within FCB
	inx	h		;was a drive letter included?
	mov	a,m
	dcx	h
	cpi	':'
	cz	GETDR		;y: go get the drive number

;get the file name

	inx	d		;file name within FCB
	lxi	b,9*256 + '.'	;8 chrs for a file name
	call	GETNAM		;..terminate on period

;get file extension, if there is one (carry set if '.' found)

	lxi	d,FCBFT
	lxi	b,4*256 + 0	;3 chrs for extension
	cc	GETNAM		;..and don't hunt for .'.

;if no file type given, then set it to the desired type

	pop	d		;desired file type string
	lxi	h,FCBFT		
	lxi	b,3		;move/test 3 bytes

	mov	a,m
	cpi	' '		;any file type?
	cz	BLKMOV		;saves all regs but a

;make sure desired file type at de matches FCBFT

	push	d		;desired type for error msg

GFNLP:	ldax	d
	xra	m
	ora	b
	mov	b,a
	inx	d
	inx	h
	dcr	c
	jnz	GFNLP

	pop	d		;desired type for error msg

	ora	a		;any mismatches?
	rz			;n: done

;wrong file type - error

	push	d		;desired type on stack
	lxi	d,WFTST		;error: file must be
	call	CRPRNT

	jmp	PRMAIN		;string is on stack

;===Subroutine=============================================
;Get file name or extension and write it into the FCB at de
; On Entry:
;   b = # of characters for the name + 1
;   c = end chr in addition to space (0 for none)
;  de = destination address for the name
;  hl = next chr in input buffer
; On Exit:
;   carry set if chr matched c
;   a=terminating chr (null, space, or period)
;  hl=address of input chr after last one used
;  trashes de
;==========================================================
GETNAM:	call	GETUC		;get input chr, z set if
	rz			;..none or space

	cmp	c
	stc			;in case of match
	rz

	dcr	b
	jz	CMDERR		;error if too many chrs	

	stax	d		;new chr into FCB
	inx	d

	jmp	GETNAM

;***Local Subroutine**************************
;Get Drive Descriptor and save it in the FCB
; On entry:
;   b=remaining bytes in LINBUF
;  hl=address of next input chr
;  (hl+1) is know to be a colon
;  de=address for drive descriptor in the FCB
; ON Exit:
;  (de)=drive descriptor from input
;   hl points to chr after colon
;**********************************************
GETDR:	call	GETUC		;get drive descriptor
	inx	h		;skip colon
	sui	'A'
	cpi	MAXDRV+1
	jnc	CMDERR

	inr	a		;since 0 means default
	stax	d		;drive descriptor into FCB
	ret

;===Subroutine==========
; Open disk file
; On Entry:
;  de points to filename
; trashes a,c
; Abort if error
;=======================
OPNFIL:	lxi	d,FCB		;FCB has file to open
	mvi	c,BOPEN		;FILE OPEN function
	call	GOBDOS
	inr	a		;-1 means open failure
	rnz	

	call	ERRABT
	db	'File not found$'

;===Subroutine==================================
; Get address and byte count from user
; On Entry:
;   z set if no parameters
;   hl=address of 1st chr after cmd
; On Exit:
;   bc = byte count
;   de=start address in EBUF buffer
;   hl=start address in EPROM
;   If no given start address, then default to 0
;   If no given count, then default to the
;     size of the EPROM.
;   If user-supplied count is beyond the
;     end of the EPROM, clip it to the end
;===============================================
GETAC:	call	GETHEX		;get de=start address
				;returns 0 if no param

;Error if the provided start address is
;greater than the last EPROM address

	lda	EPSIZE		;past EPROM end?
	dcr	a
	cmp	d
	jc	CMDERR		;y: huh?

;get the byte count. If none is given, or if the
;value is 0, then set it to FFFF, so it will
;get clipped in the next step

	push	d		;save start address

	call	GETHEX		;get de=byte count
	mov	a,d
	ora	e		;zero or no value?
	jnz	GETAC1

	dcr	d		;de=FFFF: force clip
GETAC1:

;if the provided byte count goes past the end of the
;EPROM, then clip it to exactly the EPROM end.

;first, compute remaining EPROM bytes
;hl := EPSIZE-start
;(sp) = start, de=byte count

	pop	h		;recover start
	push	h		;save start

	xra	a		;low byte of EPSIZE
	sub	l
	mov	l,a
	lda	EPSIZE		;now the high byte
	sbb	h
	mov	h,a		;hl=remaining bytes

;next, compare count to remaining bytes: hl-de

	mov	a,l
	sub	e
	mov	a,h
	sbb	d		;carry set if de>hl
	jnc	NOCLIP

;clip byte count hl, (which goes just to the end
;of the EPROM) and announce it

	lxi	d,CLIPST	;'count adjusted to'
				;hl=value
	call	PSTH16		;print string and hex
				;returns value in de

NOCLIP:	xchg			;put count into hl

;compute actual RAM address for the buffer
;RAM address = start address + EBUF

	xthl			;count on stack, get start
	push	h		;save start onto stack
	lxi	d,EBUF
	dad	d		;hl=buffer address

	xchg			;de=buffer address for ret

;compute actual RAM address for the EPROM
;RAM address = start address + (BSADDR)

	pop	h		;get start again

	lda	BSADDR
	add	h
	mov	h,a		;hl=EPROM address for ret

	pop	b		;bc=count for ret
	ret

;====Subroutine=====================
; Print error address and found data
;    hl=EPROM address
;  (hl)=found (EPROM) data
;===================================
BLAERR:	push	d
	push	h

;get EPROM data while EPROM bank is still active

	mov	a,m		;get bad data
	push	psw		;save bad data

;switch to OS bank, so we can print

	call	OSSEL		;select OS memory bank

;compute & print EPROM address = hl-(BSADDR)
;note that the low byte of (BSADDR) = 0
	xchg			;de=offending address
	lxi	h,BSADDR
	mov	a,d
	sub	m
	mov	h,a
	mov	l,e		;hl=address in EPROM	

	lxi	d,ERRAST	;error at:
	call	PSTH16		;string in de, addr in hl

; print found data

	lxi	d,FNDST		;. found:
	call	PRINTF

	pop	psw		;recover bad data
	call	PRHEX		;..and print it

	lhld	ERRCNT		;remember error
	inx	h
	shld	ERRCNT

	call	TESTQ		;user abort?

	pop	h
	pop	d
	ret
	
;===Subroutine======================================
; Print error address, found data, and expected data
; On Entry:
;    hl=EPROM address
;  (hl)=found (EPROM) data
;  (de)=expected (Buffer) data
; On Exit:
;   a trashed
;   SCRFLG=0 (cancels any scripts)
; all regs preserved
;===================================================
CMPERR:	call	BLAERR		;prnt addr & EPROM data
				;..and select OSBANK
	push	d
	lxi	d,EXPST		;Expected:
	call	PRINTF
	pop	d

	xra	a		;cancel any script
	sta	SCRFLG

	ldax	d		;get expected data
	jmp	PRHEX		;print value & ret

;===Subroutine====================
; Print ERROR: message
; On Entry:
;   message address on stack
; On jump to PRMAIN:
;   SCRFLG=0 (cancels any scripts)
;   message address on stack
;=================================
ERRABT:	xra	a		;cancel any scripts
	sta	SCRFLG

	lxi	d,ERRST		;error:
	call	CRPRNT
	jmp	PRMAIN		;string address on stack

;===Subroutine=
;Print CR, LF
;trashes a
;==============
PCRLF:	push	d
	lxi	d,CRLFST
	call	PRINTF
	pop	d
	ret

;=====Subroutine========================
; Print CR,LF,$-terminated message at de
; trashes a,de
;=======================================
CRPRNT:	call	PCRLF

;fall into PRINTF

;=====Subroutine==================
; Print $-terminated message at de
; trashes a,de
;=================================
PRINTF:	push	b
	mvi	c,BPRINT
	call	GOBDOS
	pop	b
	ret

;===Subroutine======================
; Print string at de, then hl as hex
; on exit:
;   de has value
;   trashes a,hl
;===================================
PSTH16:	call	CRPRNT		;print string at de
	lxi	d,COLSPC	;then a colon & a space
	call	PRINTF
	xchg			;print value in hl

; fall into PHEXDE

;=====Subroutine==========
;print  de as 4 hex digits
;trashes a
;=========================
PHEXDE:	mov	a,d		;high byte
	call	PRHEX
	mov	a,e		;low byte

; fall into PRHEX

;=====Subroutine========
;Print A as 2 hex digits
;On Entry
;  A = value to print
;Trashes a
;=======================
PRHEX:	push	psw		;save digit
	rrc			;hi nibble 1st
	rrc
	rrc
	rrc
	call	PHXNIB
	pop	psw		;recover lo nibble

;fall into PHXNIB

;------------------------------------
;local subroutine to print hex nibble
;On Entry:
;  Nibble in low 4 of A
;Trashes a
;------------------------------------
PHXNIB:	ani	0Fh		;get low
	cpi	0Ah		;A-F?
	jc	PHX1
	adi	'A'-'9'-1
PHX1:	adi	'0'

;fall into PRINTA

;===Subroutine==========
; Print A on the console
; Trashes a
;=======================
PRINTA:	push	b
	push	d
	mov	e,a
	mvi	c,BCONOT
	call	GOBDOS
	pop	d
	pop	b
	ret

;===Subroutine====================
;print CRLF, then de as an
;address, then a colon and a space
;trashes a,de
;=================================
PCRADR:	call	PCRLF		;new line
	call	PHEXDE		;4 digit address
	mvi	a,':'
	call	PRINTA
	mvi	a,' '
	jmp	PRINTA

;===Subroutine=================
; Incomplete EPROM
; Ask whether to continue
;==============================
PEASK:	lxi	d,PWST
	jmp	EASKYN

;===Subroutine=================
; Non-blank EPROM
; Ask whether to continue
;==============================
NBASK:	lxi	d,NBEST
EASKYN:	call	CRPRNT
	lxi	d,CONTST	;continue?
	jmp	ASKCON

;===Subroutine================
;Ask user Y or N
;On Entry: de = initial string
;Trashes a,c,de
;=============================
ASKYN:	lxi	d,ASKST		;(Y/N)?
ASKCON:	call	PRINTF

	call	CONIUC		;get uppercase
	cpi	'Y'
	rz
	call	PCMAIN		;abort if not Y
	db	'Abort$'

;=====Subroutine===================
; Print A as decimal on the console
; Trashes all registers
;==================================
PDEC8:	mov	l,a
	mvi	h,0

;fall into PDEC16

;=====Subroutine===================
;Print hl as decimal on the console
; Trashes all registers
;==================================
PDEC16:	mvi	d,0	;Suppress leading 0's

	lxi	b,-10000
	call	DECDIG
	lxi	b,-1000
	call	DECDIG
	lxi	b,-100
	call	DECDIG
	lxi	b,-10
	call	DECDIG

	mov	a,l	;last digit is simple
	jmp	DECDG0	;with leading 0's

;------------------------------------------------
; Local subroutine to divide hl by power of 10 in
; bc and print result, unless it's a leading 0.
; On Entry:
;   hl=Dividend
;   bc=divisor (a negative power of 10)
;   d=0 if all prior digits were 0
; On Exit:
;   Quotent is printed, unless it's a leading 0
;   hl=remainder
;   d=0 iff this and all prior digits are 0
;------------------------------------------------
DECDIG:	mvi	a,0FFh	;will go 1 too many times
	push	d	;leading zero state

DIGLP:	mov	d,h	;de gets prev value
	mov	e,l
	inr	a
	dad	b	;subtract power of 10
	jc	DIGLP

	xchg		;hl has remainder
	pop	d	;leading 0 state

	mov	e,a	;e has digit to print
	ora	d	;leading 0 to suppress?
	rz		;yes: digit is done

	mov	d,a	;save for next digit
 
	mov	a,e	;make ASCII
DECDG0:	adi	'0'
	jmp	PRINTA	

;=====Subroutine=========================
; get decimal parameter < 256 from user
; On Entry:
;   hl points to first chr of a parameter
; On Exit:
;   b=value, 0 if nothing entered
;  hl points to first chr of next param
;  Rude jump to CMDERR if bogus chr
;========================================
GETDEC:	mvi	b,0		;initial value

GDLOOP:	call	GET1		;get a digit
	jz	SKIPSP		;exit if none left

	sui	'0'		;de-ASCII
	cpi	10		;carry means below 10
	jnc	GPERR

	push	psw		;save new digit

	mov	a,b		;compute b=b*10
	rlc			;*2
	rlc			;*4
	add	b		;*5
	rlc			;*10
	mov	b,a

	pop	psw		;recover 1st digit
	add	b		;combine digits
	mov	b,a		;result into b

	jnc	GDLOOP		;overflow means error

; fall into GPERR

GPERR:	jmp	CMDERR		;bogus: rude abort

;=====Subroutine==========================
; get a multidigit hex value from the user
; Allow lowercase for a-f
; On Entry:
;   hl points to first hex digit
; On Exit:
;   b=number of digits found
;  de=value
;  hl points to next input param
;  Rude exit if bogus
;=========================================
GETHEX:	lxi	d,0		;initial value
	mov	b,d		;b counts digits

GHLUP:	call	GETUC		;get an input digit
	jz	SKIPSP		;no more? then exit

	call	HEXCON		;convert to binary
	jnc	GPERR

	inr	b		;bump digit count
	xchg			;shift prev digits left 4
	dad	h
	dad	h
	dad	h
	dad	h
	xchg

	add	e		;combine new digit
	mov	e,a

	jmp	GHLUP		;keep going until gone

;=====Subroutine=====================
; Skip over spaces in cmd line
; On Entry:
;  hl=address in cmd line
; On Exit:
;  a=next chr on line
;  hl=address of first chr past space
;  Z set and a=0 if no params found
;====================================
SKIPSP:	mov	a,m
	ora	a		;end of cmd already?
	rz			;Y: ret with z set

	cpi	' '		;space?
	rnz			;n: ret with z clear

	inx	h		;y: skip it
	jmp	SKIPSP

;=====Subroutine================================
;Get 1 chr from input line, convert to uppecase
; On Entry:
;   hl = address of next input chr
; On Exit:
;   Z set if end of input (null) or space
;   uppercase chr in a and hl advanced otherwise
;===============================================
GETUC:	call	GET1
	rz

	cpi	'a'		;lowercase letter?
	rc			;n: done, z cleared

	ani	'a' XOR 'A' XOR 0FFh	;make uppercase
	ret			;z cleared

;=====Subroutine================================
;Get 1 chr from input line
; On Entry:
;   hl = address of next input chr
; On Exit:
;   Z set if end of input (null) or space
;   chr in a and hl advanced otherwise
;===============================================
GET1:	mov	a,m		;any chrs?
	ora	a
	rz			;no:Z set

	cpi	' '		;no digit?
	rz

	inx	h
	ret

;=====Subroutine=======================
; Programming pacifier:
; Print a dot, then test for Q pressed.
; Rude jump to PRMAIN if Q or q
; z flag set if no chr waiting
; a=waiting chr AND 0BFh, if not Q or q
; (a=0 if chr is a space)
;======================================
PACIFY:	call	OSSEL		;select OS memory bank

	mvi	a,'.'		;print pacifier
	call	PRINTA

; fall into TESTQ

;=====Subroutine=======================
; Test for Q pressed.
; Rude jump to PRMAIN if Q or q
; z flag set if no chr waiting
; a=waiting chr AND 0BFh, if not Q or q
; (a=0 if chr is a space)
;======================================
TESTQ:	push	b
	mvi	c,BCONST
	call	GOBDOS
	ora	a
	mvi	a,0FFh		;disambiguate space
	mvi	c,BCONIN
	cnz	GOBDOS
	pop	b
	ani	'q' XOR 'Q' XOR 0FFh
	cpi	'Q'
	rnz

	xra	a		;cancel script
	sta	SCRFLG

; Fall into DODONE

DODONE:	call	PCMAIN
	db	'Done$'

;=====Subroutine===================
;Get console chr, convert to
;uppercase, and echo
; On exit:
;  chr is in a
; trashes c
;==================================
CONIUC:	mvi	c,BCONIN
	call	GOBDOS

	cpi	'a'		;need to fix case?
	rc			;n: done

	ani	'a' XOR 'A' XOR 0FFh
	ret

;=====Subroutine===============
; Move block
; On Entry:
;   de = source address
;   hl = destination address
;   bc = byte count
; On Exit:
;   a=0
;   all other regs preserved
;==============================
BLKMOV:	push	b
	push	d
	push	h

BMLOOP:	ldax	d
	mov	m,a
	inx	h
	inx	d
	dcx	b
	mov	a,b
	ora	c
	jnz	BMLOOP

	pop	h
	pop	d
	pop	b
	ret

;=====Subroutine===================
;Issue command to BDOS
; On exit:
;  a is as BDOS returned it
;  all other registers preserved
;==================================
GOBDOS:	push	b
	push	d
	push	h
	call	BDOS
	pop	h
	pop	d
	pop	b
	ret

;=====Subroutine===================
;Ask if we should continue
; quit to MAIN if Q
;==================================
ASKQSP:	push	d
	lxi	d,SCONST	;space to continue
	call	CRPRNT

WAITQS:	call	TESTQ
	ora	a		;a=0 if space
	jnz	WAITQS		;keep waiting

	pop	d
	ret

;======================================
; Command Table
; 4 bytes/entry
;  Bytes 1 & 2 are the command
;  Bytes 3 & 4 are the execution address
;=======================================

; EPROM Commands

CMDTAB:	DB	'EB'		;Blank check EPROM
	DW	FBLANK
	DB	'EC'		;EPROM compare
	DW	FCOMP
	DB	'EP'		;Program EPROM
	DW	FPROG
	DB	'ER'		;REad EPROM to buffer
	DW	FREAD
	DB	'ES'		;Checksum EPROM
	DW	FCKSUM

; Buffer Comands

	DB	'DB'		;Display buffer
	DW	FBDISP
	DB	'FB'		;fill buffer
	DW	FFILL
	DB	'MB'		;Modify buffer
	DW	FEDIT

; Programmer Options

	DB	'PA'		;Set programmer address
	DW	FPADR
	DB	'BB'		;Set Bytesaver bank
	DW	FPBANK
	DB	'MS'		;set programming ms
	DW	FSMS
	DB	'BS'		;Set Bytesaver Type
	DW	FSBSVR
	db	'ET'		;set 2704/2708
	dw	FSETYP

; File Commands

	DB	'AO'		;set address offset
	DW	FSAO
	db	'HA'		;load hex, auto-offset
	dw	FALOAD
	DB	'HL'		;Load hex file
	DW	FHLOAD
	DB	'HS'		;Save hex file
	DW	FHSAVE
	DB	'BL'		;load binary file
	DW	FBLOAD

; Others

	DB	'SE'		;display settings
	DW	FSET
	DB	'QU'		;quit
	DW	WBOOT
	DB	'?',0		;help
	DW	FHELP
	DB	'SS'		;Screen size
	DW	FSCRSZ

	DB	0		;Table End

;==================================================
; Bytesaver Parameter Table
; 2 bytes/entry
;  Byte 1 = pages/EPROM
;  Byte 2 = high byte of programming socket address
;==================================================
BSPTAB:	db	SZ2708,DB2ADR/256	;1=original bytesaver
	db	SZ2708,DB2ADR/256	;2=Bytessaver II
	db	SZ2716,DB3ADR/256	;3=32K Bytesaver

;========================
; Bytesaver String Table
; 2 bytes/entry
;========================
BSSTAB:	dw	OBSST			;1=original bytesaver
	dw	BS2ST			;2=Bytessaver II
	dw	BS32ST			;3=32K Bytesaver

MAXBT	equ	3	;max known bytesaver type

;====================
;$-terminated strings
;====================
BANNER:	db '===========================================',CR,LF
	db '= BYTESAVE EPROM Programmer/Reader/Editor =',CR,LF
	db '=   For the various Cromemco Bytesavers   =',CR,LF
	db '=  Vers. 1.02  May, 2019  by M. Eberhard  =',CR,LF
	db '===========================================',CR,LF
	db '$'
BANR2:	db CR,LF,CR,LF,'Type ? for help'	;fall into CRLFST

CRLFST:	db	CR,LF,'$'

PROMPT:	db	'>$'
NBEST:	db	'EPROM range is not blank$'
PWST:	db	'Programming partial EPROM not advised$'
CONTST:	db	'. Continue?'	;fall into ASKST
ASKST:	db	' (Y/N)? $'
SCONST:	db	'  -Space to continue, Q to quit-$'
RDYST:	db	'EPROM Power on, ready$'
PROGST:	db	'Programming$'
VERST:	db	'Verifying$'
EPSST:	db	'EPROM Type: 27$'
PADRST:	db	'Programmer address'	;fall intp COLSPC
COLSPC:	db	': $'
PAOST:	db	'Target address offset$'
PEMSST:	db	'2704/08 programming time: $'
MSPBST:	db	' mS/Byte$'
TOTEST:	db	'Total errors: $'
ERRST:	db	'Error: $'
ERRAST:	db	'Error at$'
FNDST:	db	'. Found: $'	
EXPST:	db	', expected: $'
EXISTS:	db	'File exists. Overwrite$'
BCSST:	db	'Buffer checksum: $'
EPCST:	db	'EPROM checksum: $'
LOADST:	db	' bytes loaded into buffer$'
NOBST:	db	'Bank select disabled$'
PBAST:	db	'Bytesaver bank: $'
EBOST:	db	'Error: Buffer overflow$'
OBSST:	db	'Original Bytesaver$'
BS2ST:	db	'Bytesaver II$'
BS32ST:	db	'32K Bytesaver$'
SELST:	db	' selected$'	
NOBSST:	db	'No bank select for this programmer$'
ADJUST:	db	'Address adjusted to EPROM boundary$'
OVCST:	db	'Caution: EP will overwrite this program!$'
CLIPST:	db	'Byte count$'
FADDST:	db	'First record address$'
WFTST:	db	'Error: Must be .$'
HEXST:	db	'HEX$'
BINST:	db	'BIN$'

;--------------------------------
;System Variables cleared at MAIN
;--------------------------------
CLRBEG:
AUTOFS:	ds	1	;auto-offset mode
EOFLG:	ds	1	;EOF encountered
ERRCNT:	ds	2	;error count
SBBCNT:	ds	2	;Count of bytes in SECBUF
CLREND	equ	$

;---------------------
;More System Variables
;---------------------
SBPNTR:	ds	2	;Points to next byte in SECBUF
BSBANK:	ds	1	;Bytesaver bank select byte
BSADDR:	ds	1	;base address of EPROM, high byte
EPSIZE:	ds	1	;256-byte pages/EPROM
BSTYPE:	ds	1	;Bytesaver type
SCRNSZ:	ds	1	;Screen size: 0=16X64, 1=24X80
EPMS:	ds	1	;mS of EPROM programming
OFFSET:	ds	2	;16-bit Hex Rec Address Offset
SCRPTR:	ds	2	;points to next script byte
SCRFLG:	ds	1	;nz means script present

;-----------
;Local stack
;-----------
	ds	64	;room for stack
STACK	equ	$	;top of stack

;---------------------
;Buffer for EPROM data
;---------------------
EBUF:	ds	EBUFSZ*256
EBUFND:	equ	$
;------------------
;Disk Sector Buffer
;------------------
SECBUF:	ds	SBUFSZ*SECSIZ
;-------------
;Script buffer
;-------------
SCRBUF	ds	LBSIZE

RAMEND	equ	$
	END


