
; (comm725a.asm)

; process primary option and process/validate secondary options (set
; 'uartctlb' to last declared operating mode.  usr remembers mode.)
 
PROCOPT	LXI	D,FCB+1
	LDAX	D
	STA	OPTION		;primary option stored in 'option'
	CPI	' '		;check if initial command-line option requested
	RZ			;blank --> no 'comm7.com' attribute entered
OPTLP	INX	D
	LDAX	D		; 1st secondary option into a-reg
	CPI	' '		;delimiter for filename-beginning flags..
	JZ	END$OPT		;..end of secondary option string. 
	LXI	H,OPTBL
	MVI	B,OPTBE-OPTBL	; (test each character over table length)
OPTCK	CMP	M
	JNZ	OPTNO

	 IF	PMMI OR MM100
	CPI	'O'		;originate mode?
	MOV	C,A		;preserve a-reg for 'a' test
	LDA	ORIGMOD		;get word structure and..
	JZ	OPTCK2		;branch if not
	MOV	A,C		;get a-reg for character test
	CPI	'A'		;answer mode?
	JNZ	OPTCK3		;no, neither mode requested.
	LDA	ANSWMOD		;yes, do..
	 ENDIF			;pmmi or mm100

	 IF	PMMI
OPTCK2	STA	UARTCTLB	;..store here.
	 ENDIF			;pmmi

	 IF	MM100
OPTCK2	STA	MODCTLB
	 ENDIF			;mm100

OPTCK3	MVI	M,0		;zero-out options requested in table
	JMP	OPTLP		;loop 'till done
	
OPTNO	INX	H		;see if..
	DCR	B		;..end..
	JNZ	OPTCK		;..of table string length.
	JMP	BADCMD		;option not valid, show message.

END$OPT	LDA	VSEEFLG		;if 'viewing', suppress..
	ORA	A		;..all..
	JNZ	SW3		;..but..
	STA	QFLG		;..ascii-file text.
SW3	LDA	OPTION		;check primary option
	CPI	'T'		;terminal mode
	RZ
	CPI	'E'		;return if echo (host computer) option
	RZ
	CPI	'M'		;return to menu
	RZ

	 IF	PMMI OR MM100 OR US100
	CPI	'C'		;go to call (telephone) library or..
	RZ
	CPI	'D'		;..disconnect the phone line
	RZ
	 ENDIF			;pmmi or mm100 or us100

	CPI	'S'		;send a file
	JZ	CKFILE
	CPI	'R'		;receive a file
	JNZ	BADCMD
	LDA	BATCHFLG	;if multi-file mode, receive..
	ORA	A		;..option doesn't need filename.
	RZ

; enter command again if no filename specified

CKFILE	LDA	FCB2+1		;option needs filename, check..
	CPI	' '		;..to see if name exists.
	RNZ			;if none, request to do everything over.
REENTER	CALL	ILPRTQ
	DB	ESC,ETEOP,'Filename required (S/R fn.ft), '
	DB	'<RETURN> continues: ',BELL,0
	LXI	D,CMDBUF
	CALL	INBUF		;wait for keyboard input
	LDA	CMDBUF+1	;return to command line..
	ORA	A		;..if only..
	JZ	MENU		;..a <return> entered.
	JMP	DOOPT		;loop back 'till good response entered

; t e r m i n a l   m o d e

; terminal routine allowing memory save

DSKSAVE	LDA	DIRECTB		;load direct bios flag
	STA	DTYPE		;and copy it into tempoary register
	LDA	LSTRETF		;was printer on before..
	ORA	A		;..entering command mode?
	JZ	NOLIST		;no, then branch. 
	XRA	A		;zero resets terminal return..
	STA	LSTRETF		;..flag for next time and sets..
	STA	LISTFLG		;..printer to list characters.
NOLIST	CALL	CROSSCK		;verify option compatibility
	LDA	NFILFLG		;if file open go..
	ORA	A		;..directly..
	JZ	TERM		;..to 'term' loop.
	LXI	H,FCB		;filename moved from fcb2 --> fcb by 'movefcb'
	MVI	B,11		;make sure..
CKOKLP	INX	H		;..no..
	MOV	A,M		;..wildcards..
	CPI	'?'		;..in filename.
	JZ	NGMSG		;if so, say so.
	DCR	B
	JNZ	CKOKLP		;if not, check..
	LDA	FCB+1		;..if a filename..
	CPI	' '		;..was declared.
	JNZ	GOODNM		;yes, name seems okay.
	JMP	TERM		; (no file being saved --> terminal mode)

CROSSCK	LDA	BATCHFLG	;make sure..
	ORA	A
	JZ	BADCMD
	LDA	QFLG		;..primary and secondary options..
	ORA	A
	JZ	BADCMD
	RET			;..are fully compatible.

NGMSG	CALL	ILPRT
	DB	ESC,ETEOP,BELL,' ++ WILDCARDS not allowed ++ ',0
	JMP	MSGREAD

GOODNM	CALL	ERASFIL		;see if file already exists.  erase?
	JNZ	MENU		;no, go to cmd mode.
	CALL	MOVE2		;shifts 1st 12 bytes of fcb to fcb3..
	LXI	D,FCB3		;..(fcb2 previously shifted to fcb).
	MVI	C,MAKE		;create and..
	CALL	BDOS
	LXI	D,FCB3
	MVI	C,OPEN		;..open new file.
	CALL	BDOS
	LXI	H,BOTTRAM	;start address for file-save
	SHLD	HLSAVE		; 'hlsave' --> last byte into file.
	XRA	A		;zero'd so a file can be toggled for save..
	STA	NFILFLG 	;..and 't' used for terminal return.

; main terminal loop with file-save/transfer, output to printer,
; 'softkey' and other capabilities

TERM	CALL	LISTST		;character for printer?
	CALL	STAT		;console keypress?
	JZ	TERML		;no, check line.
	CALL	KEYIN		;get char from kbd
	MOV	B,A		;save and..
	LDA	CMDFLG		;ignore next (ctrl) char locally..
	ORA	A	        ;..if zero flag not set.
	MVI	A,0		;clear for..
	STA	CMDFLG		;..next time.
	MOV	A,B		;..restore.
	JZ	CHROUT		;send character (control code out)

; check for in-line terminal commands

	 IF	TELE25TH	;others don't have 25th line
	PUSH	PSW		;save a-reg and flags
	CALL	ILPRT
	DB	ESC,'h',0	;turn off 25th line
	POP	PSW
	 ENDIF			;tele25th

	 IF	ZENITH
	PUSH	PSW
	CALL	ILPRT
	DB	ESC,'x1',0	;turn off 25th line
	POP	PSW
	 ENDIF			;zenith

	MOV	B,A
	LDA	CMDCHR		; get command character
	CMP	B		; check it out
	MOV	A,B
	JZ	CHROUT1
	CALL	UCASE		;convert to uppercase
	CPI	'E'		;  e = exit terminal mode
	JZ	ALERT		;      (see if save memory file is wanted)
	CPI	'T'		;  t = transfer a file
	CZ	TRANSFER
	JZ	TERM

	 IF	PMMI OR MM100
	CPI	'@'-40h		; ^@ = send a break (<break> on 'televideo'
	JZ	BREAK		;      equal to a ^@)
	 ENDIF			;pmmi or mm100

	 IF	PMMI OR MM100 OR US100
	CPI	'D'		;  d = disconnect and return to command mode
	JZ	DISCON1		
	CPI	'B'		;  b = change baud rate
	CZ	NEWBAUD		;get baudrate request (returns with..
	JZ	TERML		;..flags set)
	 ENDIF			;pmmi or mm100 or us100

	 IF	SOFTKEY
	CPI	'R'		;  r = display softkey table
	CZ	REVIEW		;      yes, show current string-storage.
	JZ	TERM		;      continue looping
	CPI	'0'		;  0 - 9 are soft keys
	JC	CHROUT		;      less than 0
	CPI	'9'+1		;
	CC	SOFTCMD		;yes, we have a soft key
	JZ	TERM		;      then return to term loop
	 ENDIF			;softkey

	CPI	'P'		;  p = test for printer list
	JNZ	SW6		;no, by-pass toggle.
	LDA	LISTFLG
	STA	LSTRETF		;set permanent toggle 'not'
	CMA
	STA	LISTFLG		;set tempoary toggle
	JMP	BELLOUT		;audible printer toggle

SW6	CPI	'S'		;  s = "colon-save"
	JNZ	CHROUT		;no toggle, it's a char to send.
	LDA	NFILFLG		;save not allowed if flag true
	ORA	A
	JNZ	TERML		;check line for another character
	MVI	A,TRUE
	STA	ALERTFG		; 1st time thru announces a save
	LDA	SAVEFLG
	CMA
	STA	SAVEFLG		;toggled by savechr
BELLOUT	LDA	BELLFLG		;do you want noisy bells?
	ORA	A
	JZ	TERML		;no, so don't beep at me
	MVI	A,BELL		;otherwise, start/stop printer/file-save..
	CALL	TYPE		;..with audible toggle.
	JMP	TERML		;ck for another char from remote

; exit character storage

EXTFLG	MVI	A,TRUE
	STA	CMDFLG
	JMP	TERML

; character-out through modem port

