	title	'SAVE.RSX - CP/M 3.0 save routine.  July 1982'
;	*************************************************
;	*
;	*	Title:	SAVE.RSX	Resident System eXtension       
;	*	Date:	7/28/82
;	*	Author:	Thomas J. Mason
;	*
;	*	Modified:
;	*	11/30/82 - Thomas J. Mason
;	*	Added trap for function 60 to fix PUT and SAVE
;	*	bios vector mods.
;	*
;	*********************************************************
;
;	Copyright (c) 1982
;	Digital Research
;	PO Box	579
;	Pacific Grove, Ca.  93950
;
TRUE	equ	0FFFFh
FALSE	equ	not TRUE
;
; BIOS and BDOS Jump vectors
;
WBOOT	equ	0
WBTADR	equ	1	;address of boot in BIOS
BDOS	equ	5	;BDOS jump vector
BDOSAD	equ	6	;location of instructions
DFCB	equ	05Ch	;default FCB
;
; BDOS Function calls 
;
BDOSAD	equ	6		;BDOS jump address
PSTRING	equ	9		;print string
BUFIN	equ	10		;console buffer input
CFILE	equ	16		;file close
DFILE	equ	19		;file delete
WFILE	equ	21		;file write
MFILE	equ	22		;make file
SETDMA	equ	26		;set DMA function
BDOSER	equ	45		;Set BDOS error mode
GETSCB	equ	49		;get/set scb func #
LDRSX	equ	59		;function for RSX load
CALRSX	equ	60		;call rsx func #
CONMOD	equ	109		;GET/SET Console Mode
;
; Non Printable ASCII characters
;
CTL$C	equ	03	;CONTROL-C
CR	equ	13	;ASCII Carrige Return
LF	equ	10	;ASCII Line Feed
;
VERSION	equ	30
;
; Buffer size
;
CONMAX	equ	13	;console buffer maximum
STKSZE	equ	010h	;size fo stack
SCBOST	equ	068h	;page boundary + to jmp instr
RETDSP	equ	0FEh	;RETurn and DiSPlay mode
JUMP	equ	0C3h	;opcode for jump
LXIH	equ	21h	;lxi instr to poke
BSNLY	equ	07Fh	;restore bios jump table only
CMMON	equ	0F9h	;offset of common memory base from pg. bound
;
;	*********************************
;	*				*
;	*	The Save Program	*
;	*				*
;	*********************************
;
	db	0,0,0,0,0,0
	jmp	PREFIX
NEXTJ:
	db	JUMP		;jump
NEXT:
	db	0,0		;next module in line
PREV:
	dw	5		;previous, initialized to 5
STKYBT:	db	00h		;for warm start
	db	0
	db	'SAVE    '
	ds	3
;
;
; This is the check performed every time the BDOS is
; called to see if the RSX is to be invoked
;
PREFIX:
	mov	a,c	;set up for compare
	cpi	CALRSX
	jnz	GETGOING

	push	b
	push	d
	push	h
	lxi	h,0000h		;zero out HL
	dad	d		; <HL> -> RSXPB
	mov	a,m		;get the byte
	cpi	160		; sub function defined

	pop	h
	pop	d
	pop	b
	jz	GOODBYE		;remove this RSX

GETGOING:
;
	cpi	LDRSX	;do the compare
	jz	START
	lhld	NEXT		;get address for continue
	pchl			;get going.....
;
;
;
START:
;
; They are equal so get the BIOS address to point here
; in case of a Func 0 call
;
	push	b		;save state
	push	d		; of registers
;
; check for jump byte before the SCB
	call	GETSET$SCB
	shld	SCBADR		;save address for later
;
	mvi	l,CMMON+1	;offset into scb to check BIOS
	mov	a,m		;get byte
	ora	a		;check for zero
	mvi	a,FALSE		;store for insurance
	sta	CHGJMP		;non-banked = FALSE
	jz	NBNKED		;high byte zero if non-banked
