;
;	XMODEM.ASM V4.1, by Keith Petersen, W8SDZ
;	Latest Revision: 2/17/81 Tim Nicholas
;
;	REMOTE 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 and 2 Mhz
;system clock.	See equates for options including
;other modems and 4 Mhz system clock frequency.
;
;Program updates/fixes (these are written in reverse
;order to minimize reading time to find latest update):
;
;
;02/17/81 Added test for "f2" tagged files in OPENOK
;	  for MP/M version 1.1 compatiblity, which
;	  doesn't allow Ctl-C or Ctl-S in "f1" tagged
;	  files. (Tim Nicholas)
;
;02/16/81 Added hex to file size display. Now reports
;	  size in both decimal and (xxxxH) hex. Thanks
;	  to Ben Bronson for the idea. (Tim Nicholas)
;
;02/15/81 Added a software timer to the carrier test
;	  added in SEND and RECV routines. This will
;	  now abort only if carrier is lost for a 
;	  period of 15 seconds. This is only essential
;	  for those using external modems with certain
;	  SIO's, but will provide the PMMI/DCH user
;	  faster recovery in a lost carrier situation
;	  as well. Approx 15 seconds plus 15 seconds
;	  in BYE.COM, compared to 3 minutes at 300
;	  baud with earlier revisions. Thanks to Ben
;	  Bronson for his aid in developing this
;	  revision. (Tim Nicholas)
;
;02/14/81 Corrected error in last update which read
;	  the incorrect port for PMMI in the added
;	  carrier test. (Tim Nicholas)
;	
;01/31/81 Added equates and code for a carrier test.
;	  Test performed in modem I/O routines. This
;	  is required since loss of carrier will go
;	  undetected by BYE.COM, if the loss occurs
;	  after a sucessful XMODEM signon, when using
;	  an external modem and SIO. (Tim Nicholas)
;
;01/17/81 Re-wrote routine to calculate file size so
;         that it works correctly on v2.X systems with
;         extent folding (non-zero extent mask). (BRR)
;
;12/06/80 Re-wrote routine to calculate file size,
;	  added decimal print of file size. (KBP)
;
;12/05/80 Corrected error in use of ext byte that pre-
;	  vented files greater than one extent from 
;	  being sent.     Ron Fowler
;
;12/03/80 Corrected file extent length display. Now
;	  reports correct number of records for files
;	  longer than one extent. Display is now
;	  double precision (xxxxH). Also made some
;	  cosmetic changes by re-arranging the equates.
;	  By Tim Nicholas
;
;10/28/80 Cleaned up file. (KBP)
;
;10/23/80 Expanded conditional assembly of NOCOM routines
;	  into NOCOMS, NOLBS, and NOCOMR equates, to allow
;	  separate conditional assembly of tests for sending
;	  .COM files, sending .??# files, and receiving .COM
;	  files, respectively.	(Dave Hardy)
;
;10/15/80 Added traps for ambiguous file name or
;	  none at all. (KBP)
;
;09/09/80 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.  J.SEYMOUR
;
;NOTE: If you add improvements or otherwise update
;this program, please modem a copy of the new file
;to "TECHNICAL CBBS" in Dearborn, Michigan - phone
;313-846-6127 (110, 300, 450 or 600 baud).  Use the
;filename XMODEM.NEW.	(KBP)
;
FALSE	EQU	0
TRUE	EQU	NOT FALSE
;
;-----------------------------------------------------
;	 --- Conditional Assembly Options ---	      ;
;------------------------------------------------------
;
STDCPM	EQU	TRUE	;TRUE, IS STANDARD CP/M
ALTCPM	EQU	FALSE	;TRUE, IS H8 OR TRS-80 CP/M
;
PMMI	EQU	TRUE 	;TRUE, IS PMMI
DCH	EQU	FALSE	;TRUE, IS D.C. HAYES
;
NOCOMS	EQU	FALSE	;TRUE, NO .COM FILES SENT
NOLBS	EQU	TRUE	;TRUE, NO .??# FILES SENT
NOCOMR	EQU	TRUE	;TRUE, NO .COM FILES RECEIVED
;
FASTCLK EQU	FALSE	;PUT TRUE HERE FOR 4 MHZ CLOCK
;
;------------------------------------------------------
;	     --- Modem Port Equates --- 	      ;
;------------------------------------------------------
;
	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
MODDCDB	EQU	4	;CARRIER DETECT BIT
MODDCDA	EQU	0	;VALUE WHEN ACTIVE
MODDATP EQU	0C1H	;DATA PORT
BAUDRP	EQU	0C2H	;BAUD RATE OUTPUT/MODEM STATUS
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
MODDCDB	EQU	40H	;CARRIER DETECT BIT
MODDCDA	EQU	40H	;VALUE WHEN ACTIVE
MODDATP EQU	80H	;DATA PORT
MODCTL2 EQU	81H	;SECOND CTL PORT
	ENDIF
