;
;	APMODEM.ASM V1.1
;	BY WARD CHRISTENSEN
;	Modified for the Apple ][
;	BY GORDON BANKS
;	1/1/81
;
;CP/M - CP/M FILE TRANSFER PROGRAM, AND
;TERMINAL PROGRAM.
;
;NOTE: THIS FILE WILL ASSEMBLE, WITHOUT NEED FOR
;EDITING, TO WORK WITH A D.C. HAYES MICROMODEM II.  
;IF YOU HAVE ANOTHER MODEM, YOU MUST CHANGE THE  
;EQUATES TO FALSE FOR MMII AND TRUE FOR YOUR MODEM.
;
* * * * * * * * * * * * * * * * * * * * * * * * *
*						*
*    THIS PROGRAM DOCUMENTED IN "MODEM.DOC"	*
*						*
* * * * * * * * * * * * * * * * * * * * * * * * *
;
;PLEASE PASS ON MODS, BUGS, ETC, SO YOUR
;FIXES OR ENHANCEMENTS MAY BE SHARED BY ALL,
;	     TO:
;	Gordon Banks
;	5715 S. Drexel #209
;	Chicago, IL 60637
;	(312) 324-6194
;
;	----------
;
;	12/18/80
;ADAPTED FOR THE APPLE ][ COMPUTER RUNNING CP/M WITH
;THE Z-80 SOFTCARD BY CHANGING THE EQUATES FOR THE 
;VARIOUS APPLE MODEM CONFIGURATIONS AND CHANGING THE
;IN AND OUT STATEMENTS TO LDA AND STA. (GORDON BANKS)
;IT SUPPORTS THE FOLLOWING APPLE MODEM CONFIGURATIONS:
;	1)DC HAYS MICROMODEM II
;	2)EXTERNAL MODEM THROUGH ONE OF THE FOLLOWING:
;		a) CCS 7710 SERIAL INTERFACE
;		b) SSM SERIAL INTERFACE
;		c) APPLE COMMUNICATIONS INTERFACE
;		d) APPLE SERIAL INTERFACE
;
;DEFINE FALSE AND TRUE
;
FALSE	EQU	0
TRUE	EQU	NOT FALSE
;
STDCPM	EQU	TRUE	;TRUE, IS STANDARD CP/M
;
BASE	EQU	0	;CP/M BASE ADDRESS
;
;NOTE: IF DC HAYES THEN BAUD RATE DEFAULTS TO
;300 - 1 STOP BIT.  DO NOT CHANGE NEXT EQUATES
;
ORIGMOD EQU	8EH	;OFF HOOK, 110 BAUD, CAR. ON, ORIG.
ANSWMOD	EQU	8AH	;OFF HOOK, 110 BAUD, CAR. ON, ANSW.
;
;IF YOU ARE USING AN EXTERNAL MODEM 
;CHANGE THESE EQUATES FOR YOUR MODEM PORT REQUIREMENTS
;
INITREQ	EQU	FALSE 	;TRUE, IF MODEM PORT INIT. REQ'D
;
;APPLE MODEM TYPES EQUATES
MMII	EQU	TRUE	;TRUE, IF USING D.C. HAYES MICROMODEM ][
CCS	EQU	FALSE	;TRUE, USING CCS 7710 SERIAL INTERFACE
COMCARD	EQU	FALSE	;TRUE, USING APPLE COMMUNICATIONS CARD
SSM	EQU	FALSE	;TRUE, USING SSM SERIAL INTERFACE
;
;the Apple serial card apparently has the same protocol for DATA 
;and STATUS as the communication card.
;N.B.-->I have not tested this program for the SSM or
;Apple serial interface, but according to documentation I have
;been able to obtain, they should work using these equates.
;
INITC1	EQU	03	;1ST INIT CHAR TO CTL PORT
INITC2	EQU	17 	;2ND INIT CHAR TO CTL PORT
;
	IF	CCS	;CCS CARD
MODCTLP	EQU	0E0A0H	;STATUS PORT OF CCS ACIA         
MODDATP	EQU	0E0A1H	;DATA PORT OF CCS ACIA (MOT 6850)
 	ENDIF		;CCS
;
	IF	COMCARD	;APPLE COMMUNCATIONS CARD
MODCTLP	EQU	0E0AEH	;STATUS
MODDATP	EQU	0E0AFH	;DATA
	ENDIF		;COMCARD
;
	IF	MMII	;DC HAYES MICROMODEM ][
MODCTLP	EQU	0E0A6H	;STATUS PORT
MODCTL2	EQU	0E0A5H	;2ND STATUS PORT
MODDATP	EQU	0E0A7H	;DATA PORT
	ENDIF		;MMII
;
	IF	SSM	;SSM INTERFACE
MODCTLP	EQU	0E0A4H	;STATUS
MODDATP	EQU	0E0A5H	;DATA
	ENDIF		;SSM
;
MODSNDB	EQU	2	;BIT TO TEST FOR SEND
MODSNDR	EQU	2	;VALUE WHEN READY
MODRCVB	EQU	1	;BIT TO TEST FOR RECEIVE
MODRCVR	EQU	1	;VALUE WHEN READY
;
ERRLIM	EQU	10	;MAX ALLOWABLE ERRORS
EXITCHR	EQU	'E'-40H ;CTL-E EXIT FROM T OR C
DISCCHR	EQU	'D'-40H	;CTL-D DISCONNECTS MODEM T/E
;
FASTCLK	EQU	FALSE	;TRUE FOR 4 MHZ CLOCK
;
;SOME TIME-SHARE COMPUTERS REQUIRE TERMINALS TO
;HAVE BIT 7 HIGH (MARKING), SO IN THE TERMINAL
;MODE WE FORCE IT TO HIGH IF THE FOLLOWING OPTION
;IS SELECTED:
;
TIMESHR	EQU	FALSE	;TRUE TO MAKE BIT 7 HIGH
; 
;DEFINE ASCII CHARACTERS USED
;
SOH	EQU	1	;START OF HEADER
EOT	EQU	4	;END OF TRANSMISSION
ACK	EQU	6	;ACKNOWLEDGE
NAK	EQU	15H	;NEG ACKNOWLEDGE
CAN	EQU	18H	;CANCEL
LF	EQU	10	;LINEFEED
CR	EQU	13	;CARRIAGE RETURN
; 
	ORG	BASE+100H
;
;INIT PRIVATE STACK
	LXI	H,0	;HL=0
	DAD	SP	;HL=STACK FROM CP/M
	SHLD	STACK	;..SAVE IT
	LXI	SP,STACK ;SP=MY STACK
	CALL	START	;GO PRINT ID
	DB	'APPLE ][ MODEM V1.1 as of '
	DB	'1/1/81',CR,LF,'$'
; 
START	POP	D	;GET ID MESSAGE
	MVI	C,PRINT
	CALL	BDOS	;PRINT ID MESSAGE
;
;INITIALIZE THE JMPS TO CP/M BIOS
;
	CALL	INITADR
;
	LDA	FCB+1	;GET PRIMARY OPTION
	CPI	'H'	;MODEM H(ELP)?
	JZ	HELP	;..YES, GIVE HELP
	CPI	'X'	;MODEM X(AMPLES)?
	JZ	EXAM	;GIVE EXAMPLES
;
;SAVE PRIMARY OPTION, VALIDATE SECONDARY OPT.
;
	CALL	PROCOPT
;
;INIT THE MODEM OR SERIAL PORT
;
	CALL	INITMOD
;
;MOVE THE FILENAME FROM FCB 2 TO FCB 1
;
	CALL	MOVEFCB
;
;GOBBLE UP GARBAGE CHARS FROM THE LINE
;PRIOR TO RECEIVE OR SEND
;
	LDA	MODDATP
	LDA	MODDATP
;
;JMP TO APPROPRIATE FUNCTION
;
	LDA	OPTION	;GET PRIMARY OPTION
;
	CPI	'C'	;(COMPAT W/EARLIER
	JZ	TRMECHO	;OPTION "COMPUTER")
;
	CPI	'E'	;TERMINAL IN ECHO
	JZ	TRMECHO	;..MODE?
;
	CPI	'T'	;TERMINAL..
	JZ	TERM	;..MODE?
;
	CPI	'D'
	JZ	DISCONN
;
       	CPI	'S'	;SEND..
	JZ	SENDFIL	;..A FILE?
;
	CPI	'R'	;RECEIVE..
	JZ	RCVFIL	;..A FILE?
;
;INVALID OPTION
;
	JMP	BADOPT
;
* * * * * * * * * * * * * * * * * * * * *
*					*
*	TERM: TERMINAL MODE		*
*					*
* * * * * * * * * * * * * * * * * * * * *
;
;THIS PROGRAM SIMPLY SENDS KEYED CHARACTERS
;DOWN THE LINE, AND DISPLAYS CHARACTERS
;RECEIVED FROM THE LINE.  THIS MAKES IT
;SUITABLE FOR COMMUNICATION WITH TIME SHARING
;COMPUTERS, CBBS'S, OR ANOTHER PROGRAM
;RUNING "MODEM E" (ECHO MODE)
;
;TYPE THE "EXITCHR" (ORIGINALLY CTL-E) TO EXIT.
;OR THE "DISCCHR" (ORIGINALLY CTL-D) TO DISCONN.
;
;A FUTURE ENHANCEMENT WILL BE TO WRITE THE
;RECEIVED DATA IN MEMORY, AND ALLOW IT TO
;BE WRITTEN TO DISK
;
TERM	CALL	STAT	;LOCAL CHAR KEYED?
	JZ	TERML	;..NO, CHECK LINE
	CALL	KEYIN	;GET CHAR
	CPI	EXITCHR	;TIME TO END?
	JZ	CKDIS	;YES, CK DISCONN
	CPI	DISCCHR	;DISCONNECT REQUEST?
	JZ	DISCONN	;YES, DO IT
	IF	TIMESHR
	ORI	80H	;FORCE BIT 7 TO HIGH
	ENDIF		;TIMESHR
	STA	MODDATP	;SEND THE CHAR
;
;SEE IF CHAR FROM LINE
;
TERML	LDA	MODCTLP	;READ STATUS
	ANI	MODRCVB	;ISOLATE BIT
	CPI	MODRCVR	;READY?
	JNZ	TERM	;..NO, LOOP
	LDA	MODDATP	;READ DATA
	ANI	7FH	;STRIP PARITY BIT
	CALL	TYPE	;TYPE IT
	JMP	TERM	;LOOP
;
* * * * * * * * * * * * * * * * * * * * *
*					*
*	TRMECHO: TERMINAL WITH ECHO	*
*					*
* * * * * * * * * * * * * * * * * * * * *
;
;TERMINAL PROGRAM WITH ECHO - SEE NOTES
;UNDER "TERM" ABOVE
;
;C A U T I O N   DON'T RUN WITH BOTH COMPUTERS
;IN "ECHO" MODE - LINE ERRORS (OR ANY CHAR)
;WILL BE ECHOED BACK AND FORTH AD INFINITUM.
;
TRMECHO	LDA	MODCTLP	;GET STATUS
	ANI	MODRCVB	;ISOLATE READY BIT
	CPI	MODRCVR	;ARE WE READY?
	JZ	LINECHR	;YES, READ THE CHR
	CALL	STAT	;CHECK LOCAL KB
	JZ	TRMECHO	;..NO CHAR
	CALL	KEYIN	;GET LOCAL CHAR
	CPI	EXITCHR	;END?
	JZ	CKDIS	;YES, CK DISCONN, EXIT
	CPI	DISCCHR	;DISCONN?
	JZ	DISCONN	;..YES, DO IT.
	STA	MODDATP	;SEND CHAR
	CALL	TYPE	;ECHO IT LOCALLY
	JMP	TRMECHO	;..AND LOOP
;
;GOT CHAR FROM LINE
;
LINECHR	LDA	MODDATP	;GET CHAR
	STA	MODDATP	;ECHO IT
	ANI	7FH	;STRIP PARITY BIT
	CALL	TYPE	;TYPE IT
	JMP	TRMECHO	;LOOP
;
* * * * * * * * * * * * * * * * * * * * *
*					*
*	SENDFIL: SENDS A CP/M FILE	*
*					*
* * * * * * * * * * * * * * * * * * * * *
;
;THE CP/M FILE SPECIFIED IN THE MODEM COMMAND
;IS TRANSFERRED OVER THE PHONE TO ANOTHER
;COMPUTER RUNNING MODEM WITH THE "R" (RECEIVE)
;OPTION.  THE DATA IS SENT ONE SECTOR AT A
;TIME WITH HEADERS AND CHECKSUMS, AND RE-
;TRANSMISSION ON ERRORS.  
;
SENDFIL	CALL	OPENFIL	;OPEN THE FILE
	MVI	E,80	;WAIT 80 SEC..
	CALL	WAITNAK	;..FOR INITIAL NAK
;
SENDLP	CALL	RDSECT	;READ A SECTOR
	JC	SENDEOF	;SEND EOF IF DONE
	CALL	INCRSNO	;BUMP SECTOR #
	XRA	A	;ZERO ERROR..
	STA	ERRCT	;..COUNT
;
SENDRPT	CALL	SENDHDR	;SEND A HEADER
	CALL	SENDSEC	;SEND DATA SECTOR
	CALL	SENDCKS	;SEND CKSUM
	CALL	GETACK	;GET THE ACK
	JC	SENDRPT	;REPEAT IF NO ACK
	JMP	SENDLP	;LOOP UNTIL EOF
;
;FILE SENT, SEND EOT'S
;
SENDEOF	MVI	A,EOT	;SEND..
	CALL	SEND	;..AN EOT
	CALL	GETACK	;GET THE ACK
	JC	SENDEOF	;LOOP IF NO ACK
	JMP	DONE	;ALL DONE
;
* * * * * * * * * * * * * * * * * * * * *
*					*
*	RCVFIL: RECEIVE A FILE		*
*					*
* * * * * * * * * * * * * * * * * * * * *
;
;RECEIVES A FILE IN BLOCK FORMAT AS SENT
;BY ANOTHER PERSON DOING "MODEM S FN.FT".
;
RCVFIL	CALL	ERASFIL	;ERASE THE FILE
	CALL	MAKEFIL	;..THEN MAKE NEW
	CALL	ILPRT	;PRINT:
	DB	'FILE OPEN, READY TO RECEIVE',CR,LF,0
;
RCVLP	CALL	RCVSECT	;GET A SECTOR
	JC	RCVEOT	;GOT EOT
	CALL	WRSECT	;WRITE THE SECTOR
	CALL	INCRSNO	;BUMP SECTOR #
	CALL	SENDACK	;ACK THE SECTOR
	JMP	RCVLP	;LOOP UNTIL EOF
;
;GOT EOT ON SECTOR - FLUSH BUFFERS, END
;
RCVEOT	CALL	WRBLOCK	;WRITE THE LAST BLOCK
	CALL	SENDACK	;ACK THE SECTOR
	CALL	CLOSFIL	;CLOSE THE FILE
	JMP	DONE	;ALL DONE
;
* * * * * * * * * * * * * * * * * * * * *
*					*
*		SUBROUTINES		*
*					*
* * * * * * * * * * * * * * * * * * * * *
;
;
;---->	RCVSECT: RECEIVE A SECTOR
;
;RETURNS WITH CARRY SET IF EOT RECEIVED.
;
RCVSECT	XRA	A	;GET 0
	STA	ERRCT	;INIT ERROR COUNT
;
RCVRPT	LDA	QFLG	;QUIET?
	ORA	A
	JZ	RCVSQ	;YES, NO STAT MSG.
	CALL	ILPRT	;PRINT:
	DB	'AWAITING #',0
	LDA	SECTNO	;GET SECTOR #
	INR	A	;(REAL INR LATER)
	CALL	HEXO	;PRINT IN HEX
	CALL	CRLF	;..THEN CRLF
;
RCVSQ	MVI	B,10	;10 SEC TIMEOUT
	CALL	RECV	;GET SOH/EOT
	JC	RCVSTOT	;TIMEOUT
	CPI	SOH	;GET SOH?
	JZ	RCVSOH	;..YES
;
;EARLIER VERS. OF MODEM PROG SENT SOME NULLS -
;IGNORE THEM
;
	ORA	A	;00 FROM SPEED CHECK?
	JZ	RCVSQ	;YES, IGNORE IT
	CPI	EOT	;END OF TRANSFER?
	STC		;RETURN WITH CARRY..
	RZ		;..SET IF EOT
;
;DIDN'T GET SOH  OR EOT - 
;
	MOV	B,A	;SAVE CHAR
	LDA	VSEEFLG	;VIEWING..
	ORA	A	;..MODE?
	JZ	RCVSEH	;YES, PRT.MSG
	LDA	QFLG	;QUIET..
	ORA	A	;..MODE?
	JZ	RCVSERR	;YES, SKIP MSG
RCVSEH	MOV	A,B	;GET CHAR
	CALL	HEXO	;SHOW IN HEX
	CALL	ILPRT	;PRINT:
	DB	'H RCD, NOT SOH',CR,LF,0
;
;DIDN'T GET VALID HEADER - PURGE THE LINE,
;THEN SEND NAK.
;
RCVSERR	MVI	B,1	;WAIT FOR 1 SEC..
	CALL	RECV	;..WITH NO CHARS
	JNC	RCVSERR	;LOOP UNTIL SENDER DONE
	MVI	A,NAK	;SEND..
	CALL	SEND	;..THE NAK
	LDA	ERRCT	;ABORT IF..
	INR	A	;..WE HAVE REACHED..
	STA	ERRCT	;..THE ERROR..
	CPI	ERRLIM	;..LIMIT?
	JC	RCVRPT	;..NO, TRY AGAIN
;
;10 ERRORS IN A ROW - 
;
	LDA	VSEEFLG	;VIEWING..
	ORA	A	;..FILE?
	JZ	RCVCKQ	;YES, ASK RETRY/QUIT
	LDA	QFLG	;QUIET..
	ORA	A	;..MODE?
	JZ	RCVSABT	;ABORT
;
RCVCKQ	CALL	CKQUIT	;RETRY/QUIT?
	JZ	RCVSECT	;TRY AGAIN
;
RCVSABT	CALL	CLOSFIL	;KEEP WHATEVER WE GOT
	CALL	ERXIT
	DB	'++UNABLE TO RECEIVE BLOCK'
	DB	CR,LF,'++ABORTING++$'
;
;TIMEDOUT ON RECEIVE
;
RCVSTOT	LDA	VSEEFLG	;VIEWING..
	ORA	A	;..MODE?
	JZ	RCVSPT	;YES, PRT MSG
	LDA	QFLG	;QUIET..
	ORA	A	;..MODE?
	JZ	RCVSERR	;YES, NO MSG
;
RCVSPT	CALL	ILPRT
	DB	'++TIMEOUT++ ',0
;
RCVPRN	LDA	ERRCT	;PRINT ERROR..
	CALL	HEXO	;..COUNT
	CALL	CRLF
	JMP	RCVSERR	;BUMP ERR CT, ETC.
;
;GOT SOH - GET BLOCK #, BLOCK # COMPLEMENTED
;
RCVSOH	MVI	B,1	;TIMEOUT = 1 SEC
	CALL	RECV	;GET SECTOR
	JC	RCVSTOT	;GOT TIMEOUT
	MOV	D,A	;D=BLK #
	MVI	B,1	;TIMEOUT = 1 SEC
	CALL	RECV	;GET CMA'D SECT #
	JC	RCVSTOT	;TIMEOUT
	CMA		;CALC COMPLEMENT
	CMP	D	;GOOD SECTOR #?
	JZ	RCVDATA	;YES, GET DATA
;
;GOT BAD SECTOR #
;
	LDA	VSEEFLG	;VIEWING..
	ORA	A	;..MODE?
	JZ	RCVBSE	;..YES, PRT MSG
	LDA	QFLG	;QUIET..
	ORA	A	;..MODE?
	JZ	RCVSERR	;..YES, NO MSG
;
RCVBSE	CALL	ILPRT	;PRINT:
	DB	'++BAD SECTOR # IN HDR',CR,LF,0
	JMP	RCVSERR	;BUMP ERROR CT.
;
RCVDATA	MOV	A,D	;GET SECTOR #
	STA	RCVSNO	;SAVE IT
	MVI	A,1	;SHOW..
	STA	DATAFLG	;GETTING DATA
	MVI	C,0	;INIT CKSUM
	LXI	H,BASE+80H	;POINT TO BUFFER
RCVCHR	MVI	B,1	;1 SEC TIMEOUT
	CALL	RECV	;GET CHAR
	JC	RCVSTOT	;TIMEOUT
	MOV	M,A	;STORE CHAR
	INR	L	;DONE?
	JNZ	RCVCHR	;NO, LOOP
;
;VERIFY CHECKSUM
;
	MOV	D,C	;SAVE CHECKSUM
	XRA	A	;SHOW..
	STA	DATAFLG	;..END OF DATA
	MVI	B,1	;TIMEOUT LEN.
	CALL	RECV	;GET CHECKSUM
	JC	RCVSTOT	;TIMEOUT
	CMP	D	;CHECKSUM OK?
	JNZ	RCVCERR	;NO, ERROR
;
;GOT A SECTOR, IT'S A DUP IF = PREV,
;	OR OK IF = 1 + PREV SECTOR
;
	LDA	RCVSNO	;GET RECEIVED
	MOV	B,A	;SAVE IT
	LDA	SECTNO	;GET PREV
	CMP	B	;PREV REPEATED?
	JZ	RECVACK	;ACK TO CATCH UP
	INR	A	;CALC NEXT SECTOR #
	CMP	B	;MATCH?
	JNZ	ABORT	;NO MATCH - STOP SENDER, EXIT
	RET		;CARRY OFF - NO ERRORS
;
;GOT CKSUM
;
RCVCERR	LDA	VSEEFLG	;VIEWING..
	ORA	A	;..MODE?
	JZ	RCVCPR	;..YES, PRT MSG
	LDA	QFLG	;QUIET..
	ORA	A	;..MODE?
	JZ	RCVSERR	;YES, NO MSG
;
RCVCPR	CALL	ILPRT
	DB	'++CKSUM++ ',0
	JMP	RCVPRN	;PRINT ERROR #
;
;PREV SECT REPEATED, DUE TO THE LAST ACK
;BEING GARBAGED.  ACK IT SO SENDER WILL CATCH UP 
;
RECVACK	CALL	SENDACK	;SEND THE ACK,
	JMP	RCVSECT	;GET NEXT BLOCK
;
;SEND AN ACK FOR THE SECTOR
;
SENDACK	MVI	A,ACK	;GET ACK
	CALL	SEND	;..AND SEND IT
	RET
;
;---->	SENDHDR: SEND THE SECTOR HEADER
;
;SEND: (SOH) (BLOCK #) (COMPLEMENTED BLOCK #)
;
SENDHDR	LDA	QFLG	;QUIET..
	ORA	A	;..MODE?
	JZ	SENDHNM	;YES, SKIP STATUS MSG.
	CALL	ILPRT	;PRINT:
	DB	'SEND # ',0
	LDA	SECTNO	;PRINT..
	CALL	HEXO	;..SECT #
	CALL	CRLF	;..THEN CR/LF
;
SENDHNM	MVI	A,SOH	;SEND..
	CALL	SEND	;..SOH,
	LDA	SECTNO	;THEN SEND..
	CALL	SEND	;..SECTOR #
	LDA	SECTNO	;THEN SECTOR #
	CMA		;..COMPLEMENTED..
	CALL	SEND	;..SECTOR #
	RET		;FROM SENDHDR
;
;---->	SENDSEC: SEND THE DATA SECTOR
;
;WHILE SENDING THE SECTOR, THE "DATAFLG" IS SET
;SUCH THAT IF "V" (VIEW THE FILE) WAS REQUESTED,
;THE "SHOW" ROUTINE WILL PRINT THE DATA, BUT NOT
;THE HDR OR CKSUM, OR ANY NON-FATAL MSGS.
;
SENDSEC	MVI	A,1	;SHOW NOW AT DATA..
	STA	DATAFLG	;..FOR VIEW COMMAND
	MVI	C,0	;INIT CKSUM
	LXI	H,BASE+80H	;POINT TO BUFFER
SENDC	MOV	A,M	;GET A CHAR
	CALL	SEND	;SEND IT
	INR	L	;POINT TO NEXT CHAR
	JNZ	SENDC	;LOOP IF <100H
	XRA	A	;SHOW NOT INTO DATA..
	STA	DATAFLG	;..FOR VIEW COMMAND
	RET		;FROM SENDSEC
;
;---->	SENDCKS: SEND THE CHECKSUM
;
SENDCKS	MOV	A,C	;SEND THE..
	CALL	SEND	;..CHECKSUM
	RET		;FROM SENDCKS
;
;---->	GETACK: GET THE ACK ON THE SECTOR
;
;RETURNS WITH CARRY CLEAR IF ACK RECEIVED.
;IF AN ACK IS NOT RECEIVED, THE ERROR COUNT
;IS INCREMENTED, AND IF LESS THAN "ERRLIM",
;CARRY IS SET AND CONTROL RETURNS.  IF THE
;ERROR COUNT IS AT "ERRLIM", THE PROGRAM
;ABORTS IF IN "QUIET" MODE, OR ASKS THE
;USER FOR QUIT/RETRY IF NOT.
;
GETACK	MVI	B,10	;WAIT 10 SECONDS MAX
	CALL	RECVDG	;RECV W/GARBAGE COLLECT
	JC	GETATOT	;TIMED OUT
	CPI	ACK	;OK? (CARRY OFF IF =)
	RZ		;YES, RET FROM GETACK
	MOV	B,A	;SAVE CHAR
	LDA	QFLG	;QUIET..
	ORA	A	;..MODE?
	JZ	ACKERR	;..YES, NO MSG
	MOV	A,B	;GET CHAR
	CALL	HEXO	;PRINT IN HEX
	CALL	ILPRT	;PRINT:
	DB	'H RCD, NOT ACK',CR,LF,0
;
;TIMEOUT OR ERROR ON ACK - BUMP ERROR COUNT
;
ACKERR	LDA	ERRCT	;GET COUNT
	INR	A	;BUMP IT
	STA	ERRCT	;SAVE BACK
	CPI	ERRLIM	;AT LIMIT?
	RC		;NOT AT LIMIT
;
;REACHED ERROR LIMIT
;
	LDA	VSEEFLG	;VIEWING..
	ORA	A	;..FILE?
	JZ	GACKV	;YES, ASK QUIT/RETRY
	LDA	QFLG	;QUIET..
	ORA	A	;..MODE?
	JZ	CSABORT ;..YES, NO MSG
;
GACKV	CALL	CKQUIT	;SEE IF WANT TO QUIT
	STC		;TO SHOW NO ACK
	RZ		;KEEP ON TRYIN'
;
CSABORT	CALL	ERXIT
	DB	'CAN''T SEND SECTOR '
	DB	'- ABORTING',CR,LF,'$'
;
;TIMEOUT GETTING ACK
;
GETATOT	LDA	QFLG	;QUIET..
	ORA	A	;..MODE?
	JZ	ACKERR	;YES, NO MSG
	CALL	ILPRT	;PRINT:
	DB	'TIMEOUT ON ACK',CR,LF,0
	JMP	ACKERR
; 
ABORT	LXI	SP,STACK
;
ABORTL	MVI	B,1	;1 SEC. W/O CHARS.
	CALL	RECV
	JNC	ABORTL	;LOOP UNTIL SENDER DONE
	MVI	A,NAK	;NEGATIVE ACK
	CALL	SEND	;TELL SENDING END
	CALL	ILPRT	;EXIT WITH ABORT MSG
	DB	'MODEM PROGRAM CANCELLED',CR,LF,0
	JMP	CKDIS	;CHECK FOR DISCONN.
;
;---->	INCRSNO: INCREMENT SECTOR #
;
INCRSNO	LDA	SECTNO	;INCR..
	INR	A	;..SECT..
	STA	SECTNO	;..NUMBER
	RET
;
;---->	ERASFIL: ERASE THE INCOMING FILE.
;
;IF IT EXISTS, ASK IF IT MAY BE ERASED.
;
ERASFIL	LXI	D,FCB	;POINT TO CTL BLOCK
	MVI	C,SRCHF ;SEE IF IT..
	CALL	BDOS	;..EXISTS
	INR	A	;FOUND?
	RZ		;..NO, RETURN
	CALL	ILPRT	;PRINT:
	DB	'++FILE EXISTS, TYPE Y TO ERASE: ',0
	CALL	KEYIN	;GET CHAR
	PUSH	PSW
	CALL	TYPE	;ECHO
	CALL	CRLF	;BACK TO START OF LINE
	POP	PSW
	ANI	5FH	;MAKE UPPER CASE
	CPI	'Y'	;WANT ERASED?
	JNZ	CKDIS	;QUIT IF NOT ERASE
;
;ERASE OLD FILE
;
	LXI	D,FCB	;POINT TO FCB
	MVI	C,ERASE	;GET BDOS FNC
	CALL	BDOS	;DO THE ERASE
	RET		;FROM "ERASFIL"
;
;---->	MAKEFIL: MAKES THE FILE TO BE RECEIVED
;
MAKEFIL	LXI	D,FCB	;POINT TO FCB
	MVI	C,MAKE	;GET BDOS FNC
	CALL	BDOS	;TO THE MAKE
	INR	A	;FF=BAD?
	RNZ		;OPEN OK
;DIRECTORY FULL - CAN'T MAKE FILE
	CALL	ERXIT
	DB	'++ERROR - CAN''T MAKE FILE',CR,LF
	DB	'++DIRECTORY MUST BE FULL',CR,LF,'$'
;
;---->	OPENFIL: OPENS THE FILE TO BE SENT
;
OPENFIL	LXI	D,FCB	;POINT TO FILE
	MVI	C,OPEN	;GET FUNCTION
	CALL	BDOS	;OPEN IT
	INR	A	;OPEN OK?
	JNZ	OPENOK	;..YES
	CALL	ERXIT	;..NO, ABORT
	DB	'CAN''T OPEN FILE$'
;
OPENOK	CALL	ILPRT	;PRINT:
	DB	'FILE OPEN, EXTENT LENGTH: ',0
	LDA	FCB+15	;GET # SECTORS
	CALL	HEXO	;PRINT IN HEX
	CALL	ILPRT	;PRINT:
	DB	'H',CR,LF,0
	RET
;
;---->	CLOSFIL: CLOSES THE RECEIVED FILE
;
CLOSFIL	LXI	D,FCB	;POINT TO FILE
	MVI	C,CLOSE	;GET FUNCTION
	CALL	BDOS	;CLOSE IT
	INR	A	;CLOSE OK?
	RNZ		;..YES, RETURN
	CALL	ERXIT	;..NO, ABORT
	DB	'CAN''T CLOSE FILE$'
;
;---->	RDSECT: READS A SECTOR
;
;FOR SPEED, THIS ROUTINE BUFFERS UP 16
;SECTORS AT A TIME.
;
RDSECT	LDA	SECINBF	;GET # SECT IN BUFF.
	DCR	A	;DECREMENT..
	STA	SECINBF	;..IT
	JM	RDBLOCK	;EXHAUSTED?  NEED MORE.
	LHLD	SECPTR	;GET POINTER
	LXI	D,BASE+80H	;TO DATA
	CALL	MOVE128	;MOVE TO BUFFER
	SHLD	SECPTR	;SAVE BUFFER POINTER
	RET		;FROM "READSEC"
;
;BUFFER IS EMPTY - READ IN ANOTHER BLOCK OF 16
;
RDBLOCK	LDA	EOFLG	;GET EOF FLAG
	CPI	1	;IS IT SET/
	STC		;TO SHOW EOF
	RZ		;GOT EOF
	MVI	C,0	;SECTORS IN BLOCK
	LXI	D,DBUF	;TO DISK BUFFER
;
RDSECLP	PUSH	B
	PUSH	D
	MVI	C,STDMA	;SET DMA..
	CALL	BDOS	;..ADDR
	LXI	D,FCB
	MVI	C,READ
	CALL	BDOS
	POP	D
	POP	B
	ORA	A	;READ OK?
	JZ	RDSECOK	;YES
	DCR	A	;EOF?
	JZ	REOF	;GOT EOF
;
;READ ERROR
;
	CALL	ERXIT
	DB	'++FILE READ ERROR$'
;
RDSECOK	LXI	H,80H	;ADD LENGTH OF ONE SECTOR...
	DAD	D	;...TO NEXT BUFF
	XCHG		;BUFF TO DE
	INR	C	;MORE SECTORS?
	MOV	A,C	;GET COUNT
	CPI	16	;DONE?
	JZ	RDBFULL	;..YES, BUFF IS FULL
	JMP	RDSECLP	;READ MORE
;
REOF	MVI	A,1
	STA	EOFLG	;SET EOF FLAG
	MOV	A,C
;
;BUFFER IS FULL, OR GOT EOF
;
RDBFULL	STA	SECINBF	;STORE SECTOR COUNT
	LXI	H,DBUF	;INIT BUFFER..
	SHLD	SECPTR	;..POINTER
	LXI	D,BASE+80H	;RESET..
	MVI	C,STDMA	;..DMA..
	CALL	BDOS	;..ADDR
	JMP	RDSECT	;PASS SECT TO CALLER
;
;---->	WRSECT: WRITE A SECTOR
;
;WRITES THE SECTOR INTO A BUFFER.  WHEN 16
;HAVE BEEN WRITTEN, WRITES THE BLOCK TO DISK.
;
;ENTRY POINT "WRBLOCK" FLUSHES THE BUFFER AT EOF.
;
WRSECT	LHLD	SECPTR	;GET BUFF ADDR
	XCHG		;TO DE FOR MOVE
	LXI	H,BASE+80H	;FROM HERE
	CALL	MOVE128	;MOVE TO BUFFER
	XCHG		;SAVE NEXT..
	SHLD	SECPTR	;..BLOCK POINTER
	LDA	SECINBF	;BUMP THE..
	INR	A	;..SECTOR #..
	STA	SECINBF	;..IN THE BUFF
	CPI	16	;HAVE WE 16?
	RNZ		;NO, RETURN
;
;---->	WRBLOCK: WRITES A BLOCK TO DISK
;
WRBLOCK	LDA	SECINBF	;# SECT IN BUFFER
	ORA	A	;0 MEANS END OF FILE
	RZ		;NONE TO WRITE
	MOV	C,A	;SAVE COUNT
	LXI	D,DBUF	;POINT TO DISK BUFF
;
DKWRLP	PUSH	H
	PUSH	D
	PUSH	B
	MVI	C,STDMA	;SET DMA
	CALL	BDOS	;TO BUFFER
	LXI	D,FCB	;THEN WRITE
	MVI	C,WRITE	;..THE..
	CALL	BDOS	;..BLOCK
	POP	B
	POP	D
	POP	H
	ORA	A
	JNZ	WRERR	;OOPS, ERROR
	LXI	H,80H	;LENGTH OF 1 SECT
	DAD	D	;HL= NEXT BUFF
	XCHG		;TO DE FOR SETDMA
	DCR	C	;MORE SECTORS?
	JNZ	DKWRLP	;..YES, LOOP
	XRA	A	;GET A ZERO
	STA	SECINBF	;RESET # OF SECTORS
	LXI	H,DBUF	;RESET BUFFER..
	SHLD	SECPTR	;..POINTER
;
RSDMA	LXI	D,BASE+80H ;RESET..
	MVI	C,STDMA	;..DMA..
	CALL	BDOS	;..ADDR
	RET
;
WRERR	CALL	RSDMA	;RESET DMA TO NORM.
	CALL	ILPRT	;PRINT:
	DB	'++ERROR WRITING FILE',CR,LF,0
	JMP	ABORT	;EXIT
;
;---->	RECV: RECEIVE A CHARACTER
;
;TIMEOUT TIME IS IN B, IN SECONDS.  ENTRY VIA
;"RECVDG" DELETES GARBAGE CHARACTERS ON THE
;LINE.  FOR EXAMPLE, HAVING JUST SENT A SECTOR,
;CALLING RECVDG WILL DELETE ANY LINE-NOISE-INDUCED
;CHARACTERS "LONG" BEFORE THE ACK/NAK WOULD
;BE RECEIVED.
;
RECVDG	EQU	$	;RECEIVE W/GARBAGE DELETE
	LDA	MODDATP	;GET A CHAR
	LDA	MODDATP	;..TOTALLY PURGE UART
;
RECV	PUSH	D	;SAVE
;
	IF	FASTCLK	;4MHZ?
	MOV	A,B	;GET TIME REQUEST
	ADD	A	;DOUBLE IT
	MOV	B,A	;NEW TIME IN B
	ENDIF
;
MSEC	LXI	D,50000	;1 SEC DCR COUNT
MWTI	LDA	MODCTLP	;CHECK STATUS
	ANI	MODRCVB	;ISOLATE BIT
	CPI	MODRCVR	;READY?
	JZ	MCHAR	;GOT CHAR
	DCR	E	;COUNT..
	JNZ	MWTI	;..DOWN..
	DCR	D	;..FOR..
	JNZ	MWTI	;..TIMEOUT
	DCR	B	;MORE SECONDS?
	JNZ	MSEC	;YES, WAIT
;
;MODEM TIMED OUT RECEIVING
;
	POP	D	;RESTORE D,E
	STC		;CARRY SHOWS TIMEOUT
	RET
;
;GOT CHAR FROM MODEM
;
MCHAR	LDA	MODDATP	;READ THE CHAR
	POP	D	;RESTORE DE
;
;CALC CHECKSUM
;
	PUSH	PSW	;SAVE THE CHAR
	ADD	C	;ADD TO CHECKSUM
	MOV	C,A	;SAVE CHECKSUM
;
;CHECK IF MONITORING REC'D DATA
;
	LDA	RSEEFLG	;SEE RECEIVED..
	ORA	A	;..DATA?
	JZ	MONIN	;..YES
;
;CHECK IF "VIEWING" AND THIS IS A DATA CHAR
;
	LDA	VSEEFLG	;VIEWING..
	ORA	A	;..DATA?
	JNZ	NOMONIN	;..NO
;
;"VIEW" REQUESTED.  SHOW THE CHAR IT IS DATA
;
	LDA	DATAFLG	;GET DATA FLAG
	ORA	A	;TEST IT
	JZ	NOMONIN	;..OFF, NOT DATA
;
MONIN	POP	PSW	;..IS DATA,
	PUSH	PSW	;GET IT,
	CALL	SHOW	;..AND SHOW IT
;
NOMONIN	POP	PSW	;RESTORE CHAR
	ORA	A	;CARRY OFF: NO ERROR
	RET		;FROM "RECV"
;
;---->	SEND: SEND A CHARACTER TO THE MODEM
;
SEND	PUSH	PSW	;SAVE THE CHAR
;
;CHECK IF MONITORING SENT DATA
;
	LDA	SSEEFLG	;CHECK IF MONITORING..
	ORA	A	;..SENT DATA
	JZ	MONOUT	;..YES
;
;CHECK IF "VIEWING" THE FILE
;
	LDA	VSEEFLG	;GET VIEW FLAG
	ORA	A	;TEST IT
	JNZ	NOMONOT	;NO
	LDA	DATAFLG	;IS THIS
	ORA	A	;..DATA?
	JZ	NOMONOT	;..NO.
;
MONOUT	POP	PSW	;GET THE CHAR
	PUSH	PSW	;SAVE IT
	CALL	SHOW	;SHOW IT
;
NOMONOT	POP	PSW	;RESTORE CHAR
	PUSH	PSW	;SAVE IT
	ADD	C	;CALC CKSUM
	MOV	C,A	;SAVE CKSUM
SENDW	LDA	MODCTLP	;GET STATUS
	ANI	MODSNDB	;ISOLATE READY BIT
	CPI	MODSNDR	;READY?
	JNZ	SENDW	;..NO, WAIT
	POP	PSW	;GET CHAR
	STA	MODDATP	;OUTPUT IT
	RET		;FROM "SEND"
;
;---->	WAITNAK: WAITS FOR INITIAL NAK
;
;TO ENSURE NO DATA IS SENT UNTIL THE RECEIVING
;PROGRAM IS READY, THIS ROUTINE WAITS FOR THE
;THE FIRST TIMEOUT-NAK FROM THE RECEIVER.
;(E) CONTAINS THE # OF SECONDS TO WAIT.
;
WAITNAK	LDA	VSEEFLG	;VIEWING?
	ORA	A
	JZ	WAITNPR	;PRINT MSG
	LDA	QFLG	;QUIET..
	ORA	A	;..MODE?
	JZ	WAITNLP	;YES, SKIP MSG
;
WAITNPR	CALL	ILPRT	;PRINT:
	DB	'AWAITING INITIAL NAK',CR,LF,0
;
WAITNLP	MVI	B,1	;TIMEOUT DELAY
	CALL	RECV	;DID WE GET..
	CPI	NAK	;..A NAK?
	RZ		;YES, SEND BLOCK
	DCR	E	;80 TRIES?
	JZ	ABORT	;YES, ABORT
	JMP	WAITNLP	;NO, LOOP
;
;---->	INITADR: INIT'S CP/M BDOS ADDRESSES
;
;THIS ROUTINE FILLS IN THE ADDRESSES OF VARIOUS
;JMP AND CALL INSTRUCTIONS, SO THAT CP/M BDOS
;IS BYPASSED WHILE ACCESSING THE CONSOLE.  THIS
;IS DONE TO ALLOW CHARACTERS SUCH AS CONTROL-C
;AND CONTROL-S TO BE KEYED WHILE IN TERMINAL
;MODE, WITHOUT CP/M INTERPRETING THEM.
;
INITADR	LHLD	BASE+1	;GET WARM BOOT ADDR
	LXI	D,3	;LENGTH OF A 'JMP'
	DAD	D	;TO CONSOLE STAT
	SHLD	VSTAT+1	;MODIFY CALL
	DAD	D	;TO CONSOLE IN
	SHLD	VKEYIN+1 ;MODIFY CALL
	DAD	D	;TO CONSOLE OUT
	SHLD	VTYPE+1	;MODIFY CALL
	RET
;
;---->	PROCOPT: PROCESS COMMAND OPTIONS
; 
;1) SAVES THE PRIMARY OPTION IN 'OPTION';
;2) SCANS THE SUB-OPTION CHARACTERS, AND FOR
;EACH FOUND, ZEROS THE APPROPRIATE ENTRY IN
;THE OPTION TABLE.  FOR EXAMPLE, IF 'D' IS 
;CODED (DISCONNECT) THEN THE 'D' STORED AT
;'DISCFLG' IS SET TO 0 SO IT CAN BE TESTED
;LATER.
;
PROCOPT	LXI	D,FCB+1	;TO PRIMARY OPT.
	LDAX	D	;GET PRIMARY
	STA	OPTION	;SAVE IT
