;
;XMODEM.ASM V3.2, BY KEITH PETERSEN, W8SDZ
;	   (revised 9/13/80)
;
;CP/M - CP/M FILE TRANSFER PROGRAM
;BASED ON MODEM.ASM V2.0, BY WARD CHRISTENSEN
;THIS PROGRAM IS INTENDED FOR USE ON REMOTE CP/M
;SYSTEMS WHERE IT IS IMPORTANT THAT THE INITIALIZATION
;OF THE MODEM NOT BE CHANGED, SUCH AS WHEN USING
;THE PMMIBYE PROGRAM. THE BAUD RATE AND NUMBER OF BITS
;REMAINS THE SAME AS WHATEVER WAS SET PREVIOUSLY.
;THERE IS NO DISCONNECT, TERMINAL OR ECHO OPTION.
;
;NOTE: THIS FILE WILL ASSEMBLE, WITHOUT NEED FOR
;EDITING, TO WORK WITH A PMMI MM-103 MODEM.  SEE
;EQUATES FOR OTHER OPTIONS INCLUDING SYSTEM CLOCK
;FREQUENCY AND OTHER TYPES OF MODEMS.
;
; Added conditional assembly to prevent filetypes
;'.COM' or '.??#' from being sent to distant end
;and added conditional assembly of test for '.COM'
;filetype on receive as well. See 'NOCOM' below.
; Any filetype ending in '#' will not be sent by
;this program if 'NOCOM' is set to TRUE.
;			9-SEP-80	J.SEYMOUR
;
FALSE	EQU	0
TRUE	EQU	NOT FALSE
;
STDCPM	EQU	TRUE	;TRUE, IS STANDARD CP/M
ALTCPM	EQU	FALSE	;TRUE, IS H8 OR TRS-80 CP/M
;
	IF	STDCPM
BASE	EQU	0	;CP/M BASE ADDRESS
	ENDIF
;
	IF	ALTCPM
BASE	EQU	4200H	;ALTERNATE CP/M BASE ADDRESS
	ENDIF
;
PMMI	EQU	TRUE	;TRUE, IS PMMI
;
DCH	EQU	FALSE	;TRUE, IS D.C. HAYES
;
NOCOM	EQU	TRUE	;TRUE, NO .COM OR .??# FILES
			;SENT OR '.COM' RECEIVED
;
	IF	PMMI
MODCTLP	EQU	0C0H	;PMMI VALUES
MODSNDB EQU	1	;BIT TO TEST FOR SEND
MODSNDR	EQU	1	;VALUE WHEN READY
MODRCVB EQU	2	;BIT TO TEST FOR RECEIVE
MODRCVR	EQU	2	;VALUE WHEN READY
MODDATP EQU	0C1H	;DATA PORT
BAUDRP	EQU	0C2H	;BAUD RATE OUTPUT
MODCTL2	EQU	0C3H	;SECOND CTL PORT
	ENDIF
;
	IF	DCH
MODCTLP	EQU	82H	;D. C. HAYES VALUES
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
MODDATP EQU	80H	;DATA PORT
MODCTL2	EQU	81H	;SECOND CTL PORT
	ENDIF
;
;IF YOU ARE USING AN EXTERNAL MODEM (NOT S-100 PLUG-IN)
;CHANGE THESE EQUATES FOR YOUR MODEM PORT REQUIREMENTS
;
	IF	NOT PMMI AND NOT DCH
MODCTLP	EQU	02H	;PUT YOUR MODEM CONTROL PORT HERE
MODSNDB	EQU	80H	;YOUR BIT TO TEST FOR SEND
MODSNDR	EQU	80H	;YOUR VALUE WHEN READY
MODRCVB	EQU	40H	;YOUR BIT TO TEST FOR RECEIVE
MODRCVR	EQU	40H	;YOUR VALUE WHEN READY
MODDATP	EQU	03H	;YOUR MODEM DATA PORT
	ENDIF		;END OF EXTERNAL MODEM EQUATES
;
ERRLIM	EQU	10	;MAX ALLOWABLE ERRORS
;
FASTCLK	EQU	FALSE	;PUT TRUE HERE FOR 4 MHZ CLOCK
;
;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	;CONTROL-X FOR 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	ILPRT	;PRINT:
	DB	CR,LF
	DB	'XMODEM ver 3.2',CR,LF,0
;
;GET OPTION
;
	LDA	FCB+1	;GET OPTION (S or R)
	PUSH	PSW	;SAVE OPTION
;
;MOVE THE FILENAME FROM FCB 2 TO FCB 1
;
	CALL	MOVEFCB
;
;GOBBLE UP GARBAGE CHARS FROM THE LINE
;PRIOR TO RECEIVE OR SEND
;
	IN	MODDATP
	IN	MODDATP
;
;JMP TO APPROPRIATE FUNCTION
;
	POP	PSW	;GET OPTION
;
       	CPI	'S'	;SEND..
	JZ	SENDFIL	;..A FILE?
;
	CPI	'R'	;RECEIVE..
	JZ	RCVFIL	;..A FILE?