COMMAND	MVI	A,TRUE
	STA	CMDFLG		;command next

	 IF	TELE25TH OR ZENITH
	MVI	A,255		;turn off printer for a bit
	STA	LISTFLG
	 ENDIF			;tele25th or zenith

	 IF	TELE25TH
	CALL	ILPRT
	DB	ESC,'f'		;turn on 25th line
	 ENDIF

	 IF	ZENITH
	CALL	ILPRT
	DB	ESC,'j',ESC,'x1',ESC,'Y8 '	;turn on 25th line
	 ENDIF

	 IF	TELE25TH OR ZENITH
;		'----5---10---15---20---25---30---35---40'
	DB	'B>aud | D>isc | E>xit | P>rint | R>eview'
	DB	' | S>ave | T>ransfer | <break> | (0-9)'
	 ENDIF			;tele25th or zenith

	 IF	TELE25TH	;restore cursor
	DB	CR,ESC,'g',0
	 ENDIF			;tele25th

	 IF	ZENITH
	DB	ESC,'Y  ',ESC,'k',0	;restore cursor
	 ENDIF			;zenith

	 IF	TELE25TH OR ZENITH
	LDA	LSTRETF		;get permanent list flag
	CMA			;flip it
	STA	LISTFLG		;and restore the printer's condition
	 ENDIF			;tele25th or zenith

	JMP	TERML		;print message on 25th line

; character-out through modem port -- full/half-duplex option from 'sel'

CHROUT	MOV	B,A
	LDA	CMDCHR		;get command character
	CMP	B		;check it out
	MOV	A,B
	JZ	COMMAND		;it was a command, so show menu
CHROUT1	CALL	OUTCHAR
	MOV	B,A
	LDA	HALFDUP		;if half-duplex..
	ORA	A
	MOV	A,B
	CNZ	TYPE		;..show character locally.

; character input from telephone line -- filter out control codes option

TERML	CALL	INSTAT
	JNZ	TERM
	CALL	INCHAR
	ANI	7FH		;strip parity
	JZ	TERM		;don't process nulls
	MOV	B,A
	LDA	FILBYTE		;filter control codes out?
	ORA	A
	MOV	A,B
	JZ	TERML1		;no, process them.
	CPI	' '
	JNC	TERML1
	CPI	BS		;print backspace,..
	JZ	TERML1
	CPI	HT		;..horizontal tab..
	JZ	TERML1
	CPI	CR		;..carriage return..
	JZ	TERML1
	CPI	LF		;..and line feed.
	JNZ	TERM
TERML1	CALL	TYPE		;print locally
	MOV	B,A		; save line character and..
	LDA	SAVEFLG
	ORA	A
	JZ	TERM		;branch if no-file-save
	LHLD	HLSAVE		;get address of last saved character
	MOV	M,B		;last character copied into ram, pointed..
	INX	H		;..to by 'hlsave-1'.
	SHLD	HLSAVE
	MOV	A,B		; ..put back here.
	CPI	LF		; lf?  if yes,..
	JNZ	NOCOLON		;..type ":" after each line feed..
	MVI	A,':'		;..when memory save active.  (don't put into..
	CALL	TYPEQ		;..colon-save file or send to printer.)
NOCOLON	LDA	SAVCCP
	ORA	A
	LDA	BDOS+2		;check to see if..
	JZ	SUB1
	SUI	CCP+RING	;..page below 'ccp+ring'..
SUB1	DCR	A		;..or 'bdos' has been..
	CMP	H		;..reached and disksave is needed.
	JNZ	TERM		;no, continue looping.  else fall-thru.

; ramsave:  routine automatically stops remote computer when colon-save buffer
; is full and writing to disk is required.  after 'xoff' is sent, up to 'rbuf'
; characters are saved, if necessary, waiting for remote computer to stop
; sending.  if remote never stops, characters are lost during auto-write to
; disk.

RAMSAVE	MVI	A,XOFF		;send a <ctrl-s>, 'xoff', to stop..
	CALL	OUTCHAR		;..remote computer sending.
	MVI	D,RBUF		;initialize for maximum ram buffer length
	CALL	INMODEM		;get characters received after sending 'xoff'
	PUSH	D		;protect d-reg counter
	CALL	WRTDSK		;write 'colon-save' memory to disk
	POP	D		;counter restore
	LXI	H,BOTTRAM	;reset 'colon-save' memory and..
	LXI	B,LASTBYTE	;..buffer areas.
TORAMLP INR	D		;bump 'till..
	MOV	A,D		;..at..
	CPI	RBUF+1		;..maximum.
	JZ	CTRLQ		;branch if at limit
	LDAX	B		;get character from buffer and..
	MOV	M,A		;..put in 'colon-save' memory area.
	INX	H		;ready for..
	INX	B		;..another character.
	CALL	TYPE		;show locally
	JMP	TORAMLP		;continue 'till d-reg tops out

CTRLQ	SHLD	HLSAVE		; 'last character + one' position
	MVI	A,XON		;restart remote computer
	CALL	OUTCHAR
	JMP	TERM		;continue terminal looping

INMODEM	LXI	B,LASTBYTE	;set pointer to buffer begin-address
	PUSH	B
INMDM	LXI	B,104*MHZ	;constand for 100 msec wait
DELAYLP	CALL	INSTAT		;character from line?  if yes, store..
	JZ	GETBYTE		;..received character after 'xoff' sent.
	DCX	B
	MOV	A,B
	ORA	C
	JNZ	DELAYLP		;loop 'till time-out
	POP	B		;restore stack
	RET			;return if remote computer stops sending

GETBYTE	CALL	INCHAR
	POP	B		;get pointer to 'lastbyte' buffer and..
	STAX	B		;..store character there.
	INX	B		;increment pointer and..
	PUSH	B		;..save if a next time thru occurs.
	DCR	D		;bump character count.  at zero yet?
	JNZ	INMDM		;get more, if not.
	POP	B		;restore stack
	RET			;return if buffer full

; routine to send 'break' (null) code -- used by to reboot some remotes

	 IF	PMMI
BREAK	LDA	MODCTLB		;get modem control byte
	ANI	BRK		;set break bit low (active)
	 ENDIF			;pmmi

	 IF	MM100 OR US100
BREAK	LDA	MODCTLB		;get modem control byte
	ORI	BRK		;set break bit hi (active)
	 ENDIF			;mm100 or us100

	 IF	PMMI OR MM100 OR US100
	CALL	OUTCTR2	 	;send to modem
	LXI	B,104*MHZ   	;constant for 100 msec wait
BRKLP	CALL	INSTAT
	JZ	FETCHEM
	DCX	B
	MOV	A,B
	ORA	C
	JNZ	BRKLP
	LDA	MODCTLB		;restore previous..
	CALL	OUTCTR2		;..modem condition if time-out.
	LHLD	HLSAVE		;last address written if colon-save active
	LDA	SAVCCP
	ORA	A
	LDA	BDOS+2		;check to see if..
	JZ	SUB2
	SUI	CCP+RING	;..page below 'ccp+ring'..
SUB2	DCR	A		;..or 'bdos' has been..
	CMP	H		;..reached and disksave is needed.
	JNZ	TERM		;if a-reg not equal to h-reg continue
	CALL	ILPRT		;if equal, show msg and continue.
	DB	CR,LF,'++ File buffer full ++',CR,LF,BELL,0
	JMP	TERM

FETCHEM	CALL	INCHAR
	ORA	A		;check for nulls (cpi 0) but..
	JZ	BRKLP		;..don't process them.
	ANI	7FH		;strip parity
	CALL	TYPE
	MOV	B,A		;copy line character here..
	LDA	SAVEFLG
	ORA	A
	JZ	BRKLP
	LHLD	HLSAVE		;get last address of ram-saved character
	MOV	M,B		;..and here..
	INX	H		; update hl-pair, char by char..
	SHLD	HLSAVE		; ..and store in 'hlsave'.
	MOV	A,B		;..and put back here.
	CPI	LF		;lf?
	JNZ	BRKLP		;yes, type ":" after each line feed..
	MVI	A,':'		;..when memory-save active.
	CALL	TYPE
	JMP	BRKLP
	 ENDIF			;pmmi or mm100 or us100

; write ram to disk

; determine # of records in file

RAMDISK	LHLD	HLSAVE		;get 'colon-save' file-end address and..
	MVI	M,EOFCHAR	;..put cp/m terminator there.  then..
	INX	H		;..lengthen thru one record,  making..
	LXI	D,127		;..sure file-bytes..
	DAD	D		;..aren't chopped.
WRTDSK	LXI	D,-(BOTTRAM)	;subtract by adding 2's complement to..
	DAD	D		;..'hlsave' --> total bytes in ram-file.
	MVI	B,7+1		;set to divide total by 128 and return..
	CALL	SHIFTLP		;..with # of records in hl-pair.
	LDA	WRT$FLG		;allow zero-length if..
	ORA	A		;..at least..
	JZ	NEXTWRT		;..one buffer full..
	XRA	A		;..has been written.
	STA	WRT$FLG

; write records to disk

	LXI	D,BOTTRAM
	LDAX	D		;don't save zero-length..
	CPI	EOFCHAR		;..file.  delete..
	JZ	ZEROMSG		;..filename from directory.
NEXTWRT	MVI	C,SETDMA
	CALL	BDOSRET
	PUSH	D
	LXI	D,FCB3
	MVI	C,WRITE
	CALL	BDOSRET
	POP	D
	XCHG			; (if you understand these..
	PUSH	D
	LXI	D,128		;..lines, consider..
	DAD	D
	POP	D		;..yourself..
	XCHG
	DCX	H		;..a cp/m assembly..
	MOV	A,H
	ORA	L		;..language programmer.)
	JNZ	NEXTWRT		;branch if not done
	RET