;
;---> NOTE: DCD (Carrier Detect) values above are for
;	    the Micromodem 100. For DC-Hayes 80-103
;	    the values are different.
;	    MODDCDB  EQU  1  ;Carrier bit (CTS).
;	    MODDCDA  EQU  1  ;Active value.
;
;
;
;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	35H	;PUT YOUR MODEM STATUS PORT HERE
MODSNDB EQU	01H	;YOUR BIT TO TEST FOR SEND
MODSNDR EQU	01H	;YOUR VALUE WHEN READY
MODRCVB EQU	02H	;YOUR BIT TO TEST FOR RECEIVE
MODRCVR EQU	02H	;YOUR VALUE WHEN READY
MODDCDB	EQU	1	;CARRIER DETECT BIT
MODDCDA	EQU	1	;VALUE WHEN ACTIVE
MODDATP EQU	34H	;YOUR MODEM DATA PORT
MODCTL2	EQU	36H	;SECOND CONTROL/STATUS PORT.
	ENDIF		;END OF EXTERNAL MODEM EQUATES
;
;		--- End of Options ---
;------------------------------------------------------
;
;
ERRLIM	EQU	10	;MAX ALLOWABLE ERRORS (10 STANDARD)
;
;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
; 
	IF	STDCPM
BASE	EQU	0	;CP/M BASE ADDRESS
	ENDIF
;
	IF	ALTCPM
BASE	EQU	4200H	;ALTERNATE CP/M BASE ADDRESS
	ENDIF
;
	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 4.1',CR,LF,0
;
;Get option
;
	LDA	FCB+1	;GET OPTION (S or R)
	PUSH	PSW	;SAVE OPTION
;
;Move the filename from FCB2 to FCB1
;
	CALL	MOVEFCB
;
;Gobble up garbage chars from the line
;prior to receive or send
;
	IN	MODDATP
	IN	MODDATP
;
;Jump 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 XMODEM 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	TRAP	;CHECK FOR NO NAME OR AMBIG. NAME
	CALL	CNREC	;COMPUTE # OF RECORDS.
	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	CALL	TRAP	;CHECK FOR NO NAME OR AMBIG. NAME
;
	IF	NOCOMR
	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		*
*					*
* * * * * * * * * * * * * * * * * * * * *
;
;---->	TRAP: Check for no file name or ambiguous name
;
TRAP	LXI	H,FCB+1 ;POINT TO FILE NAME
	MOV	A,M	;GET FIRST CHAR OF FILE NAME
	CPI	' '	;ANY THERE?
	JNZ	ATRAP	;YES, CHECK FOR AMBIGOUS FILE NAME
	CALL	ERXIT	;PRINT MSG, EXIT
	DB	'++NO FILE NAME SPECIFIED++',CR,LF,'$'
;
ATRAP	MVI	B,11	;11 CHARS TO CHECK
;
TRLOOP	MOV	A,M	;GET CHAR FROM FCB
	CPI	'?'	;AMBIGUOUS?
	JZ	TRERR	;YES, EXIT WITH ERROR MSG
	INX	H	;POINT TO NEXT CHAR
	DCR	B	;ONE LESS TO GO
	JNZ	TRLOOP	;NOT DONE, CHECK SOME MORE
	RET		;NO AMBIGUOUS NAME, RETURN
;
TRERR	CALL	ERXIT	;PRINT MSG, EXIT
	DB	'++CAN''T USE WILD CARD OPTIONS',CR,LF,'$'
;
;---->	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 versions of MODEM program send 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 - 
;	-or-
;Did'nt 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,'$'
;
;Timed out 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 duplicate if = previous,
;	or OK if = 1 + previous 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
;
;Previous sector 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 use a different name.
;
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	XRA	A	;SET EXT & REC # TO 0
	STA	FCBEXT
	STA	FCBSNO
	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,'$'
;
;---->	CNREC: Computes record count, and saves it
;	       until successful file OPEN.
;
;LOOK UP THE FCB IN THE DIRECTORY
CNREC	MVI	A,'?'	;MATCH ALL EXTENTS
	STA	FCBEXT
	MVI	A,0FFH
	STA	MAXEXT	;INIT MAX EXT NO.
	MVI	C,SRCHF ;GET 'SEARCH FIRST' FNC
	LXI	D,FCB
	CALL	BDOS	;READ FIRST
	INR	A	;WERE THERE ANY?
	JNZ	SOME	;GOT SOME
	CALL	ERXIT
	DB	'++FILE NOT FOUND++$'