;
OPTLP	INX	D	;TO SECONDARY OPTION
	LDAX	D	;GET CHAR
;
;IF YOU MOD THIS PROGRAM FOR >7 OPTIONS,
;YOU MUST CHANGE THE FOLLOWING, SINCE
;THERE WON'T BE A ' ' AFTER THE OPTION
;IF A BAUD RATE WAS SPECIFIED.
;
	CPI	' '	;NO MORE OPT'NS?
	JZ	ENDOPT	;..YES
;SET THE APPROP. OPT: STORE 0 IN IT
	LXI	H,OPTBL	;HL = ADDR OF 'OAQDSRV'
	MVI	B,OPTBE-OPTBL ;OPT TABLE LEN
;
OPTCK	CMP	M	;FOUND THE OPTION?
	JNZ	OPTNO	;NO, DON'T SET IT
	MVI	M,0	;SET THE OPTION
	JMP	OPTLP	;GET NEXT OPTION
;
OPTNO	INX	H	;TO NEXT
	DCR	B	;MORE?
	JNZ	OPTCK
;OPTION NOT IN TABLE
	JMP	BADOPT	;SHOW BAD SUB OPTION
;
;IF "VIEW" WAS ASKED FOR, SET QUIET FLAG
;
ENDOPT	LDA	VSEEFLG	;VIEW..
	ORA	A	;..ASKED FOR?
	RNZ		;..NO, RET FROM 'PROCOPT'
	STA	QFLG	;YES, NO HDR/CKSUM PRT
	RET		;FROM 'PROCOPT'