; zero-length file announcement and filename deletion

ZEROMSG CALL	ILPRT
	DB	CR,ESC,ETEOP,'---> Zero-length file deleted ',0
	MVI	B,20
	CALL	TIMER
	JMP	DELNEWF

; close file at fcb3 -- bdos call with all registers saved and restored

CLOSE3	LXI	D,FCB3
	MVI	C,CLOSE
BDOSRET	PUSH	PSW
	PUSH	B
	PUSH	D
	PUSH	H
BDOSV	CALL	BDOS
	POP	H
	POP	D
	POP	B
	POP	PSW
	RET

; file transfer subroutine -- called with <ctrl-t> from terminal
; routine.  transfer cancelled while sending by using <ctrl-x>.
; choice of blind or protocol-send into receiving line-editor.  file
; may be transfered while another is being saved.

; transfer fcb set-up

TRANSFER LXI	H,FCB4		;hl-pair points to fcb..
	 CALL	INITFCB	 	;..to be intialized.

; process name of file (fn.ft) to transfer

GETFNFT	MVI	A,TRUE		;turn off printer for a bit
	STA	LISTFLG
	CALL	ILPRT

	 IF	TELE25TH
	DB	ESC,'g',ESC,'f'
	 ENDIF			;tele25th

	 IF	NOT TELE25TH
	DB	CR,LF
	 ENDIF			;not tele25th

	DB	'Enter filename to transfer, '
	DB	'<return> to terminal mode: ',0
	LXI	D,CMDBUF
	CALL	INBUF
	LDA	CMDBUF+2	;was file entered?
	CPI	' '
	JZ	RETURN

; set-up to transfer file bytes

	LXI	D,CMDBUF	;move filename to..
	LXI	H,FCB4		;..fcb4..
	CALL	CMDLINE		;..and..
	LXI	D,FCB4		;..open file..
	MVI	C,OPEN		;..for..
	CALL	BDOS		;..reading.
	INR	A		; 0ffh --> 0 if no file found
	JNZ	CONTIN0		;continue, file found.
TRANSL1 CALL	ILPRT
	DB	CR,LF,'++ Unable to find file ++',CR,LF,0
	JMP	RETURN

; choice of blind-send or protocol-send

CONTIN0	CALL	ILPRT

	 IF	TELE25TH
	DB	ESC,'f'
	 ENDIF		;tele25th

	 IF	NOT TELE25TH
	DB	CR,LF
	 ENDIF		;not tele25th

	DB	'Delay between characters/lines sent?  (Y/N): ',0
	CALL	RESPOND
	CPI	'N'
	JZ	CONTIN
	XRA	A		;zero line-editor flag
	STA	LNEDFLG

CONTIN	 IF	TELE25TH
	CALL	ILPRT		;turn off 25th line before printer goes on
	DB	ESC,'h',0
	 ENDIF			;tele25th

	LDA	LSTRETF		;get printer flag
	CMA			;flip it
	STA	LISTFLG
	CALL	ILPRT		;a fresh line before..
	DB	CR,LF,LF,0	;..reading file..
	LXI	D,TBUF		;..and away we go.
	MVI	C,SETDMA
	CALL	BDOS

; get 128-byte data record

READMR	LXI	D,FCB4
	MVI	C,READ		;read 128 bytes
	CALL	BDOS
	ORA	A		; eof?
	JZ	SENDMR		;no, branch.
RETURNS	CALL	ILPRT		;yes, show msg:
	DB	CR,LF,'---> Transfer completed',CR,LF,BELL,0
	JMP	RETURN

SENDMR	CALL	SEND80C
	CPI	EOFCHAR		; eof?
	JZ	RETURNS		;return if finished
	CPI	CAN		; (^x) cancellation?
	JNZ	READMR		;loop till 'eof' or <ctrl-x>
	CALL	ILPRT
	DB	CR,LF,'++ Transfer cancelled ++',CR,LF,0

RETURN
	 IF	TELE25TH	;force printer off, turn off 25th line.
	MVI	A,TRUE		;turn off printer if it was on
	STA	LISTFLG
	CALL	ILPRT		;turn off 25th line
	DB	ESC,'h',0
	 ENDIF			;tele25th

	LDA	LSTRETF		;make sure printer is in right state
	CMA
	STA	LISTFLG
	MVI	A,TRUE		;sequential transfer default condition
	STA	LNEDFLG
	XRA	A		;return with zero flag set
	RET			;xfer finished, continue loop thru 'term'.

; send 128-byte record

SEND80C	MVI	A,80H		;send 128 bytes
	STA	CHARCNT		;count characters
	LXI	H,TBUF		;points to cp/m 'tbuf'
SENPLHR	LDA	LNEDFLG		;is it line-editor prompted mode?
	ORA	A
	JZ	EDIT		;yes..
	MOV	A,M		;..no, straight blind-send mode.
	CALL	MODOUT
	RZ			;to 'sendmr' for finish
GETNEXT	CALL	STAT		;test for keypress
	JZ	SKIP12
	CALL	KEYIN
	CPI	CAN		; (^x) cancellation character
	RZ			;to readmr for cancel and exit
SKIP12	INX	H		;next character to send pointed to by..
	LDA	CHARCNT
	DCR	A		;..bumping hl-pair, count in charcnt.
	STA	CHARCNT
	JNZ	SENPLHR		;get next character
	RET			;to 'cpi eofchar' in 'sendmr' for 128 bytes

; blind-send to line-editor (dumb protocol)
; sends at 85 words per minute and delays at end-of-line

	 IF	DUMB
EDIT	MOV	A,M
	CPI	LF
	JZ	PROMPT
	CALL	MODOUT
	RZ
	LXI	B,375*MHZ	;delay..
CHRLOOP	DCX	B
	MOV	A,B		;..between..
	ORA	C
	JNZ	CHRLOOP		;..characters.
	JMP	GETNEXT		;get next to send

PROMPT	CALL	MODOUT		;send 'lf' to remote
	MVI	B,7		;delay at line-end after sending a 'cr'
	CALL	TIMER		; 100 msec in b-reg
	JMP	GETNEXT

; blind-send to remote (no protocol)

MODOUT	PUSH	PSW		;save send-character in a-reg 
	PUSH	H		;protect 'tbuf' pointer
	CALL	LISTST		;character for printer?
	POP	H
MODLP	CALL	OUTSTAT		;test if ready to send
	JNZ	MODLP		; (out at baudrate)
	POP	PSW		;character back into a-reg
	CALL	OUTCHAR		;char out modem
	CPI	EOFCHAR		;if (^z) don't send to..
	RZ			;..terminal, just return.  if..
	JMP	TYPE		;..not, print locally.
	 ENDIF			;dumb

; (experimental)
; smart-protocol send/receive to line-editor (receives echo before next
; send).  checks for bell near, and cursor return at, end-of-line.  waits
; for remote prompt(s) to be received before continuing send.

	 IF	NOT DUMB
EDIT	MOV	A,M		;char to send into a-reg
	CPI	CR		;end of line?
	JZ	WAITFOR		;yes, get delay, else..
	CALL	MODOUT2		;..send chararcter out modem.
	CPI	EOFCHAR		;if eof then..
	RZ			;..finish up.
	JMP	GETNEXT		;get another character to send

; wait for remote prompt(s) to be sent

WAITFOR	CALL	MODOUT2
	MVI	B,10		;wait window for bbs prompt(s)
	CALL	TIMER		; (100 msec timer)
	CALL	CRLF		; (crlf not received as echo)
	JMP	GETNEXT		;get another character

; smart-send/receive to/from remote (special protocol)

MODOUT2	CALL	OUTCHAR		;character out
	CPI	EOFCHAR		;eof?
	RZ			;yes, don't type.  ret & finish up.
MODLP2	CALL	INSTAT
	JNZ	MODLP2		;(in at baudrate)
	CALL	INCHAR		;get echo
	ANI	7FH		;strip parity
	RZ	      		;don't process null
	CALL	TYPE		;if not null, show locally.
 	CPI	BELL		;bell?  if yes, delay till remote..
	JZ	DELAY07		;..is able to receive next char else..
	RET			;..ret and send next character.

; delay sending after receiving bell (07H)

DELAY07	MVI	B,2		; 200 msec wait before sending next..
	CALL	TIMER		;..character after near-end-of-line bell
	RET			;to caller of modout2
	 ENDIF			;not dumb

; softkey routine outputs stored text by pressing the lead-in
; character followed by a single numeral, 0,1,2,3, etc.  see skone
; db fields before start of program for the command and message
; strings (alter them as desired).  <lead-in> d displays the fields
; locally while in terminal mode.

	 IF	SOFTKEY
SOFTCMD	SUI	30H		;ok, now convert ascii digit to binary.
	RLC			; *2 for table offset (0,1,2... --> 0,2,4...)
	LXI	H,SKTBL		;point to softkey table and.
	MOV	E,A		;..index down, factor in a-reg.
	MVI	D,0		;clear msb
	DAD	D		;add offset to table beginning address
	MOV	E,M		;get dw address in table
	INX	H		;fully fill de-pair
	MOV	D,M		;done, next..
	XCHG			;..point to desired db string in table.