;
	lhld	SCBADR		;restor SCB
	mvi	l,SCBOST	;offset from page for instr
	mov	a,m		;get byte
	cpi	JUMP		;is it a jump?
	jnz	MORRSX		;we are not alone
	mvi	a,TRUE
	sta	CHGJMP		;set flag
	mvi	m,LXIH		;put in lxi h,xxxx mnemonic
;
MORRSX:
;	continue with processing
NBNKED:
;
;
	lhld	WBTADR		;get address at 01h
	inx	h		;now points to address of jmp xxxx
	mov	a,m		;get low order byte
	sta	BIOSAD
	inx	h		;next byte
	mov	a,m
	sta	BIOSAD+1	;high order byte
;
; Now poke the BIOS address to point to
; the save routine.
;
	lxi	d,BEGIN		;begining of routine
	mov	m,d
	dcx	h		;point back to first byte
	mov	m,e		;low order
;
	mvi	c,BDOSER	;now set BDOS errormode
	mvi	e,RETDSP	;to trap any hard
	call	BDOS		;errors
;
;
	pop	d
	pop	b
	lhld	NEXT
	pchl			;continue on
;
BEGIN:
; Start of the save routine
; Notify the user which program is running
;
	lxi	sp,STACK	;initialize stack
	lxi	d,SIGNON	;prompt
	call	PSTR
;
; Get the file from the user
;
FLEGET:
	lxi	d,FLEPRMPT	;ask for file name
	call	PSTR
	call	GETBUF
; zero at end of string for parser
	lxi	h,CONBUF-1	;address of #
	mov	a,m		;get it
	cpi	0
	jz	REPLCE
	inx	h		;HL->CONBUF
	mvi	d,0		;zero out high order
	mov	e,a		;fill low
	dad	d		;add to h
	mvi	m,00		;zero out byte for parse
	push	h
;
;
	call	PARSE
	mov	a,h
	cpi	0FFh
	jz	FLEGET
;
	pop	h		;get end of string address back
	inx	h
	mvi	m,'?'		;put in question mark
	inx	h		;bump
	mvi	m,' '		;blank in string
	inx	h		;bump
	mvi	m,'$'		;end of string
;
	mvi	c,17		;Search for first
	lxi	d,DFCB
	call	BDOS		;find it
	inr	a		;bump Acc
	jz	FLECLR		;file no present skip prompt
;
	lxi	d,DELFLE
	call	PSTR		;print out delete prompt
	lxi	d,CONBUF	;buffer address
	call	PSTR		;print out filename
	call	GETBUF		;get answer
	call	GNC		;get the next char
	cpi	'Y'		;is it yes
	jnz	FLEGET		;another name if not
;
; Delete any existing file, then make a new one
FLECLR:
	mvi	c,DFILE		;file delete func
	lxi	d,DFCB		;default FCB
	call	BDOS		;real BDOS call
;
	mvi	a,0
	lxi	h,07ch		;M -> record count in FCB
	mov	m,a		;zero out record count
;
	mvi	c,MFILE		;make file function
	lxi	d,DFCB		;default FCB
	call	BDOS
; Get the address of start of write
;
STRADD:
	lxi	d,SPRMPT	;first address
	call	PSTR
	call	GETBUF
;
	lda	BUFFER+1	;get # of chars read
	cpi	0
	jz	STRADD
;
	call	SCANAD		;get address
	jc	STRADD
;
	shld	SADDR		;store in SADDR
;
; Get the finish address
ENDADD:
	lxi	d,FPRMPT	;load prompt
	call	PSTR		;print
	call	GETBUF		;read in
;
	lda	BUFFER+1
	cpi	0
	jz	ENDADD
;
	call	SCANAD		;get finish address
	jc	ENDADD
;
	shld	FADDR		;store it
	xchg
	lhld	SADDR
	xchg
;
	call	CHECK
	jc	STRADD
;
;
	lhld	SADDR		;beginning DMA address
	xchg			;DE=DMA address
;
; Write the first record then check the beginning address
; if DMA address ends up larger exit
;
WLOOP:
	call	WFLAG
	push	d		;save DMA address
 	mvi	c,SETDMA
	call	BDOS		;set DMA address