;
;DONE - CLOSE UP SHOP
;
DONE	LDA	VSEEFLG	;VIEWING?
	ORA	A
	JZ	DONETC	;SHOW MSG
	LDA	QFLG	;QUIET
	ORA	A	;..MODE?
	JZ	DONECTE	;YES, CK TERM/ECHO
;
DONETC	CALL	ILPRT
	DB	CR,LF,'TRANSFER COMPLETE' 
	DB	CR,LF,0
;
;CHECK IF TERMINAL OR ECHO SUB COMMAND
;WAS SPECIFIED
;
DONECTE	LDA	TERMFLG	;TERM?
	ORA	A
	JZ	TERM	;..YES
	LDA	ECHOFLG	;ECHO?
	ORA	A
	JZ	TRMECHO	;..YES
;
;FALL INTO 'CKDIS'
;
;---->	CKDIS: CHECK IF DISCONNECT REQUESTED
;
;THIS ROUTINE IS JUMPED TO AT THE END OF
;PROCESSING, AND DISCONNECTS THE PHONE IF
;'D' WAS SPECIFIED AS A SUB-OPTION.
;
CKDIS	LDA	DISCFLG	;CHECK 'D' FLAG
	ORA	A	;REQUESTED?
;
	IF	MMII
	JNZ	NDIS	;..NO, JUST EXIT
	ENDIF