;
;INVALID OPTION
	CALL	ERXIT	;EXIT W/ERROR
	DB	'++INVALID OPTION ON XMODEM '
	DB	'COMMAND++',CR,LF
	DB	'Must be S for SEND or R for '
	DB	'RECEIVE',CR,LF,'$'
;
* * * * * * * * * * * * * * * * * * * * *
*					*
*	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	EXIT	;ALL DONE
;
* * * * * * * * * * * * * * * * * * * * *
*					*
*	RCVFIL: RECEIVE A FILE		*
*					*
* * * * * * * * * * * * * * * * * * * * *
;
;RECEIVES A FILE IN BLOCK FORMAT AS SENT
;BY ANOTHER PERSON DOING "MODEM S FN.FT".
;
RCVFIL	EQU	$
;
	IF	NOCOM
	LXI	H,FCB+9	;POINT TO FILETYPE
	MVI	A,'C'	;1ST LETTER
	CMP	M	;IS IT C ?
	JNZ	CONTINU	;IF NOT, CONTINUE NORMALLY
	INX	H	;GET 2ND LETTER
	MVI	A,'O'	;2ND LETTER
	CMP	M	;IS IT O ?
	JNZ	CONTINU	;IF NOT, CONTINUE NORMALLY
	INX     H	;GET 3RD LETTER
	MVI	A,'M'	;3RD LETTER
	CMP	M	;IS IT M ?
	JNZ	CONTINU	;IF NOT, CONTINUE NORMALLY
	CALL	ERXIT	;EXIT, PRINT ERROR MESSAGE
	DB	'++CAN''T RECEIVE A .COM FILE++'
	DB	CR,LF,CR,LF
	DB	'Rename filetype ".OBJ" and try again'
	DB	CR,LF,'$'
	ENDIF
;
CONTINU CALL	CHEKFIL	;SEE IF FILE EXISTS
	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	EXIT	;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	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	RCVRPT	;YES, IGNORE IT
	CPI	EOT	;END OF TRANSFER?
	STC		;RETURN WITH CARRY..
	RZ		;..SET IF EOT
;
;DIDN'T GET SOH  OR EOT - 
;
;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 - 
;
RCVSABT	CALL	CLOSFIL	;KEEP WHATEVER WE GOT
	CALL	ERXIT
	DB	'++UNABLE TO RECEIVE BLOCK '
	DB	'- ABORTING++',CR,LF,'$'
;
;TIMEDOUT ON RECEIVE
;
RCVSTOT	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 #
;
	JMP	RCVSERR	;BUMP ERROR CT.
;
RCVDATA	MOV	A,D	;GET SECTOR #
	STA	RCVSNO	;SAVE IT
	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
	MVI	B,1	;TIMEOUT LEN.
	CALL	RECV	;GET CHECKSUM
	JC	RCVSTOT	;TIMEOUT
	CMP	D	;CHECKSUM OK?
	JNZ	RCVSERR	;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
;
;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	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
;
SENDSEC	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
	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.
;
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
;
;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
;
CSABORT	CALL	ERXIT
	DB	'++CAN''T SEND SECTOR '
	DB	'- ABORTING++',CR,LF,'$'
;
;TIMEOUT GETTING ACK
;
GETATOT	JMP	ACKERR	;NO MSG
; 
ABORT	LXI	SP,STACK
;
ABORTL	MVI	B,1	;1 SEC. W/O CHARS.
	CALL	RECV
	JNC	ABORTL	;LOOP UNTIL SENDER DONE
	MVI	A,CAN	;CONTROL X
	CALL	SEND	;STOP SENDING END
;
ABORTW	MVI	B,1	;1 SEC W/O CHARS.
	CALL	RECV
	JNC	ABORTW	;LOOP UNTIL SENDER DONE
	MVI	A,' '	;GET A SPACE...
	CALL	SEND	;TO CLEAR OUT CONTROL X
	CALL	ERXIT	;EXIT WITH ABORT MSG
	DB	'XMODEM PROGRAM CANCELLED',CR,LF,'$'
;
;---->	INCRSNO: INCREMENT SECTOR #
;
INCRSNO	LDA	SECTNO	;INCR..
	INR	A	;..SECT..
	STA	SECTNO	;..NUMBER
	RET
;
;---->	CHEKFIL: SEE IF FILE EXISTS
;
;IF IT EXISTS, SAY IT MUST BE ERASED.
;
CHEKFIL	LXI	D,FCB	;POINT TO CTL BLOCK
	MVI	C,SRCHF ;SEE IF IT..
	CALL	BDOS	;..EXISTS
	INR	A	;FOUND?
	RZ		;..NO, RETURN
	CALL	ERXIT	;EXIT, PRINT ERROR MESSAGE
	DB	'++FILE EXISTS, USE A DIFFERENT NAME++'
	DB	CR,LF,'$'
;
;---->	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	'++FILE NOT FOUND++',CR,LF,'$'
;
;CHECK FOR DISTRIBUTION-PROTECTED FILE
;
OPENOK	LDA	FCB+1	;FIRST CHAR OF FILE NAME
	ANI	80H	;CHECK BIT 7
	JZ	OPENOK2	;IT WAS OFF, FILE CAN BE SENT