;
	mvi	c,WFILE	
	lxi	d,DFCB
	call	BDOS		;write
;
; Check for directory space on disk for extents
	lxi	d,NODIR
	cpi	01h		;no more directory
	jz	FINIS
;
; CHECK data block error
	lxi	d,NOBLK
	cpi	02h
	jz	FINIS		;out of disk space!
; final check
	ora	a		;if bad write occured...
	jnz	REPLCE		;restore BIOS address
;
; Write OK now check write address
	pop	d		;get DMA address
	lxi	h,080h
	dad	d
	xchg
	lhld	FADDR		;HL=end of write
;
	call	CHECK
;
	lda	ONEFLG
	cpi	TRUE
	jnz	WLOOP		;WLOOP if not done
;
; Else, Close file and print out ending prompt
CLOSE:
	mvi	c,CFILE		;close function
	lxi	d,DFCB		;get filename
	call	BDOS
;
	inr	a		;check for close error
	lxi	d,CERROR
	jz	FINIS		;maybe write protected
;
;good copy
	lxi	d,ENDMSG
FINIS:
	call	PSTR
;
; Replace the BIOS Address to correct one
REPLCE:
	lhld	BIOSAD	;HL=BIOS warm jump
	xchg		;DE="     "    "
	lhld	WBTADR
	inx	h
	mov	m,e
	inx	h
	mov	m,d
;
GOODBYE:
	mvi	a,0FFh
	sta	STKYBT		;change sticky byte for 
;				; removal of RSX
;
; check to see if JMP changed for BANKED system
	lda	CHGJMP
	cpi	TRUE		;has it been done?
	jnz	CHGBIOS
	lhld	SCBADR		;retreive SCB address
	mvi	l,SCBOST	;points to page + offset
	mvi	m,JUMP		;restore original code
;
CHGBIOS:
	mvi	c,13		;reset the disk system
	call	BDOS
;
	mvi	c,0		;set up for wboot
	call	BDOS
;****************************************
;*					*
;*	 Logical end of the program	*
;*					*
;****************************************
;
GETSET$SCB:
	mvi	c,GETSCB
	lxi	d,SCBPB
	call	BDOS
	ret
;
WFLAG:
	mvi	a,FALSE
	sta	ONEFLG
	lda	RSLT+1
	cpi	00h
	rnz	
	lda	RSLT
	cpi	080h
	jc	WFLAG1
	jz	WFLAG1
	ret
;
WFLAG1:
	mvi	a,TRUE
	sta	ONEFLG
	ret
;
;
;
CHECK:
; Subtract the two to find out if finished
	mov	a,l		;low order
	sub	e		;subtraction
	sta	RSLT
	mov	a,h		;now ...
	sbb	d		;high order subtraction	
	sta	RSLT+1		;saved
	ret
;
GETBUF:
;buffer input routine
;
	lxi	h,CONBUF	;address of buffer
	shld	NEXTCOM		;store it
	mvi	c,BUFIN
	lxi	d,BUFFER
	call	BDOS
	ret
;
PSTR:
; String output routine for messages
;
	mvi	c,PSTRING
	call	BDOS
	ret
;
PARSE:
; General purpose parser
;
; Filename = [d:]file[.type][;password]
;
; FCB assignments
;
;	0	=> drive, 0=default, 1=A, 2=B
;	1-8	=> file, converted to upper case,
;		   padded with blanks
;	9-11	=> type, converted to upper case,
;		   padded with blanks
;	12-15	=> set to zero
;	16-23	=> passwords, converted to upper case,
;		   padded with blanks
;	24-25	=> address of password field in "filename",
;		   set to zero if password length=0.
;	26	=> length of password (0-8)
;
; Upon return, HL is set to FFFFh if BC locates
;		   an invalid file name;
; otherwise, HL is set to 0000h if the delimiter
;		   following the file name is a 00h (null)
;		   or a 0Dh (CR);
; otherwise, HL is set to the address of the delimiter
;		   following the file name.
;
;
	lxi	h,0
	push	h
	push	h
	lxi	d,CONBUF	;set up source address
	lxi	h,DFCB		;set up dest address
	call	DEBLNK		;scan the blanks
	call	DELIM		;check for delimeter
	jnz	PARSE1
	mov	a,c
	ora	a
	jnz	PARSE9
	mov	m,a
	jmp	PARSE3