;
	IF	NOT MMII
	JNZ	EXIT
	ENDIF
;
;AWAIT C/R TO DISC. SO WE DON'T LOSE THE PHONE
;
	CALL	ILPRT
	DB	CR,LF,'PRESS RETURN TO DISCONNECT:',0
	CALL	KEYIN
	PUSH	PSW
	CALL	CRLF
	POP	PSW
	CPI	CR
	JNZ	CKDIS	;ASK AGAIN
;
;---->	DISCONN: DISCONNECT THE PHONE
;
DISCONN	EQU	$
;
	IF	MMII
	XRA	A	;GET DISCONNECT VALUE
	STA	MODCTL2	;DISCONNECT
	ENDIF
;
	CALL	ILPRT	;PRINT:
	DB	'++DISCONNECTED++',0
	JMP	EXIT
;
;NO DISCONNECT, TYPE MSG AS REMINDER THAT PHONE'S
;OFF HOOK
;
	IF	MMII
NDIS	LDA	QFLG	;QUIET..
	ORA	A	;..MODE?
	JZ	EXIT	;..YES, NO MSG
	CALL	ILPRT
	DB	CR,LF,'++DON''T FORGET - THE MODEM IS '
	DB	'NOT DISCONNECTED++',CR,LF
	DB	'USE "MODEM D" TO DISCONNECT',CR,LF,0
	JMP	EXIT