KEYSEND	MOV	A,M		;put character in a-reg for sending
	ORA	A		;ck for null terminator
	RZ
	CALL	OUTCHAR		;send character to remote
	LXI	B,400*MHZ
MDMINLP	CALL	INSTAT		;test if echo character received
	JZ	GETCHR		;if not, delay..
	DCX	B		;..and..
	MOV	A,B		;..try..
	ORA	C		;..again..
	JNZ	MDMINLP		;..and again 'till timeout or char rcvd.
	RET			;ret to terminal loop

GETCHR	CALL	INCHAR		;character received in a-reg
	ANI	7FH		;strip parity
	JZ	NULSKIP		;don't process if null
	CALL	TYPE		;display locally
NULSKIP	INX	H		;get next character to be sent
	JMP	KEYSEND		;loop till terminator

; softkey dispatch table

SKTBL	DW	SKZERO		;address of db text strings
	DW	SKONE
	DW	SKTWO
	DW	SKTHREE
	DW	SKFOUR
	DW	SKFIVE
	DW	SKSIX
	DW	SKSEVEN
	DW	SKEIGHT
	DW	SKNINE		; 0 to 9 presently permitted
	 ENDIF			;softkey

; e c h o   m o d e   (resemble a computer)

TERM$ECHO CALL	CROSSCK		;comfirm option compatibility
ECHOLP	CALL	INSTAT
	JZ	LINECHR
	CALL	STAT
	JZ	ECHOLP
	CALL	KEYIN
	MOV	B,A		;save character
	LDA	CMDFLG		;ignore next (ctrl) char locally..
	ORA	A		;..if zero flag not set.
	MVI	A,0
	STA	CMDFLG		;clear for next time
	MOV	A,B		;restore character
	JNZ	ECHOCMD		;we are in command mode, check for command.
	LDA	CMDCHR		;get command character
	CMP	B
	MOV	A,B		;put character back into A
	JNZ	SW7		;not command character, so send it out.
	ORI	TRUE		;set command mode
	STA	CMDFLG
	JMP	ECHOLP		;go back to main echo loop

LINECHR	CALL	INCHAR
SW7	CPI	CR		;if cursor return..
	JNZ	SW7A
	CALL	OUTCHAR
	CALL	TYPE		;print locally
	MVI	A,LF		;..auto-output a linefeed.
SW7A	CALL	OUTCHAR
	CALL	TYPE
	JMP	ECHOLP		;loop for more

ECHOCMD	CALL	UCASE
	CPI	'E'		;exit to (re-enter) command mode
	JZ	MENU

	 IF	PMMI OR MM100 OR US100
	CPI	'B'		;same routines as in 'terminal mode'
	CZ	NEWBAUD
	JZ	ECHOLP
	CPI	'D'		;disconnect?
	JZ	DISCON1
	 ENDIF			;pmmi or mm100

	JMP	SW7		;not a valid command, so send it

; s e n d

SENDFIL	 MVI	A,'C'		;always force checksum..
	 STA	CRCFLG		;..mode initially on send.
SENDFIL1 LDA	BATCHFLG	;check if multiple file..
	 ORA	A		;..(batch) mode is set.
	 JNZ	SENPLF
	 CALL	ILPRTQ
	 DB	CR,LF,'Ready to send -- batch mode',0
SENDFIL2 MVI	A,TRUE		;indicate 'batch' send mode
	 STA	SENDFLG
	 LDA	FSTFLG		;if first time thru..
	 ORA	A		;..scan the command line..
	 CNZ	TNMBUF		;..for multiple names.
	 CALL	SENDFN		;sends filename to receiver
	 JNC	SENPLF		;carry set means no more files
	 MVI	A,'B'		;stop batch..
	 STA	BATCHFLG	;..mode secondary option.
	 MVI	A,EOT		;final file transfer indication
	 CALL	SEND
	 JMP	DONE

SENPLF	CALL	CNREC		;get number of records
	CALL	OPENFIL
	MVI	E,90		;waiting time-out
	CALL	WAITNAK
SENDLP	CALL	CKABORT		; (manual abort?)
	CALL	RDRECD		;read a record
	JC	SENDEOF		;send 'eof' if done
	CALL	INCRRNO		;bump count
	MVI	A,1             ;initialize error count for 1st error
	STA	ERRCT
SENDRPT	CALL	CKABORT		;want to quit sending?
	CALL	SENDHDR		;send a header
	CALL	SENDREC		;send bytes of record
	LDA	CRCFLG
	ORA	A
	CZ	SENPLRC		;send phone line crc or..
	CNZ	SENPLKS		;..cksum.
	CALL	GETACK		;get the ack.  rets with carry..
	JC	SENDRPT		;..set if no ack received.
	JMP	SENDLP		;loop till 'eof'

SENDEOF	MVI	A,EOT
	CALL	SEND		;send an 'eot'
	CALL	GETACK
	JC	SENDEOF		;loop if no ack
	JMP	DONE		;to 'finish up' routine

; r e c e i v e

; initial entry routine

RCVFIL	XRA	A		;set initially..
	STA	CRCFLG		;..to 'crc' mode
RCVFIL1	LDA	BATCHFLG	;check if mult-file..
	ORA	A		;..(batch) mode.
	JNZ	RCVC3
	XRA	A		;flag --> 'batch' receive..
	STA	SENDFLG		;..for next file transfer.
	CALL	GETFN		;get filename
	JNC	RCVC2		;carry set means no more files
	MVI	A,'B'		;stop batch..
	STA	BATCHFLG	;..mode option.
	JMP	DONE		;finish

RCVC2	CALL	CKCPM2
	CALL	CKBAKUP
RCVC3	CALL	ERASFIL		;if file exists -- erase.  else use..
	JNZ	MENU		;..different filename, as desired.
	CALL	MAKEFIL
	LDA	BATCHFLG
	ORA	A
	JZ	RCVFST		;no msg if in 'batch' mode..
	CALL	ILPRTQ		;..but shows in 'quiet'.
	DB	CR,LF,'File open -- ready to receive',0
RCVFST	LDA	CRCFLG		;determine mode and..
	ORA	A
	JNZ	RCVFIL2		; (do 'checksum')
	CALL	ILPRTQ		;..if 'crc', say:
	DB	CR,LF,'CRC in effect',CR,LF,0
	MVI	A,CRC
	JMP	RCVCRCM		;store 'c' (or 'nak')

RCVFIL2	CALL	ILPRTQ		;else show this msg then..
	DB	CR,LF,'Checksum in effect',CR,LF,0
	MVI	A,NAK		;..store..
RCVCRCM	STA	ACKFLG		;..'nak'.

; main receive loop -- update record count, write, and close file

RCVLP	CALL	RCVRECD		;get a record
	JC	RCVEOT		;get 'eot'
	CALL	WRRECD		;write the record
	CALL	INCRRNO		;bump count
	MVI 	A,ACK
	STA	ACKFLG		;store 'ack'
	JMP	RCVLP		;loop till 'ack'

RCVEOT	CALL	WRBLOCK		;write last record
	CALL	SENDACK		;send 'ack'
	CALL	CLOSFIL		;close file
	JMP	DONE		;finished
	
; subroutines used by batch mode

; await 'nak' -- send 'ack'