;
PARSE1:
	mov	b,a
	inx	d
	ldax	d
	cpi	':'
	jnz	PARSE2
;
	mov	a,b
	sui	'A'
	jc	PARSE9
	cpi	16
	jnc	PARSE9
	inr	a
	mov	m,a
	inx	d
	call	DELIM
	jnz	PARSE3
	cpi	'.'
	jz	PARSE9
	cpi	':'
	jz	PARSE9
	cpi	';'
	jz	PARSE9
	jmp	PARSE3
;
PARSE2:
	dcx	d
	mvi	m,0
PARSE3:
	mvi	b,8
	call	SETFLD
	mvi	b,3
	cpi	'.'
	jz	PARSE4
	call	PADFLD
	jmp	PARSE5
;
PARSE4:
	inx	d
	call	SETFLD
PARSE5:
	mvi	b,4
PARSE6:
	inx	h
	mvi	m,0
	dcr	b
	jnz	PARSE6
	mvi	b,8
	cpi	';'
	jz	PARSE7
	call	PADFLD
	jmp	PARSE8
PARSE7:
	inx	d
	call	PWFLD
PARSE8:
	push	d
	call	DEBLNK
	call	DELIM
	jnz	PARSE81
	inx	sp
	inx	sp
	jmp	PARSE82
PARSE81:
	pop	d
PARSE82:
	mov	a,c
	ora	a
	pop	b
	mov	a,c
	pop	b	
	inx	h
	mov	m,c
	inx	h
	mov	m,b
	inx	h
	mov	m,a
	xchg
	rnz
	lxi	h,0
	ret
PARSE9:
	pop	h
	pop	h
	lxi	h,0FFFFh
	ret
;
SETFLD:
	call	DELIM
	jz	PADFLD
	inx	h
	cpi	'*'
	jnz	SETFD1
	mvi	m,'?'
	dcr	b
	jnz	SETFLD
	jmp	SETFD2
SETFD1:
	mov	m,a
	dcr	b
SETFD2:
	inx	d
	jnz	SETFLD
SETFD3:
	call	DELIM
	rz
	pop	h
	jmp	PARSE9
;
PWFLD:
	call	DELIM
	jz	PADFLD
	inx	sp
	inx	sp
	inx	sp
	inx	sp
	inx	sp
	inx	sp
	push	d
	push	h
	mvi	l,0
	xthl
	dcx	sp
	dcx	sp
PWFLD1:
	inx	sp
	inx	sp
	xthl
	inr	l
	xthl
	dcx	sp
	dcx	sp
	inx	h
	mov	m,a
	inx	d
	dcr	b
	jz	SETFD3
	call	DELIM
	jnz	PWFLD1
;
PADFLD:
	inx	h
	mvi	m,' '
	dcr	b
	jnz	PADFLD
	ret
;
DELIM:
	ldax	d
	mov	c,a
	ora	a
	rz
	mvi	c,0
	cpi	0Dh
	rz
	mov	c,a
	cpi	09h
	rz
	cpi	' '
	jc	DELIM2
	rz
	cpi	'.'
	rz
	cpi	':'
	rz
	cpi	';'
	rz
	cpi	'='
	rz
	cpi	','
	rz
	cpi	'/'
	rz
	cpi	'['
	rz
	cpi	']'
	rz
	cpi	'<'
	rz
	cpi	'>'
	rz
	cpi	'a'
	rc
	cpi	'z'+1
	jnc	DELIM1
	ani	05Fh
DELIM1:
	ani	07Fh
	ret
DELIM2:
	pop	h
	jmp	PARSE9
;
DEBLNK:
	ldax	d
	cpi	' '
	jz	DBLNK1
	cpi	09h
	jz	DBLNK1
	ret