;
	ENDIF
;
;---->	INITMOD: INITIALIZES THE MODEM
;
;THIS ROUTINE IS USED TO INITIALIZE SERIAL
;BOARDS, OR SETUP S-100 MODEM BOARDS.
;JUST RETURNS IF NO INITIALIZATION REQUIRED.
;
INITMOD:
;
	IF	INITREQ  ;REQUIRE INIT?
	MVI	A,INITC1 ;GET 1ST INIT CHAR
	STA	MODCTLP	 ;OUTPUT IT
	NOP ! NOP! NOP	 ;DELAY FOR USART
	NOP ! NOP
	MVI	A,INITC2 ;GET 2ND INIT CHAR
	STA	MODCTLP	 ;OUTPUT IT
	NOP ! NOP! NOP	 ;DELAY FOR USART
	NOP ! NOP
	ENDIF
;
	IF	MMII
	CALL	GETBAUD	;GET BAUD RATE
	LDA	ANSWFLG		;ANSWER..
	ORA	A
	MVI	B,ANSWMOD	;SET ANSWER MODE
	JZ	INITM1
	LDA	ORIGFLG		;GET ORIGINATE FLAG
	ORA	A
	MVI	B,ORIGMOD	;SET ORIGINATE MODE
	JZ	INITM1
	LDA	HOLDD		;NEITHER - GET LAST VALUE
	MOV	B,A		;STORE IN B