;
OPENOT	CALL	ERXIT	;EXIT W/MESSAGE
	DB	'++THIS FILE IS NOT FOR DISTRIBUTION, SORRY++'
	DB	CR,LF,'$'
;
OPENOK2	EQU	$
;
	IF	NOCOM
	LXI	H,FCB+11
	MOV	A,M	;CHECK FOR PROTECT ATTR
	ANI	7FH	;REMOVE CP/M 2.x ATTRS
	CPI	'#'	;CHK FOR '#' AS LAST FIRST
	JZ	OPENOT	;IF '#', CAN'T SEND, SHOW WHY
	CPI	'M'	;IF NOT, CHK FOR '.COM'
	JNZ	OPENOK3	;IF NOT, OK TO SEND
	DCX	H
	MOV	A,M	;CHK NEXT CHAR
	ANI	7FH	;STRIP ATTRIBUTES
	CPI	'O'	; 'O'?
	JNZ	OPENOK3	;IF NOT, OK TO SEND
	DCX	H
	MOV	A,M	;NOW CHK FIRST CHAR
	ANI	7FH	;STRIP ATTRIBUTES
	CPI	'C'	; 'C' AS IN '.COM'?
	JNZ	OPENOK3	;IF NOT, CONTINUE
	CALL	ERXIT	;EXIT W/MESSAGE
	DB	'++CAN''T SEND A .COM FILE++'
	DB	CR,LF,'$'
	ENDIF
;
OPENOK3	CALL	ILPRT	;PRINT:
	DB	'FILE OPEN, EXTENT LENGTH: ',0
	LDA	FCB+15	;GET # SECTORS
	CALL	HEXO	;PRINT IN HEX
	CALL	ILPRT	;PRINT H AFTER NUMBER, THEN CR,LF
	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++',CR,LF,'$'
;
;---->	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	;GED 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',CR,LF,'$'
;
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 DHE 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.
	MVI	C,CAN	;CANCEL..
	CALL	SEND	;..SENDER
	CALL	ERXIT	;EXIT W/MSG:
	DB	'++ERROR WRITING FILE++',CR,LF,'$'
;
;---->	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
	IN	MODDATP	;GET A CHAR
	IN	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
;
	IF	NOT DCH
MWTI	IN	MODCTLP	;CHECK STATUS
	ENDIF
;
	IF	DCH
MWTI	IN	MODCTL2	;CHECK STATUS
	ENDIF
;
	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	IN	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
	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
	ADD	C	;CALC CKSUM
	MOV	C,A	;SAVE CKSUM
;
	IF	NOT DCH
SENDW	IN	MODCTLP	;GET STATUS
	ENDIF
;
	IF	DCH
SENDW	IN	MODCTL2	;GET STATUS
	ENDIF
;
	ANI	MODSNDB	;ISOLATE READY BIT
	CPI	MODSNDR	;READY?
	JNZ	SENDW	;..NO, WAIT
	POP	PSW	;GET CHAR
	OUT	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	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	WAITNAK	;NO, LOOP
;
;---->	MOVEFCB: MOVES FCB(2) TO FCB
;
;I ATTEMPTED TO MAKE THE MODEM COMMAND 'NATURAL',
;I.E. MODEM SEND FILENAME (MODEM S FN.FT) RATHER
;THAN 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
;
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"
;
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	CTYPE	;..THEN TYPE IT
;
;---->	ILPRT: INLINE PRINT OF MSG
;
;THE CALL TO ILPRT IS FOLLOWED BY A MESSAGE,
;BINARY 0 AS THE END.
;
ILPRT	XTHL		;SAVE HL, GET HL=MSG
;
ILPLP	MOV	A,M	;GET CHAR
	ORA	A	;END OF MSG?
	JZ	ILPRET	;..YES, RETURN
	CALL	CTYPE	;TYPE THE MSG
	INX	H	;TO NEXT CHAR
	JMP	ILPLP	;LOOP
;
ILPRET	XTHL		;RESTORE HL
	RET		;PAST MSG
;
;---->	ERXIT: EXIT PRINTING MSG FOLLOWING CALL
;
ERXIT	POP	D	;GET MESSAGE
	MVI	C,PRINT	;GET BDOS FNC
	CALL	BDOS	;PRINT MESSAGE
;
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
;
;TEMPORARY STORAGE AREA
;
RCVSNO	DB	0	;SECT # RECEIVED
SECTNO	DB	0	;CURRENT SECTOR NUMBER 
ERRCT	DB	0	;ERROR COUNT
;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
;
DBUF	EQU	$	;16 SECTOR DISK BUFFER
;
; 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
FCB	EQU	BASE+5CH ;SYSTEM FCB
FCBEXT	EQU	FCB+12	;FILE EXTENT
FCBSNO	EQU	FCB+32	;SECTOR #
FCB2	EQU	BASE+6CH ;SECOND FCB
;
	END