;
;READ MORE DIRECTORY ENTRIES
MOREDIR	MVI	C,SRCHN ;SEARCH NEXT
	LXI	D,FCB
	CALL	BDOS	;READ DIR ENTRY
	INR	A	;CHECK FOR END (0FFH)
	JNZ	SOME	;NOT END OF DIR...PROCESS EXTENT
	LDA	MAXEXT	;HIT END...GET HIGHEST EXTENT NO. SEEN
	MOV	L,A	;WHICH GIVES EXTENT COUNT - 1
	MVI	H,0
	MOV	D,H
	LDA	RCNT	;GET RECORD COUNT OF MAX EXTENT SEEN
	MOV	E,A	;SAVE IT IN DE
	DAD	H
	DAD	H	;MULTIPLY # OF EXTENTS - 1
	DAD	H	; TIMES 128
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	D	;ADD IN SIZE OF LAST EXTENT
	SHLD	RCNT	;SAVE TOTAL RECORD COUNT
	RET		;AND EXIT
;
;POINT TO DIRECTORY ENTRY 
SOME	DCR	A	;UNDO PREV 'INR A'
	ANI	3	;MAKE MODULUS 4
	ADD	A	;MULTIPLY...
	ADD	A	;..BY 32 BECAUSE
	ADD	A	;..EACH DIRECTORY
	ADD	A	;..ENTRY IS 32
	ADD	A	;..BYTES LONG
	LXI	H,BASE+80H ;POINT TO BUFFER
	ADD	L	;POINT TO ENTRY
	ADI	15	;OFFSET TO RECORD COUNT
	MOV	L,A	;HL NOW POINTS TO REC COUNT
	MOV	B,M	;GET RECORD COUNT
	DCX	H
	DCX	H	;BACK DOWN TO EXTENT NUMBER
	DCX	H
	LDA	MAXEXT	;COMPARE WITH CURRENT MAX.
	ORA	A	;IF NO MAX YET
	JM	BIGGER	;THEN SAVE RECORD COUNT ANYWAY
	CMP	M
	JNC	MOREDIR
BIGGER:	MOV	A,B	;SAVE NEW RECORD COUNT
	STA	RCNT
	MOV	A,M	;SAVE NEW MAX. EXTENT NO.
	STA	MAXEXT
	JMP	MOREDIR	;GO FIND MORE EXTENTS
;
;---->	OPENFIL: Opens the file to be sent
;
OPENFIL	XRA	A	;SET EXT & REC # TO 0 FOR PROPER OPEN
	STA	FCBEXT
	STA	FCBSNO
	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	'++OPEN ERROR++',CR,LF,'$'
;
;Check for distribution-protected file
;
OPENOK	LDA	FCB+1	;FIRST CHAR OF FILE NAME
	ANI	80H	;CHECK BIT 7
	JNZ	OPENOT	;If on, file can't be sent.
	LDA	FCB+2	;Also check "f2" for tag.
	ANI	80H	;Is it set?
	JZ	OPENOK2	;If not, ok to send file.
;
OPENOT	CALL	ERXIT	;EXIT W/MESSAGE
	DB	'++THIS FILE IS NOT FOR DISTRIBUTION, SORRY++'
	DB	CR,LF,'$'
;
OPENOK2 EQU	$
;
	IF	NOLBS OR NOCOMS ;CHECK FOR SEND RESTRICTIONS
	LXI	H,FCB+11
	MOV	A,M	;CHECK FOR PROTECT ATTR
	ANI	7FH	;REMOVE CP/M 2.x ATTRS
	ENDIF		;NOLBS OR NOCOMS
;
	IF	NOLBS	;DON'T ALLOW '#' TO BE SENT.
	CPI	'#'	;CHK FOR '#' AS LAST FIRST
	JZ	OPENOT	;IF '#', CAN'T SEND, SHOW WHY
	ENDIF		;NOLBS
;
	IF	NOCOMS	;DON'T ALLOW .COM TO BE SENT
	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		;NOCOMS
;
OPENOK3 CALL	ILPRT	;PRINT:
	DB	'FILE OPEN - SIZE: ',0
	LHLD	RCNT	; Get record count.
	CALL	DECOUT	;PRINT DECIMAL NUMBER OF SECTORS
	CALL	ILPRT	;Print:
	DB	' (',0
	CALL	DHXOUT	;Now print size in hex.
	CALL	ILPRT	;PRINT:
	DB	'H) SECTORS',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,'$'
;
;
;----> DECOUT: Decimal output routine
;
DECOUT:	PUSH	B
	PUSH	D
	PUSH	H
	LXI	B,-10
	LXI	D,-1