;
INITM1:	MOV	A,B		;GET MODE
	STA	HOLDD		;SAVE VALUE
	MOV	A,C		;GET BAUD RATE INDICATOR
	ORA	A		;ZEBO IF 110 BAUD
	MOV	A,B		;GET MODE
	JZ	OFFHOOK		;DO OFFHOOK
	ORI	1		;SET 300 BAUD
;GO OFFHOOK IN REQUESTED (ORIG/ANSW) MODE
;
OFFHOOK	LXI	H,4000	;DELAY AMT
;
OFFDLY	DCR	L
	JNZ	OFFDLY
	DCR	H
	JNZ	OFFDLY
	STA	MODCTL2	;GO OFF HOOK
	RET
	ENDIF		;MMII
;
	IF	MMII
GETBAUD	LDA	FCB+9	;GET FILETYPE
	CPI	' '	;DEFAULT?
	JNZ	GETBAU1	;NO - DO BAUD RATE STUFF
	MVI	C,1	;SET 300 BAUD
	MVI	B,15H	;SET 1 STOP BIT
	JMP	GETBAU2
;
; CONVERT BAUD RATE TO BINARY
GETBAU1	CALL	CVBIN	;CONVERT TO BINARY
	PUSH	H	;SAVE BAUD RATE
	MVI	C,0	;ANTICIPATE 110 BAUD
	MVI	B,15H	;SET 2 STOP BITS
	LXI	D,-110	;GET CONSTANT
	DAD	D	;SUBTRACT
	MOV	A,H
	ORA	L
	POP	H
	JZ	GETBAU2	;110 BAUD
	MVI	B,15H	;SET 1 STOP BIT
	INR	C
	LXI	D,-300	;GET CONSTANT
	DAD	D
	MOV	A,H
	ORA	L
	JNZ	BADRATE	;INVALID
;
GETBAU2	MOV	A,B	;GET SET UP
	STA	MODCTLP	;INITIALIZE MODEM FOR STOP BITS..
	RET		;..DATA BITS, ETC.
	ENDIF		;DCH