DBLNK1:
	inx	d
	jmp	DEBLNK
; End of the Parser
;
; GET a character from the console buffer
GNC:
	push	h
	lxi	h,CONBUF-1	;get length
	mov	a,m
	ora	a		;zero?
	mvi	a,CR		;return with CR if so
	jz	GNCRET
	dcr	m		;lenght = length-1
	lhld	NEXTCOM		;next char address
	mov	a,m
	inx	h		;bump to next
	shld	NEXTCOM		;update
GNCRET:
	pop	h
TRANS:
	cpi	7Fh		;Rubout?
	rz
	cpi	('A' or 0100000b)
	rc
	ani	1011111b	; clear upper case bit
	ret
;
;
; Scan the buffer for the address read in ASCII from the terminal
;
SCANAD:
	lxi	d,00h		;zero out address
	push	d		;and save
;
	lda	CONBUF-1	;get character count
	cpi	05		;5 is too many
	jc	SCAN0
	stc			;set carry for routine
	jmp	SCNRET
SCAN0:
	call	GNC		;get a char
	cpi	CR		;end?
	jz	SCNRET		;to scnret if so
	cpi	'0'		;is it >0?
	jnc	SCAN01		;bad character
	jmp	SCNRET
SCAN01:
	cpi	'@'
	jnz	SCAN02		;bad character
	stc
	jmp	SCNRET		;return on bad file
SCAN02:
	jnc	SCAN1		;must be A-F
	sui	030h		;normalize 0-9
	jmp	SCAN2
SCAN1:
	cpi	'G'		;is it out of range?
	jc	SCAN11
	stc
	jmp	SCNRET
SCAN11:	
	sui	037h		;normalize
SCAN2:
	mov	l,a		;character in low of DE
	lda	CONBUF-1	;get # left
	adi	1		;readjust
	mov	c,a
	mvi	h,00		;zero out high order
SCAN3:
	dcr	c		;dec to set flag
	jz	SCAN4		;were done
	dad	h		;shift 1bit left
	dad	h		;same
	dad	h		;same
	dad	h		;finally
	jmp	SCAN3		;back for more
;
SCAN4:
	pop	d		;ready for or
	mov	a,d		;high order
	ora	h		;
	mov	d,a
	mov	a,e		;low order
	ora	l		;ORed
	mov	e,a		;back
	push	d		;save
	jmp	SCAN0		;get more characters
SCNRET:
	pop	d		;hl = address
	xchg			;DE->HL
	ret
;
;
;	*********************************
;	*				*
;	*	Data Structures		*
;	*				*
;	*********************************
;
SCBPB:
	db	03Ah	;SCB address
	db	0
;
SADDR:	dw	0		;write start address
FADDR:	dw	0		;write finish address
BIOSAD:	dw	0		;WarmBOOT bios address
NEXTCOM: dw	0		;address of next character to read
ONEFLG:	db	0
RSLT:	dw	0
CHGJMP	db	FALSE
;
SCBADR:	dw	0		;Scb address
;
BIOSMD:	db	0		;if non-zero change LXI @jmpadr to
				;JUMP when removed.
;
BUFFER:	db	CONMAX
	db	0		;# of console characters read
CONBUF:	ds	CONMAX
;
SIGNON:	db	CR,LF,'CP/M 3 SAVE - Version ',VERSION/10+'0','.',VERSION mod 10+'0','$'
FLEPRMPT: db	CR,LF,'Enter file '
	db	'(type RETURN to exit): $'
DELFLE:	db	CR,LF,'Delete $'
SPRMPT:	db	CR,LF,'Beginning hex address $'
FPRMPT:	db	CR,LF,'Ending hex address    $'
ENDMSG:	db	CR,LF,'$'
;
; Error messages......
CERROR:	db	CR,LF,'ERROR: Bad close.$'
NODIR:	db	CR,LF,'ERROR: No directory space.$'
NOBLK:	db	CR,LF,'ERROR: No disk space.$'
;
; Stack for program
	ds	STKSZE
STACK:
	end		;Physical end of program