;
DECOU2:	DAD	B
	INX	D
	JC	DECOU2
	LXI	B,10
	DAD	B
	XCHG
	MOV	A,H
	ORA	L
	CNZ	DECOUT
	MOV	A,E
	ADI	'0'
	CALL	CTYPE
	POP	H
	POP	D
	POP	B
	RET
;
; DHXOUT - double precision hex output routine.
;	   Call with hex value in HL.
;
DHXOUT	PUSH	H	;Save H,L
	PUSH	PSW	;Save A
	MOV	A,H	;Get MS byte.
	CALL	HEXO	;Output hi order byte.
	MOV	A,L	;Get LS byte.
	CALL	HEXO	;Output lo order byte.
	POP	PSW	;Restore A
	POP	H	;Restore H,L
	RET		;Return to caller.
;
;
;---->	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 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.
	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
;
;Test for the presence of carrier - if none, go to 
;CARCK and continue testing for 15 seconds. If carrier
;returns, continue. If is doesn't return, exit.
;
	IF	NOT DCH AND NOT PMMI
	IN	MODCTL2	;Read modem status.
	ENDIF
;
	IF	DCH
	IN	MODCTLP	;Read modem status.
	ENDIF
;
	IF	PMMI
	IN	BAUDRP	;Read modem status.
	ENDIF
;
	ANI	MODDCDB	;Carrier detect mask.
	CPI	MODDCDA	;Is it still on?
	CNZ	CARCK	;If not, test for 15 seconds.
;
;Modem timed out receiving - but carrier still on.
;
	POP	D	;RESTORE D,E
	STC		;CARRY SHOWS TIMEOUT
	RET
;
;Got character 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"
;
; CARCK - common 15 second carrier test for RECV and
; SEND. If carrier returns within 15 seconds, normal
; program execution continues. Else, it will abort
; to CP/M via EXIT.
;
CARCK	MVI	E,150	;Value for 15 second delay.
CARCK1	CALL	DELAY	;Kill .1 seconds.
	;
	IF	NOT DCH AND NOT PMMI
	IN	MODCTL2	;Read modem status.
	ENDIF
;
	IF	DCH
	IN	MODCTLP	;Read modem status.
	ENDIF
;
	IF	PMMI
	IN	BAUDRP	;Read modem status.
	ENDIF
;
	ANI	MODDCDB	;Carrier detect mask.
	CPI	MODDCDA	;Is it still on?
	RZ		;Return if carrier on.
	DCR	E	;Has 15 seconds expired?
	JNZ	CARCK1	;If not, continue testing.
	JMP	EXIT	;Else, abort to CP/M.
;
; DELAY - 100 millisecond delay.
;
DELAY	PUSH	B	;Save B,C
;
	IF	FASTCLK	;If 4mhz clock.
	LXI	B,16667	;Value for 100 ms delay.
	ENDIF
;
	IF	NOT FASTCLK
	LXI	B,8334	;Value for 100ms delay.
	ENDIF
;
DELAY2	DCX	B	;Update count.
	MOV	A,B	;Get MS byte.
	ORA	C	;Count = zero?
	JNZ	DELAY2	;If not, continue.
	POP	B	;Restore B,C
	RET		;Return to CARCK1.
;
;
;
;---->	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?
	JZ	SENDR	;..Yes, go send.
;
;Xmit status not ready, so test for carrier before
;looping - if lost, go to CARCK and give it up to 15
;seconds to return. If it doesn't return abort via
;EXIT.
;
	PUSH	D	;Save D,E
;
	IF	NOT DCH AND NOT PMMI
	IN	MODCTL2	;Read modem status.
	ENDIF
;
	IF	DCH
	IN	MODCTLP	;Read modem status.
	ENDIF
;
	IF	PMMI
	IN	BAUDRP	;Read modem status.
	ENDIF
;
	ANI	MODDCDB	;Carrier detect mask.
	CPI	MODDCDA	;Is it still on?
	CNZ	CARCK	;If not, continue testing it.
	POP	D	;Restore D,E
	JMP	SENDW	;Else, wait for xmit ready.
;
;Xmit status ready, carrier still on - send the data.
;
SENDR	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
;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
;
;In order to make the XMODEM command 'natural',
;i.e. XMODEM SEND FILENAME (MODEM S FN.FT) rather
;than XMODEM FILENAME SEND (MODEM FN.FT S), 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 IT <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 message
;
;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 message 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
;
MAXEXT	DB	0	;HIGHEST EXTENT NO. SEEN IN FILE SIZE CALC.
RCNT	DW	0	;RECORD COUNT
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
;
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