;
	IF COMCARD	;APPLE COMMUNCATIONS CARD
GETBAUD	LDA	FCB+9	;GET FILETYPE
	CPI	' '	;DEFAULT?
	JNZ	GETBAU1 ;NO - DO BAUD RATE STUFF
	MVI	B,11H	;300 BAUD STATUS CODE
	JMP	GETBAU2
;  CONVERT BAUD RATE TO BINARY
GETBAU1	CALL	CVBIN	;CONVERT TO BINARY
	PUSH	H	;SAVE BAUD RATE
	MVI	B,52H	;ANTICIPATE 110 BAUD
	LXI	D,-110	;GET CONSTANT
	DAD	D	;SUBTRACT
	MOV	A,H	
	ORA	L
	POP	H
	JZ	GETBAU2	;110 BAUD
	MVI	B,11H	;300 BAUD STATUS CODE
	INR	C
	LXI	D,-300	;GET CONSTANT
	DAD	D
	MOV	A,H
	ORA	L
	JNZ	BADRATE	;INVALID RATE (300 OR 110 ONLY)
GETBAU2	MOV	A,B	;GET SET UP
	STA	MODCTLP	;STORE TO STATUS PORT
	RET
	ENDIF	;COMCARD
;
	IF	COMCARD OR MMII
; ROUTINE TO CONVERT BAUD RATE TO BINARY
;
CVBIN:	LXI	D,FCB+9	;TO ASCII VALUE
	LXI	H,0	;INIT BINARY RESULT
;
DECLP	LDAX	D	;GET ASCII DIGIT
	INX	D	;TO NEXT DIGIT
	CPI	' '	;BLANK ONE?
	JZ	DECLP	;..YES, SKIP IT
	CPI	'0'	;VALIDATE IT
	JC	BADRATE	;ERROR
	CPI	'9'+1	;VALIDATE
	JNC	BADRATE	;ERROR
	SUI	'0'	;MAKE DIGIT BINARY
;
;MULTIPLY PREV VALUE BY 10
;
	MOV	B,H	;SET UP FOR
	MOV	C,L	;MULTIPLY BY 10
	DAD	H	;MULTIPLY BY 2
	DAD	H	;X 2 = 4
	DAD	B	;+ 1 = 5
	DAD	H	;X 2 = 10
	ADD	L	;ADD IN DIGIT
	MOV	L,A	;SAVE BACK
	JNZ	DIGNC	;NO CARRY?
	INR	H	;ADD IN CARRY
;CHECK IF DONE?
DIGNC	MOV	A,E	;SEE IF PAST
	CPI	FCB+12	;..LAST DIGIT
	JNZ	DECLP	;NO, LOOP
	RET
;
;INVALID BAUD RATE
;
BADRATE	CALL	ERXIT
	DB	'++INVALID BAUD RATE++$'
	ENDIF		;PMMI OR DCH
;
;THE FOLLOWING PROVIDES A RETURN FROM INITMOD
	IF	NOT MMII AND NOT COMCARD
	RET		;**THIS MUST BE HERE**
	ENDIF		;NOT PMMI AND NOT DCH
;
;---->	MOVEFCB: MOVES FCB(2) TO FCB
;
;I ATTEMPTED TO MAKE THE MODEM COMMAND 'NATURAL',
;I.E. MODEM SEND FILENAME (MODEM S FN.FT) RATHER
;THAT MODEM FILENAME SEND (MODEM FN.FT S) SO THIS
;ROUTINE MOVES THE FILENAME FROM THE SECOND FCB
;TO THE FIRST
;
MOVEFCB	LXI	H,FCB+16 ;FROM
	LXI	D,FCB	;TO
	MVI	B,16	;LEN
	CALL	MOVE	;DO THE MOVE
	XRA	A	;GET 0
	STA	FCBSNO	;ZERO SECTOR #
	STA	FCBEXT	;..AND EXTENT
	RET
;
;---->	SHOW: SHOWS CHAR SENT/RECEIVED
;
;CR, LF, AND TAB ARE SHOWN.  ALL OTHER
;NON-PRINTABLE CHARACTERS ARE SHOWN IN
;HEX AS (XX)
;
SHOW	CPI	LF	;LF?
	JZ	CTYPE	;..YES, TYPE IT
	CPI	CR	;CR?
	JZ	CTYPE	;..YES, TYPE IT
	CPI	09	;TAB
	JZ	CTYPE	;..YES, TYPE IT
	CPI	' '	;CTL-CHR?
	JC	SHOWHEX	;YES, SHOW IN HEX
	CPI	7FH	;DEL?
	JC	CTYPE	;NO, TYPE THE CHAR
;
SHOWHEX	PUSH	PSW	;SAVE THE CHAR
	MVI	A,'('	;TYPE..
	CALL	CTYPE	;..'('
	POP	PSW	;THEN..
	CALL	HEXO	;..THE CHAR
	MVI	A,')'	;THEN..
	JMP	CTYPE	;..')' AND RETURN.
;
;---->	CTYPE: TYPES VIA CP/M SO TABS ARE EXPANDED
;
CTYPE	PUSH	B	;SAVE..
	PUSH	D	;..ALL..
	PUSH	H	;..REGS
	MOV	E,A	;CHAR TO E
	MVI	C,WRCON	;GET BDOS FNC
	CALL	BDOS	;PRIN THE CHR
	POP	H	;RESTORE..
	POP	D	;..ALL..
	POP	B	;..REGS
	RET		;FROM "CTYPE"
;
CRLF	MVI	A,CR
	CALL	TYPE
	MVI	A,LF
;
;---->	TYPE: TYPE VIA DIRECT CBIOS ACCESS
;WE ASSUME CBIOS MAY DESTROY SOME REGISTERS,
;SO SAVE THEM ALL.
;
;THIS ROUTINE BYPASSES CP/M'S CTL-S, CTL-C
;TESTS.
;
TYPE	PUSH	PSW	;SAVE CHAR
	PUSH	B	;AND OTHER REGISTERS
	PUSH	D
	PUSH	H
	MOV	C,A	;FOR BIOS
VTYPE	CALL	$-$	;MODIFIED AT INIT
	POP	H	;RESTORE REGISTERS
	POP	D
	POP	B
	POP	PSW	;..AND CHAR
	RET		;FROM "TYPE"
;
;---->  STAT: KEYBOARD STATUS
;
;SAVE ALL REGISTERS, EXCEPT A, IN CASE
;CBIOS CLOBBERS THEM.
;
STAT	PUSH	B
	PUSH	D
	PUSH	H
VSTAT	CALL	$-$	;ADDR SET AT INIT
	POP	H
	POP	D
	POP	B
	ORA	A	;0 => NOT READY
	RET
;
;---->  KEYIN: KEYBOARD INPUT
;
;SAVE ALL REGISTERS, EXCEPT A, IN CASE
;CBIOS CLOBBERS THEM.
;
KEYIN	PUSH	B
	PUSH	D
	PUSH	H
VKEYIN	CALL	$-$	;ADDR SET AT INIT
	POP	H
	POP	D
	POP	B
	ANI	7FH	;STRIP PARITY IF THERE
	RET		;FROM KEYIN
;
;---->  HEXO: HEX OUTPUT
;
HEXO	PUSH	PSW	;SAVE FOR RIGHT DIGIT
	RAR		;RIGHT..
	RAR		;..JUSTIFY..
	RAR		;..LEFT..
	RAR		;..DIGIT..
	CALL	NIBBL	;PRINT LEFT DIGIT
	POP	PSW	;RESTORE RIGHT
;
NIBBL	ANI	0FH	;ISOLATE DIGIT
	CPI	10	;IS IS <10?
	JC	ISNUM	;YES, NOT ALPHA
	ADI	7	;ADD ALPHA BIAS
;
ISNUM	ADI	'0'	;MAKE PRINTABLE
	JMP	TYPE	;..THEN TYPE IT
;
;---->	CKQUIT: QUIT/RETRY AFTER MULTIPLE ERRS.
;
;RETURNS W/ ZERO SET IF "RETRY" ASKED FOR
;
CKQUIT	XRA	A	;ZERO..
	STA	ERRCT	;..ERROR COUNT
	CALL	ILPRT	;PRINT:
	DB	'MULTIPLE ERRORS ENCOUNTERED.  '
	DB	'TYPE Q TO QUIT, R TO RETRY: ',0
	CALL	KEYIN	;QUIT/RETRY
	PUSH	PSW
	CALL	TYPE
	CALL	CRLF
	POP	PSW
	ANI	5FH	;MAKE UPPER CASE
	CPI	'R'	;RETRY?
	RZ		;'KEEP ON TRUCKIN'
	CPI	'Q'	;QUIT?
	JNZ	CKQUIT	;NO, ASK AGAIN
	ORA	A	;SET NON-ZERO
	RET