SENDFN	CALL	ILPRT		;don't show in quiet mode
	DB	CR,LF,'Awaiting filename NAK',CR,LF,0
	MVI	E,90
	CALL	WAITNLP		; (don't show 'awaiting ready signal')
	MVI	A,ACK		;got nak, send ack.
	CALL	SEND
	LXI	H,FILECT
	DCR	M
	JM	NOMRNM
	LHLD	NBSAVE		;get filename..
	LXI	D,FCB		;..into fcb.
	MVI	B,12
	CALL	MOVE
	SHLD	NBSAVE
	CALL	SENDNM		;send it
	ORA	A		;clear carry
	RET

NOMRNM	MVI	A,EOT
	CALL	SEND
	STC
	RET

; send filename

SENDNM	PUSH	H
SENDNM1	MVI	D,11		;count chars in name
	MVI	C,0		;init checksum
	MOV	A,C
	STA	FTYCNT		;initialize filetype count
	LXI	H,FCB+1		;address name
NAMLPS	MOV	A,M		;send name
	ANI	7FH		;strip high order bit so cp/m 2..
	CALL	SEND		;..won't send r/o file designation.
	LDA	QFLG		;don't show filename..
	ORA	A		;..if in 'quiet' mode.
	MOV	A,M
	CNZ	FTYTST		;type locally filename characters
ACKLP	PUSH	B		;save cksum
	MVI	B,10		;wait 1 sec for receiver..
	CALL	RECV		;..to acknowledge..
	POP	B		;..getting letter.
	JC	SCKSER		;cksum error --> time-out on receive
	CPI	ACK
	JNZ	ACKLP
	INX	H		;bump for next character
	DCR	D
	JNZ	NAMLPS
	MVI	A,EOFCHAR 	;tell receiver end of name
	CALL	SEND
	CALL	CRLF		; (doesn't show in quiet mode)
	MOV	D,C		;save checksum
	MVI	B,10		; 1-sec time-out
	CALL	RECV		;get checksum..
	CMP	D		;..from receiver.
	JZ	NAMEOK
SCKSER	MVI	A,BDNMCH 	;bad name, tell receiver.
	CALL	SEND
	CALL	ILPRTQ
	DB	CR,LF,'++ Filename error ++',CR,LF,0
	MVI	E,90		;do handshaking over
	CALL	WAITNLP		;don't print 'awaiting filename nak' msg
	MVI	A,ACK
	CALL	SEND
	JMP	SENDNM1		;re-send filename

NAMEOK	MVI	A,OKNMCH 	;successful filename sent and received,..
	CALL	SEND		;..tell receiver.
	POP	H
	RET	

GETFN	LXI	H,FCB
	CALL	INITFCB+2 	;does not initialize drive
	CALL	ILPRTQ
	DB	CR,LF,'Ready to receive filename',CR,LF,0
GNAMELP	MVI	E,45		; 90-seconds for receiving 'ack'
	CALL	HSNAK		;get 'ack' or auto-abort
	CALL	GETNM		;get the name
	CPI	EOT		;if eot, then no more files.
	JZ	NOMRNMG
	ORA	A		;clear carry
	RET

NOMRNMG	STC
	RET

GETNM	PUSH	H
GETNM1	MVI	C,0		;initialize checksum
	MOV	A,C
	STA	FTYCNT		;initiate count for filetype
	LXI	H,FCB+1
NAMELPG	MVI	B,50
	CALL	RECV		;get char (5-sec time-out)
	JNC	GETNM3
	CALL	ILPRTQ
	DB	CR,LF,'Timeout receiving filename',CR,LF,0
	JMP	GCKSER

GETNM3	CPI	EOT		;if 'eot', then no more files.
	JZ	GNRET
	CPI	EOFCHAR		;got end of name
	JZ	ENDNAME
	MOV	M,A		;put name in fcb
	CALL	FTYTST		;show locally received filename
	PUSH	B		;save cksum
	MVI	A,ACK		; 'ack' getting letter
	CALL	SEND
	POP	B
	INX	H		;get next char
	MOV	A,L		;don't let noise...
	CPI	7FH		;..cause overflow..
	JZ	GCKSER		;..into program area.
	JMP	NAMELPG

; show locally filename sent/received

FTYTST	LDA	FTYCNT		;filetype counter
	INR	A
	STA	FTYCNT
	CPI	8+1		;into filetype yet?
	JZ	SPCTST		;go if so
ENDSPT	MOV	A,M
	CPI	' '		;test for space
	CNZ	TYPEQ		;type if not
	RET

SPCTST	MOV	A,M		; 'space' test
	CPI	' '		;test for space in first filetype byte
	RZ			;don't output period if space
	MVI	A,'.'
	CALL	TYPEQ
	JMP	ENDSPT		;output first filetype byte

; verify 'goodname' recevied after sent

ENDNAME	CALL	CRLF		; (not shown in quiet mode)
	MOV	A,C		;send checksum
	CALL	SEND
	MVI	B,10		; 1-sec time-out
	CALL	RECV	   	;checksum good?
	CPI	OKNMCH	   	;yes, if oknmch sent..
	JZ	GNRET	   	;..else do over.
GCKSER	LXI	H,FCB	   	;clear fcb (except drive) since it can be..
	CALL	INITFCB+2 	;..changed by too many characters.
	CALL	ILPRTQ
	DB	CR,LF,'++ Filename error ++',CR,LF,0
GCKSER1	MVI	E,45
	CALL	HSNAK		;do handshaking over
	JMP	GETNM1

GNRET	POP	H
	RET

; handshake 'nak'/'ack'

HSNAK	MVI	A,NAK		;send nak until receiving..
	CALL	SEND		;..'ack' or e-reg times out.
	CALL	CKABORT		; (manual abort?)
	MVI	B,20		;wait 2 seconds..
	CALL	RECV		;..in receive.
	CPI	ACK		;if ack, then..
	RZ			;..return.
	DCR	E		;time-out in e-reg
	JNZ	HSNAK		;loop 'till time-out and..
	JMP	ABORT		;..then abort.

; load 'namebuf' with filename(s) to send

TNMBUF	XRA	A		;call from 'sendfil' only once.
	STA	FSTFLG
	STA	FILECT
	CALL	SCANM
	LXI	H,NAMEBUF
	SHLD	NBSAVE		;save addr of 1st name
TNLP1	CALL	TRTOBUF
	LXI	H,FCB
	LXI	D,FCBBUF
	CALL	CMDLINE		;parse name to cp/m format
TNLP2	CALL	MFNAME		;search for names (* format)
	JC	NEXTNM
	LDA	FCB+10		;if cp/m 2 $sys file..
	ANI	80H		;..don't send.
	JNZ	TNLP2
	LHLD	NBSAVE		;get name
	LXI	D,FCB		;move it to fcb
	XCHG
	MVI	B,12
	CALL	MOVE
	XCHG
	SHLD	NBSAVE	 	;addr of next name
	LXI	H,FILECT 	;count files found
	INR	M
	JMP	TNLP2

NEXTNM	LXI	H,NAMECT 	;count names found
	DCR	M
	JNZ	TNLP1
	LXI	H,NAMEBUF 	;save start of buffer
	SHLD	NBSAVE
	LDA	FILECT
	CPI	64+1		;no more than 64 transfers
	RC
	MVI	A,64		;only x'fer first 64
	STA	FILECT
	RET

; scan cmdbuf, counts names, and puts "space" after last name

SCANM	PUSH	H
	LXI	H,NAMECT
	MVI	M,0
	LXI	H,CMDBUF+1 	;find end of cmd line..
	MOV	C,M		;..and put space there.
	MVI	B,0
	LXI	H,CMDBUF+2
	DAD	B
	MVI	M,' '		;space
	LXI	H,CMDBUF+1
	MOV	B,M
	INR	B
	INR	B
SCANLP1	INX	H
	DCR	B
	JZ	DNSCAN
	MOV	A,M
	CPI	' '
	JNZ	SCANLP1
SCANLP2	INX	H		;eat extra spaces
	DCR	B
	JZ	DNSCAN
	MOV	A,M
	CPI	' '
	JZ	SCANLP2
	SHLD	BGNMS		;save start of names in cmdbuf
	INR	B
	DCX	H
SCANLP3	INX	H
	DCR	B
	JZ	DNSCAN
	MOV	A,M
	CPI	' '
	JNZ	SCANLP3
	LDA	NAMECT		;counts names
	INR	A
	STA	NAMECT
SCANLP4	INX	H		;eat spaces
	DCR	B
	JZ	DNSCAN
	MOV	A,M
	CPI	' '
	JZ	SCANLP4
	JMP	SCANLP3

DNSCAN	MVI	M,' '		;space after last char
	POP	H
	RET

; place next name in buffer so cpmline may parse it

TRTOBUF	LHLD	BGNMS
	MVI	B,0
	LXI	D,FCBBUF+2
TBLP	MOV	A,M
	CPI	' '		;space
	JZ	TRBFEND
	STAX	D
	INX	H
	INX	D
	INR	B		;count chars in name
	JMP	TBLP

TRBFEND	INX	H
	MOV	A,M		;eat extra spaces
	CPI	' '
	JZ	TRBFEND
	SHLD	BGNMS
	LXI	H,FCBBUF+1 	;put # chars before name
	MOV	M,B
	RET

; $r/o and $sys files changed to 'bak'

CKCPM2	LXI	D,TBUF		;establish 'dma'
	MVI	C,SETDMA
	CALL	BDOS
	LXI	D,FCB
	MVI	C,SRCHF		;search for file
	CALL	BDOS
	INR	A		; 0ffh --> 0 means no file found
	RZ
	CALL	GETADDR		;returns filename address in hl-pair
	DCX	H
	LXI	D,9
	DAD	D		;point to 'r/o' attribute byte
	MOV	A,M
	ANI	80H		;test msb
	JNZ	MKCHG		;if set, make change.
	INX	H		;check 'system' attribute byte
	MOV	A,M
	ANI	80H
	RZ			;not $sys or $r/o
	DCX	H
MKCHG	LXI	D,-8
	DAD	D		;point hl-pair to filename+1
	LXI	D,FCB+1		;move directory name to fcb..
	MVI	B,11		;..without changing drive.
	CALL	MOVE
	LXI	H,FCB+9		; $r/o attribute is..
	MOV	A,M
	ANI	7FH		;..stripped here and..
	MOV	M,A
	INX	H		;..$sys attribute is..
	MOV	A,M
	ANI	7FH		;..stripped here.
	MOV	M,A
	LXI	D,FCB
	MVI	C,30		;set new file attributes in directory
	CALL	BDOS
	JMP	PLANCHG

; check if backup required and change filetype to 'bak'

CKBAKUP	LDA	BAKUPBYTE
	ORA	A
	RZ
	MVI	C,SRCHF
	LXI	D,FCB
	CALL	BDOS
	INR	A		; 0ffh --> 0 means..
	RZ			;..file not found.
PLANCHG	LXI	H,FCB		;change name to type 'bak'
	LXI	D,FCB2
	MVI	B,9		;copy drive and filename (not filetype)
	CALL	MOVE
	LXI	H,FCB2+9	;start of filetype in fcb2
	MVI	M,'B'
	INX	H
	MVI	M,'A'
	INX	H
	MVI	M,'K'
	LXI	D,FCB2
	MVI	C,ERASE		;erase existing backup, if one.
	CALL	BDOS
	LXI	H,FCB2		;fcb2 'dr' field should..
	MVI	M,0		;..be 0 for rename.
	LXI	D,FCB
	MVI	C,REN		;rename the file
	JMP	BDOS		;ret to caller

; receive 'cp/m file' routines

; receive a record

RECVACK	MVI	A,ACK
	STA	ACKFLG		;store 'ack' first
RCVRECD MVI	A,1		;initialize error count for 1st error
	STA	ERRCT
RCVRPT	XRA	A		;clear receive..
	STA	ERRCDE		;..error code.
	CALL	CKABORT		;ck keyboard for abort request
	CALL	ILPRT
 	DB	CR,'Awaiting #',0
	PUSH	H		;save it
	LHLD	RECDNO		;get record number
	INX	H		;bump it
	CALL	DECOUT		;print record number in decimal
	CALL	ILPRT
	DB	' (', 0
	CALL	DHXOUT		; 16-bit hex conversion & output
	CALL	ILPRT
	DB	'H) ',0
	POP	H		;restore it
	LDA	ACKFLG		;send 'ack' or..
	CALL	SEND		;..'nak' now.

;---->	rcvsq:
;	if 'crc' is in effect, there is a 10-second timeout to first
;	'soh'.  rountine tries 'errcrc' times to let sender know system
;	is capable of receiving a crc check.  At the end of that time
;	a 'nak' is sent which tells sender to use checksum checking
;	instead of crc.  thus automatic compatability with systems
;	implementing crc (cyclic redundancy checking) is provided.

RCVSQ	MVI	B,100-10	; 10 (-1) second timeout
	CALL	RECV		;get line character
	JC	RCVSTOT		;indicate 'timeout' occurred
	CALL	RCVERR		;check for receive errors (fop) and report
	CPI	SOH		;a start of header?
	JZ	RCVSOH		;branch, if yes.
	ORA	A		;character a null?
	JZ	RCVSQ		; (disregard)
	CPI	CRC		;was it a 'crc' we sent?
	JZ	RCVSQ		; (disregard)
	CPI	NAK		;or a checksum 'nak'?
	JZ	RCVSQ		; (disregard)
	CPI	EOT		;or an end of text?
	STC			;if yes, done -- return..
	RZ			;..with carry set.
	MOV	B,A		;save received character..
	CALL	ILPRT
	DB	CR,LF,'++ ',0
	MOV	A,B
	CALL	HEXO		;..to display on console as hex value.
	CALL	ILPRT
	DB	'h received, not SOH - ',0
RCVPRN	CALL	SHOWERR		;display error count
RCVSERR	MVI	B,10		;wait up to 1 second..
	CALL	RECV		;..with no character received.
	JNC	RCVSERR		;loop until sender done
	CALL	CKABORT		;want to stop receiving now?
	LDA	CRCFLG		;get 'crc' flag
	ORA	A		; 'crc' in effect?
	MVI	A,NAK		;put 'nak' in accumulator
	JNZ	RCVSER1		;no, store the 'nak'.
	LDA	FIRSTME		;first soh..
	ORA	A		;..been received?
	MVI	A,NAK		;put 'nak' in accumulator
	JZ	RCVSER1		;yes, then store 'nak'.
	MVI	A,CRC		;tell sender 'crc' is in effect
RCVSER1 STA	ACKFLG		;store the 'nak' or 'crc' request
	LDA	ERRCT		;include this error..
	INR	A		;..in the error count.
	STA	ERRCT		;abort if at limit
	CPI	ERRLIM+1	; (started out with count at 1)
	JC	RCVRPT		;no, try again.
	JMP	ABORT		;auto-abort at error limit

; cancel file-receive and erase unfinished file

RCVSABT	LXI	SP,STACK	;start anew
	CALL	CLOSFIL		;save records received then..
	CALL	NOASK		;..delete the partial file.
	CALL	ILPRTQ
	DB	CR,LF,'Routine cancelled -- '
	DB	'Unfinished file deleted' ,CR,LF,LF,BELL,0
	JMP	DONETCA		;to (tc) 'transfer completed' routine

; get error count and show locally

SHOWERR	PUSH	H
	LHLD	ERRCT		;load 8-bit error count and..
	MVI	H,0		;..zero high byte, then..
	CALL	DECOUT		;..display in decimal.
	POP	H
	CALL	ILPRT
	DB	' ++',CR,LF,0	;add trailer and start newline
	RET

; time-out announcement -- routine switches from 'crc' to 'checksum' if
; 'errct' reaches 'errcrc' and 'firstme' is true

RCVSTOT	CALL	ILPRT
	DB	CR,LF,'++ Timeout ',0
	CALL	SHOWERR
	LDA	ERRCT
	CPI	ERRCRC		;number of tries for crc/checksum soh
	JC	RCVSERR
	LDA	FIRSTME
	ORA	A
	JZ	RCVSERR
	LDA	CRCFLG
	ORA	A
	JNZ	RCVSERR
	MVI	A,'C'		;show now in checksum mode
	STA	CRCFLG
	CALL	ILPRTQ
	DB	'Switching to Checksum mode',CR,LF,BELL,0
	JMP	RCVSERR

;---->	rcverr:
;	checks for framing, overrun, and parity (fop) errors.
;	  1. error code (errcde) was set in 'recv' routine.
;	  2. errcde = 0 for no errors, errcde <> 0 for errors.
;	  3. on an error, routine jumps to rcvderr for display.
;	     but if no error, simply rets to caller.

RCVERR	 MOV	B,A		;save received character and..
	 LDA	ERRCDE		;get receive error code
	 ORA	A		;is it zero?
	 JNZ	RCVDERR		;no, get receive error.
	 MOV	A,B		;..put it back here.
	 RET

;---->	rcvderr:  checks for receive error and displays appropriate error
;	message.  then goes to 'rcvprn' to show error count, purge line,
;	and send 'nak'.

RCVDERR	 POP	PSW		;balance stack from call to 'rcverr'

	  IF	(NOT PMMI) AND (NOT MM100) AND (NOT US100)
	 JMP	RCVRPT
	  ENDIF			;neither pmmi or mm100 or us100

	  IF	US100
	 LDA	MODCTLB		;reset 'fop' error..
	 CALL	OUTCTRL		;..flags in 8251a.
	  ENDIF			;us100

	  IF	PMMI OR MM100 OR US100
	 LDA	ERRCDE	 	;get receive error code
	 ANI	FE	 	;was there a framing error?
	 JZ	RCVDERR2 	;no, go check for overrun.
	 CALL	ILPRT		;show error type then..
	 DB	CR,LF,'++ Framing error ',0
	 JMP	RCVPRN  	;..print error number.

RCVDERR2 LDA	ERRCDE
	 ANI	OE	 	;was there an overrun?
	 JZ	RCVDERR3 	;no, go check for parity error.
	 CALL	ILPRT
	 DB	CR,LF,'++ Overrun error ',0
	 JMP	RCVPRN

RCVDERR3 LDA	ERRCDE
	 ANI	PE		;parity error?
	 JZ	RCVRPT		;no, get another character.
	 CALL	ILPRT
	 DB	CR,LF,'++ Parity error ',0
	 JMP	RCVPRN
	  ENDIF			;pmmi or mm100 or us100

; got soh -- get record # and its complement

RCVSOH	XRA	A
	STA	FIRSTME		;indicate first soh was recevied
	MVI	B,10		; 1-sec time-out
	CALL	RECV		;receive character routine
	JC	RCVSTOT 	;indicate time-out
	CALL	RCVERR		;check for receive (fop) error and report
	MOV	D,A		;save record # for..
	MVI	B,10
	CALL	RECV
	JC	RCVSTOT
	CALL	RCVERR
	CMA			;..comparsion with..		
	CMP	D		;..its complement.
	JZ	RCVDATA		;okay, get 128-byte record.
	CALL	ILPRT
	DB	CR,LF,'++ Invalid record # in header - ',0
	JMP	RCVPRN		;display error count

RCVDATA	MOV	A,D
	STA	RCVRNO
	MVI	A,1		;set to 'show' data if in..
	STA	DATAFLG		;..'view' mode.
	MVI	C,0
	CALL	CLRCRC		;clear crc counter
	LXI	H,TBUF
RCVCHR	MVI	B,10		; 1-sec time-out
	CALL	RECV		;receive character routine
	JC	RCVSTOT 	;indicate time-out
	CALL	RCVERR		;check for receive (fop) error
	MOV	M,A		;store rcvd char in ram
	INR	L		;loop for 128 bytes
	JNZ	RCVCHR
	XRA	A		;indicate don't 'show' if in..
	STA	DATAFLG		;..'view' mode.
	LDA	CRCFLG
	ORA	A
	JZ	RCVCRC
	MOV	D,C
	MVI	B,10
	CALL	RECV
	JC	RCVSTOT
	CALL	RCVERR
	CMP	D
	JNZ	RCVCERR		;indicate checksum error
CK$R$NM	LDA	RCVRNO		;compare record..
	MOV	B,A
	LDA	RECDNO
	CMP	B		;..numbers.
	JZ	RECVACK
	INR	A
	CMP	B
	JNZ	ABORT
	RET

RCVCRC	MVI	E,2		;nr of crc bytes
RCVCRC2	MVI	B,10
	CALL	RECV
	JC	RCVSTOT
	CALL	RCVERR
	DCR	E
	JNZ	RCVCRC2
	CALL	CHKCRC
	ORA	A
	JZ	CK$R$NM
	CALL	ILPRT
	DB	CR,LF,'++ CRC error ',0
	JMP	RCVPRN		;display error count

RCVCERR	CALL	ILPRT
	DB	CR,LF,'++ Checksum error ',0
	JMP	RCVPRN

; send 'cp/m file' routines

; send start-of-header, record #, and its complement

SENDHDR	CALL	ILPRT
	DB	CR,'Sending #',0
	PUSH	H
	LHLD	RECDNO		;get record number
	CALL	DECOUT		;display in decimal and..
	CALL	ILPRT
	DB	' (',0
	CALL	DHXOUT		;..in hexidecimal.
	CALL	ILPRT
	DB	' Hex) ',0
	POP	H
SENDHNM	MVI	A,SOH		;send soh..
	CALL	SEND
	LDA	RECDNO		;..record # and..
	CALL	SEND
	LDA	RECDNO
	CMA
	JMP	SEND		;..its complement.

; send-record loop

SENDREC	MVI	A,1		;ready to 'show' data if in..
	STA	DATAFLG		;..'view' mode.
	MVI	C,0
	CALL	CLRCRC
	LXI	H,TBUF		;point to characters to send
SENPL	MOV	A,M		;get one chararcter from ram
	CALL	SEND
	INR	L		;loop 'till 128 bytes..
	JNZ	SENPL		;..are sent.
	XRA	A		;stop possibility..
	STA	DATAFLG		;;..of 'viewing'.
	RET

; send (phone line) checksum

SENPLKS	MOV	A,C
	JMP	SEND

; send 2-byte 'crc'

SENPLRC	PUSH	H
	LHLD	CRCVAL
	MOV	A,H
	CALL	SEND
	MOV	A,L
	CALL	SEND
	POP	H
	XRA	A
	RET

; receive either 'acknowledge' or error character

GETACK	MVI	B,100		; 10-sec time-out
	CALL	RECVDG		;enter clearing noise char
	JC	GETATOT		;indicate timeout occurred
	CPI	ACK
	RZ			; 'ack' received -- return.
	MOV	B,A
	CALL	ILPRT
	DB	CR,LF,'++ ',0
	MOV	A,B
	CPI	NAK		;if 'nak'..
	JZ	PRTNAK		;..say so.
	CALL	HEXO		;else show hex value..
	CALL	ILPRT
	DB	'h',0		;..received instead of 'nak'.
	JMP	ACKERR0
PRTNAK	CALL	ILPRT
	DB	'NAK',0
ACKERR0	CALL	ILPRT
	DB	' received, not ACK - ',0

; acknowledge and keep track of errors (for send-file)

ACKERR	CALL	SHOWERR		;display error count and line tail
	LDA	ERRCT		;update error counter
	INR	A
	STA	ERRCT
	CPI	ERRLIM+1	;reached the error limit yet?
	RC			;if not, back to work.
	CALL	ERXIT
	DB	'---> File send cancelled','@'

; time-out message

GETATOT	CALL	ILPRT
	DB	CR,LF,'++ Timeout on ACK - ',0
	JMP	ACKERR

; check for request to abort

CKABORT	LDA	VSEEFLG		;abort permitted in 'view' and other..
	ORA	A
	JZ	CKABRT1
	LDA	QFLG
	ORA	A
	RZ			;..modes but not in quiet mode.
CKABRT1	CALL	STAT
	RZ
	CALL	KEYIN
	CPI	CAN
	RNZ			;if zero, abort.

; cancellation routines

ABORT	LXI	SP,STACK
ABORTL	MVI	B,10		; 1-second timeout to clear input here..
	CALL	RECV
	JNC	ABORTL
	MVI	A,CAN
	CALL	SEND
ABORTW	MVI	B,10		;..and here.
	CALL	RECV
	JNC	ABORTW		;loop
	MVI	A,' '
	CALL	SEND
	MVI	A,'B'	 	;turn multi-file mode..
	STA	BATCHFLG 	;..off so routine ends.
	MVI	A,TRUE   	;show abort..
	STA	ABORTFLG	;..was requested.
	LDA	OPTION		;receiving a file now?
	CPI	'R'
	JZ	RCVSABT		;if yes, cancel the unfinished file.
	CALL	ILPRT
	DB	CR,LF,'---> Routine cancelled',CR,LF,LF,BELL,0
	JMP	DONETCB

; increment record number

INCRRNO	PUSH	H
	LHLD	RECDNO		;get record number
	INX	H		;bump it
	SHLD	RECDNO		;store it
	MOV	A,L
	POP	H
	RET

; erase file routine with question

ERASFIL	LDA	BATCHFLG	;don't ask for erase..
	ORA	A	 	;..in multi-file mode..
	JZ	NOASK	 	;..just do it.
	LXI	D,FCB
	MVI	C,SRCHF
	CALL	BDOS
	INR	A		; 0ffh --> 0 if file not found
	RZ			;ok to create file
	CALL	ILPRT
	DB	CR,LF,'File exists - erase?  (Y/N): ',BELL,0
	CALL	RESPOND
	CPI	'Y'		;anything else..
	RNZ			;..rets to caller/cmd mode.
	CALL	CRLF
NOASK	LXI	D,FCB
	MVI	C,ERASE
	JMP	BDOS

; create a cp/m file

MAKEFIL	LXI	D,FCB
	MVI	C,MAKE
	CALL	BDOS
	INR	A
	RNZ
	CALL	ERXIT
	DB	'++ Unable to make file - directory '
	DB	'may be full ++','@'

; determine file record count and save in rcnt

CNREC	MVI	C,COMPSZ	;compute file size function
	LXI	D,FCB		;point to file control block
	CALL	BDOS
	LHLD	FCB+33		;get record count
	SHLD	RCNT		;store it
	LXI	H,0		;zero hl-pair
	SHLD	FCB+33		;reset random record in fcb
	RET

; open file and ready to send

OPENFIL	LXI	D,FCB
	MVI	C,OPEN
	CALL	BDOS
	INR	A
	JNZ	OPENOK
	CALL	ERXIT
	DB	'++ Unable to find file ++','@'

OPENOK	CALL	ILPRT
	DB	CR,LF,'File open: ',0
	LHLD	RCNT		;get record count
	CALL	DECOUT		;print decimal number of records
	CALL	ILPRT
	DB	' (',0
	CALL	DHXOUT		;print records in hex
	CALL	ILPRT
	DB	' Hex) records   (^X cancels)',CR,LF
	DB	'Send time: ',0
	LXI	H,BTABLE	;point to baud factor table
	MVI	D,0		;clear msb
	LDA	MSPEED		;load speed indicator
	MOV	E,A		;set up for table access
	DAD	D		;index to required factor
	MOV	A,M		;put factor in a-reg
	LHLD	RCNT		;get number of records
	CALL	DIVHLA		;divide hl-pair by value in a (rec/min)
	PUSH	H		;save remainder then..
	MVI	H,0		;..clear msb of hl-pair.
	CALL	DECOUT		;show minutes
	CALL	ILPRT
	DB	' minutes, ',0
	LXI	H,SECTBL	;divisor table for seconds calc
	MVI	D,0
	LDA	MSPEED		;speed factor
	MOV	E,A
	DAD	D		;index into table
	MOV	A,M		;get multiplier
	POP	H		;get remainder
	CALL	MULHA		;multiply contents of h-reg by a-reg
	CALL	SHIFTHL		;shift hl-pair four bits right
	MVI	H,0
	CALL	DECOUT		;show seconds
	CALL	ILPRT
	DB	' seconds at ',0
	LXI	H,SPTBL		;baudrate text table
	MVI	D,0
	LDA	MSPEED		;baudrate factor
	RLC			; *4 offset into..
	RLC			;..baudrate table.
	MOV	E,A		;put speed factor in de-pair
	DAD	D		;add to hl-pair and..
	CALL	TEXTOUT		;..show baudrate.
	CALL	ILPRT		;add tailer
	DB	' baud',CR,LF,0
	RET

; time-to-send and baudrate tables

BTABLE	DB	5,13,19,25,29,49
SECTBL	DB	192,74,51,38,33,20
SPTBL	DB	'110@','300@','450@','600@','710@','1200@'

; close cp/m file

CLOSFIL	LXI	D,FCB		;point to filename
	MVI	C,CLOSE
	CALL	BDOS
	INR	A		; 0ffh --> 00 if close not okay
	RNZ			;return if close successful
	MVI	A,'Q'		;reset quiet flag..
	STA	QFLG		;..and..
	JMP	ERXIT1		;..abort operation.
	
; update record read

RDRECD	LDA	RECINBF		;records in buffer
	DCR	A
	STA	RECINBF
	JM	RDBLOCK
	LHLD	RECPTR
	LXI	D,80H
	CALL	MOVE128
	SHLD	RECPTR		;buffer pointer
	RET

; buffer empty -- read in another block (128 records)

RDBLOCK	LDA	EOFLG
	CPI	1
	STC
	RZ
	MVI	C,0
	LXI	D,DBUF
RDRECLP	PUSH	B
	PUSH	D
	MVI	C,SETDMA
	CALL	BDOS
	LXI	D,FCB
	MVI	C,READ
	CALL	BDOS
	POP	D
	POP	B
	ORA	A		;record read okay?
	JZ	RDRECOK		;yes
	DCR	A		; 'eof'?
	JZ	REOF		;yes
	CALL	ERXIT
	DB	'++ File read error ++','@'
RDRECOK	LXI	H,80H
	DAD	D
	XCHG
	INR	C
	MOV	A,C
	CPI	DBUFSIZ*8	;buffer size (128 128-byte records)
	JZ	RDBFULL		;read-buffer full?
	JMP	RDRECLP		;no, loop for another record.

REOF	MVI	A,1
	STA	EOFLG
	MOV	A,C

; buffer full or received 'eof'

RDBFULL	STA	RECINBF
	LXI	H,DBUF
	SHLD	RECPTR
	LXI	D,TBUF
	MVI	C,SETDMA
	CALL	BDOS
	JMP	RDRECD

; update record write

WRRECD	LHLD	RECPTR
	XCHG
	LXI	H,80H
	CALL	MOVE128
	XCHG
	SHLD	RECPTR
	LDA	RECINBF
	INR	A
	STA	RECINBF
	CPI	DBUFSIZ*8 	;buffer size
	RNZ			;ret if not full block

; write block to disk (128 records)

WRBLOCK	LDA	RECINBF
	ORA	A
	RZ
	MOV	C,A
	LXI	D,DBUF
DKWRLP	PUSH	H
	PUSH	D
	PUSH	B
	MVI	C,SETDMA
	CALL	BDOS
	LXI	D,FCB
	MVI	C,WRITE
	CALL	BDOS
	POP	B
	POP	D
	POP	H
	ORA	A
	JNZ	WRERR		;write error
	LXI	H,80H
	DAD	D
	XCHG
	DCR	C
	JNZ	DKWRLP
	XRA	A		;reset record..
	STA	RECINBF		;..counter.
	LXI	H,DBUF		;reset buffer..
	SHLD	RECPTR		;..pointer.
	RET

WRERR	MVI	C,CAN
	CALL	SEND
	CALL	ERXIT
	DB	'++ File write error ++','@'

; recv: receive a character.  time-out is in b, in tenth-seconds.
; entry via 'recvdg' deletes line-noise characters.  for example,
; having just sent a record, calling 'recvdg' deletes a line noise
; induced character long before the ack/nak is received.

; time-out routine -- delete line-noise character -- check for abort

RECVDG	CALL	CLR$L		;clear noise from line
RECV	PUSH	D
RECMSEC	LXI	D,113*MHZ	;makes b-reg tenth-seconds
	CALL	CKABORT
RECWLP	CALL	INSTAT 		;test if character received
	JZ	RECEIVE		;get character from line
	DCX	D
	MOV	A,D
	ORA	E
	JNZ 	RECWLP
	DCR	B		;timeout value
	JNZ 	RECMSEC
	POP 	D
	STC 			;carry means time-out occurred
	RET

; receive character from modem

RECEIVE	
	 IF	PMMI OR MM100 OR US100
	CALL	INCTRL	 	;get error-status byte
	ANI	ERRCDM   	;check for parity, overrun or framing..
	STA	ERRCDE	   	;..errors, save results.
	 ENDIF			;pmmi or mm100 or us100

MCHAR	CALL	INCHAR
	POP	D
	PUSH	PSW
	CALL	UPDCRC		;calculate crc value
	ADD	C		;update..
	MOV	C,A		;..checksum.
	LDA	RSEEFLG		;show data and error msg if zero'd
	ORA	A
	JZ	MONIN
	LDA	VSEEFLG		;suppress status messages..
	ORA	A		;..if zero'd, but view ascii text.
	JNZ	NOMONIN
	LDA	DATAFLG
	ORA	A
	JZ	NOMONIN
MONIN	POP	PSW
	PUSH	PSW
 	CALL	SHOW		;display character on crt
NOMONIN	POP	PSW
	ORA	A
	RET

; send character out thru modem

SENDACK	MVI	A,ACK		;send acknowledge
SEND	PUSH	PSW
	LDA	SSEEFLG		;show file as sent
	ORA	A
	JZ	MONOUT
	LDA	VSEEFLG		;view ascii file as sent but..
	ORA	A		;..without status messages.
	JNZ	NOMONOT
	LDA	DATAFLG
	ORA	A
	JZ	NOMONOT
MONOUT	POP	PSW
	PUSH	PSW
	CALL	SHOW
NOMONOT	POP	PSW
	PUSH	PSW
	CALL	UPDCRC		;calculate crc value
	ADD	C		;update..
	MOV	C,A		;..checksum.
SENDW	CALL	OUTSTAT		;test if ready to send
	JNZ	SENDW		;out at baudrate
	POP	PSW
	JMP	OUTCHAR		;send character

; waits for 1st received chararcter (time-out in e-reg) in send mode

WAITNAK	LDA	QFLG		;add 'crlf' and ready msg..
	ORA	A		;..if..
	JNZ	WTNAK1		;..in quiet mode.
	CALL	ILPRTQ		;print crlf
	DB	CR,LF,0
WTNAK1	CALL	ILPRTQ
	DB	'Awaiting ready signal ',0
WAITNLP	CALL	CKABORT
	MVI	B,10		; 1-sec time-out
	CALL	RECV
	CPI	NAK		;checksum (or name nak) or..
	JZ	WAITCK		; (show checksum msg, if not batch mode.)
	CPI	CRC		;..crc request?
	JZ	WAITCRC		;set crc flag and show msg
	DCR	E		;e-reg in seconds
	JNZ	WAITNLP		;loop 'till time-out then..
	JMP	ABORT		;..abort.

WAITCRC	CALL	ILPRT
	DB	'-- CRC request received',CR,LF,0
	XRA	A
	STA	CRCFLG
	RET

WAITCK	LDA	BATCHFLG	;don't show..
	ORA	A		;..if in batch mode.
	RZ
	CALL	ILPRT
	DB	'-- Checksum received',CR,LF,0
	RET

; file transfer completed

DONE	LDA	BATCHFLG
	ORA	A
	JNZ	DONETC		;branch if wasn't batch mode

; prepare for next file transfer

	LDA	FCB	  	;save drive #
	STA	DISKNO
	LXI	H,FCB	  	;blank out file control block
	CALL	INITFCB
	LDA	DISKNO	  	;put drive number back
	STA	FCB
	LXI	H,RESTRN  	;restore record numbers..
	LXI	D,RECDNOB 	;..for new file transfer.
	MVI	B,RECDNOE-RECDNOB
	CALL	MOVE
	LDA	SENDFLG	  	;go to either send or..
	ORA	A	  	;..receive file, controlled by..
	JNZ	SENDFIL2  	;..the routine setting flag..
	JMP	RCVFIL1	  	;..in multi-file (batch) mode.

; end-of-transfer routine

DONETC	MVI	A,TRUE		;reset multi-file..
	STA	FSTFLG		;..transfer mode.
	CALL	ILPRTQ
	DB	CR,LF,'---> Transfer completed',CR,LF,BELL,0

DONETCA
	 IF	PMMI OR MM100 OR US100
	LDA	EXITFLG		;exit to cp/m with..
	ORA	A		;..disconnect and reboot?
	JZ	PEXIT		;branch, if yes.
	LDA	DISCFLG		;disconnect when finished?
	ORA	A		;yes, if zero.  disconnect..
	JZ	DISCON1		;..and go to command line.
	 ENDIF			;pmmi or mm100 or us100

DONETCB	MVI	A,CRC
	STA	CRCFLG		;turn off 'crc' option
	MVI	A,'Q'		;reset the flag to normal
	STA	QFLG
	MVI	A,TRUE
	STA	FIRSTME		;set 'first-time' flag
	LDA	ABORTFLG	;came here from a timeout?
	ORA	A
	JNZ	MENU		;if yes, go to command mode.
	LDA	TERMFLG		;see if return to..
	ORA	A		;..terminal mode..
	JZ	TERM		;..after x'fer.
	JMP	MENU		;else, go to cmd line.

; c p m

; leave comm7 program

PREEXIT	
	 IF	PMMI OR MM100 OR US100
	LDA	LINEFLG		;simply exit if..
	ORA	A		;..line not..
	JZ	EXIT		;..connected.
	CALL	ILPRT
	DB	CR,LF,LF,'Exit to CP/M with telephone line '
	DB	'connected?  (Y/N): ',0
	CALL	RESPOND
	CPI	'N'		;exit with line connected?
	JNZ	EXIT		;no, with line disconnected.  any..
PEXIT	CALL	DISCONN		;..other character simply exits to cp/m.
	 ENDIF			;pmmi or mm100 or us100

EXIT	LDA	O$USR		;restore original..
	CALL	SET$USR		;..user area and..
	LXI	D,TBUF		;..tidy up before..
	MVI	C,SETDMA
	CALL	BDOS		;..leaving comm7.
	CALL	STAT		;flush any character..
	MVI	C,RDCON		;..remaining..
	CNZ	BDOS		;..in bdos.
	CALL	CRLF		;exit to cp/m with a fresh line
	LDA	EXITFLG		;warm boot if 'e' secondary..
	ORA	A		;..option..
	JZ	WBOOT		;..requested.
	LDA	SAVCCP		;see if 'ccp' saved
	ORA	A
	JZ	WBOOT		;no, noisily warm boot.  else..
	LHLD	STACK		;..restore cp/m stack by..
	SPHL			;..hl/sp-pair swap then..
	RET			;..return quickly to cp/m 'ccp'.

	LINK	COMM725B	;chains to 'comm725b.asm' using lasm.com