;
;---->	ILPRT: INLINE PRINT OF MSG
;
;THE CALL TO ILPRT IS FOLLOWED BY A MESSAGE,
;BINARY 0 AS THE END.  BINARY 1 MAY BE USED TO
;PAUSE (MESSAGE 'PRESS RETURN TO CONTINUE')
;
ILPRT	XTHL		;SAVE HL, GET HL=MSG
;
ILPLP	MOV	A,M	;GET CHAR
	ORA	A	;END OF MSG?
	JZ	ILPRET	;..YES, RETURN
	CPI	1	;PAUSE?
	JZ	ILPAUSE	;..YES
	CALL	CTYPE	;TYPE THE MSG
;
ILPNEXT	INX	H	;TO NEXT CHAR
	JMP	ILPLP	;LOOP
;
;PAUSE WHILE TYPING HELP SO INFO DOESN'T
;	SCROLL OFF OF VIDEO SCREENS
;
ILPAUSE	CALL	ILPRT	;PRINT:
	DB	CR,LF,'PRESS RETURN TO CONTINUE'
	DB	CR,LF,0
	CALL	KEYIN	;GET ANY CHAR
	CPI	'C'-40H	;REBOOT?
	JZ	EXIT	;YES.
	JMP	ILPNEXT	;LOOP
;
ILPRET	XTHL		;RESTORE HL
	RET		;PAST MSG
;
;---->	PRTMSG: PRINTS MSG POINTED TO BY (DE)
;
;A '$' IS THE ENDING DELIMITER FOR THE PRINT.
;NO REGISTERS SAVED.
;
PRTMSG	MVI	C,PRINT	;GET BDOS FNC
	JMP	BDOS	;PRINT MESSAGE, RETURN
;
;---->	ERXIT: EXIT PRINTING MSG FOLLOWING CALL
;
ERXIT	POP	D	;GET MESSAGE
	CALL	PRTMSG	;PRINT IT
	CALL	CKDIS	;DISCONNECT?
;
EXIT	LHLD	STACK	;GET ORIGINAL STACK
	SPHL		;RESTORE IT
	RET		;--EXIT-- TO CP/M
;
;MOVE 128 CHARACTERS
;
MOVE128	MVI	B,128	;SET MOVE COUNT
;
;MOVE FROM (HL) TO (DE) LENGTH IN (B)
;
MOVE	MOV	A,M	;GET A CHAR
	STAX	D	;STORE IT
	INX	H	;TO NEXT "FROM"
	INX	D	;TO NEXT "TO"
	DCR	B	;MORE?
	JNZ	MOVE	;..YES, LOOP
	RET		;..NO, RETURN
;	----------------
OPTION	DB	0	;PRIMARY OPTION
;
;DATAFLG IS USED BY THE "V" SUBCOMMAND -
;IT IS 0 WHEN A HEADER OR CKSUM IS BEING
;SENT/RCD, AND 1 IF "VIEWABLE" DATA (THE
;SECTOR ITSELF) IS
;
DATAFLG	DB	0	;AT HEADER, FIRST
;
;
;SUB-OPTION TABLE.  IF AN OPTION IS IN EFFECT,
;	THE CHARACTER IS SET TO BINARY 0
;
OPTBL	EQU	$
ANSWFLG	DB	'A'	;ANSWER MODE
DISCFLG	DB	'D'	;DISCONNECT WHEN DONE
ECHOFLG	DB	'E'	;TO ECHO AFTER XFER
ORIGFLG	DB	'O'	;ORIGINATE MODE
QFLG	DB	'Q'	;QUIET TRANSFER (NO MSGS)
RSEEFLG	DB	'R'	;SEE WHAT'S RECEIVED
SSEEFLG	DB	'S'	;SEE WHAT'S SENT
TERMFLG	DB	'T'	;TO TERM AFTER XFER
VSEEFLG	DB	'V'	;VIEW MESSAGES (NO HDR, ETC)
OPTBE	EQU	$	;END OF OPTIONS
;
RCVSNO	DB	0	;SECT # RECEIVED
SECTNO	DB	0	;CURRENT SECTOR NUMBER 
ERRCT	DB	0	;ERROR COUNT
HOLDD	DB	8EH	;HOLD AREA - LAST DC HAYES CONT CHAR.
;FOLLOWING 3 USED BY DISK BUFFERING ROUTINES
EOFLG	DB	0	;EOF FLAG (1=TRUE)
SECPTR	DW	DBUF
SECINBF	DB	0	;# OF SECTORS IN BUFFER
	DS	60	;STACK AREA
STACK	DS	2	;STACK POINTER
;
;16 SECTOR DISK BUFFER (OVERLAYS HELP MSGS)
;
DBUF	EQU	$	;16 SECTOR DISK BUFFER
;
;INVALID COMMAND
;
BADOPT	CALL	TYPE
	CALL	ILPRT	;EXIT W/ERROR
	DB	': INVALID OPTION ON MODEM '
	DB	'COMMAND - ',CR,LF
 DB 'PRESS RETURN FOR HELP, CTL-C IF NOT',CR,LF,1,0
;
HELP	CALL	ILPRT
 DB 'Format for command is:',cr,lf,cr,lf
 DB 'MODEM # FILENAME',CR,LF,CR,LF
 DB 'Where # is a 1 character primary option,',cr,lf
 DB ' which may be followed by sub-options,',cr,lf
 DB ' and by ".xxx" to set baud rate to xxx'
 DB cr,lf,cr,lf,1
 DB 'Primary Options:',cr,lf
 DB '	S to send a file',cr,lf
 DB '	R to receive a file',cr,lf
 DB '	T to act as a terminal',cr,lf
 DB '	E to act as a computer (echo data)',cr,lf
 DB '	D to disconnect the phone'
 DB '	(Micromodem ][ only)',cr,lf
 DB '	H to print this help file'
 DB cr,lf,cr,lf,1
 DB 'Secondary options:',cr,lf
 DB '	A answer mode',cr,lf
 DB '	O originate mode',cr,lf
 DB '	D disconnect after execution',cr,lf
 DB '	T go to terminal mode after file xfer',cr,lf
 DB '	E go to echo mode after file xfer',cr,lf
 DB '	Q quiet mode - no status msgs',cr,lf
 DB '	R show chars received',cr,lf
 DB '	S show chars sent',cr,lf
 DB '	V view file sent/received (no status)',cr,lf
 DB CR,LF,'FOR EXAMPLES, TYPE: MODEM X',cr,lf,0
	JMP	EXIT
;
EXAM	CALL	ILPRT
 DB 'Send file, originate mode, 300 baud:',CR,LF
 DB '	MODEM SO fn.ft',cr,lf
 DB 'Send another file:',CR,LF
 DB '	MODEM S fn.ft',cr,lf
 DB 'Then send a third file at 110 baud and disconnect:'
 DB CR,LF,'	MODEM SD.110 fn.ft',cr,lf
 DB 'Act as a terminal, originate mode, at 110 baud:',cr,lf
 DB '	MODEM TO.110',CR,LF
 DB '	(Use ctl-D to disconnect)',cr,lf
 DB 'Receive file, answer mode, view it, 600 baud:',cr,lf
 DB '	MODEM RAV.110 fn.ft',cr,lf,0
JMP EXIT
;
; BDOS EQUATES (VERSION 2)
;
RDCON	EQU	1
WRCON	EQU	2
PRINT	EQU	9
CONST	EQU	11	;CONSOLE STAT
OPEN	EQU	15	;0FFH=NOT FOUND
CLOSE	EQU	16	;	"	"
SRCHF	EQU	17	;	"	"
SRCHN	EQU	18	;	"	"
ERASE	EQU	19	;NO RET CODE
READ	EQU	20	;0=OK, 1=EOF
WRITE	EQU	21	;0=OK, 1=ERR, 2=?, 0FFH=NO DIR SPC
MAKE	EQU	22	;0FFH=BAD
REN	EQU	23	;0FFH=BAD
STDMA	EQU	26	;SET DMA
BDOS	EQU	BASE+5
REIPL	EQU	BASE
FCB	EQU	BASE+5CH	;SYSTEM FCB
FCBEXT	EQU	FCB+12		;FILE EXTENT
FCBSNO	EQU	FCB+32		;SECTOR #
FCB2	EQU	BASE+6CH	;SECOND FCB
	END

