	PAGE 59, 132

	TITLE MSXModem -- Send and receive files using the XMODEM protocol

; Update 8 Jan 86

IF1
 %OUT >> Starting pass 1
ELSE
 %OUT >> Starting pass 2
ENDIF

    PUBLIC XSend, XReceive

    INCLUDE MsDefs.H

DataS	SEGMENT PUBLIC 'DataS'

    EXTRN Count:WORD, Pack:BYTE, OFilSz:WORD, TFilSz:WORD, PortVal:WORD

; Static data

ErMs31	DB '? File not found',cr,lf,'$'
ErMs32	DB "? Can't create file",cr,lf,'$'
No_data_in_file DB '? File is empty', Cr, Lf, '$'
No_initiating_NAK DB 'Timeout waiting for receiver to start$'
Too_many_retries DB 'Too many retries$'
XM_send_status    DB cr,'         XMODEM send: Waiting for receiver to start$'
XM_receive_status DB cr,'      XMODEM receive: Starting$'
In_progress DB 'In progress$'
Completed DB 'Completed$'
Failed	DB 'Failed$'
File_help DB ' File specification with optional path name$'

; Writable data

    EVEN
Tally	DW 0			; Counter for checksum
Filename_ptr DW 0		; Pointer to filename from SPath routine
Handle	DW 0			; File handle for FT file
File_size DD 0			; File size
Buffer_ptr DW 0			; Pointer into file buffer
Buffer_count DW 0		; Count of characters in buffer
HrMn	DW 0			; Place to save hours and minutes
ScHn	DW 0			; Place to save seconds and hundredths
TFile	DB 100 DUP (?)		; Place to store file name
Buffer	DB 1024 DUP (?)		; File I/O buffer
Abort_flag DB 0			; Flag that ^C was hit
EOT_flag DB 0			; Flag that we received a valid EOT signal
Timeout DB 0			; Number of seconds to allow

DataS	ENDS

Code	SEGMENT PUBLIC

    EXTRN Comnd:NEAR, SPath:NEAR, Init:NEAR, Close_transfer_screen:NEAR
    EXTRN Show_error:NEAR, Show_retries:NEAR, Show_status:NEAR, PerPr:NEAR
    EXTRN SerIni:NEAR, Nout:NEAR, RRInit:NEAR, Init:NEAR, Show_packets:NEAR
    EXTRN ClrFln:NEAR, Write_to_standard_output:NEAR, KbPr:NEAR, PrtChr:NEAR
    EXTRN Beep:NEAR, Say_aborted:NEAR, OutChr:NEAR, ClrBuf:NEAR, SerRst:NEAR
    EXTRN EOT_bells:NEAR

    ASSUME cs:Code, ds:DataS, es:DataS


; XReceive -- XMODEM receive routine, called at command level

XReceive PROC

	mov ah, CMTXT		; Read in a line of text
	mov bx, OFFSET TFile	; Temp area for text, max 100 chars
	mov dx, OFFSET File_help ; File specification with optional path name
	call Comnd		; Do it
	 jmp RSkp

	mov si, OFFSET TFile	; Point to start of user input
	mov bl, ah		; Copy length to bl
	sub bh, bh		; Clear high half
	mov BYTE PTR [si+bx], 0	; Make it ASCIZ

	mov ax, (CREATE2*256) + 0 ; DOS 2.0 file create call
	sub cx, cx		; No special attributes
	mov dx, OFFSET TFile	; Point to name again
	int Dos
	 jnc XRE_3		;  Create ok, keep going

XRE_2:	mov ah, PrStr		; Code to type a message
	mov dx, OFFSET ErMs32	; ? Can't create file
	int Dos			; Type it

	jmp RSkp

XRE_3:	mov Buffer_count, 0	; Clear count of chars in buffer
	mov Buffer_ptr, OFFSET Buffer ; Set up initial buffer pointer

	mov Handle, ax		; Save file descriptor
	mov OFilSz, 0		; Flag no percentages on this

	call SerRst		; Unhook serial interrupt first just-in-case
	call SerIni		; Set up serial port as required

	call RRInit		; Set up counters
	call Init		; Set up screen for file transfer

	mov dx, OFFSET XM_receive_status ; "XMODEM receive: Starting"
	call Show_status	; Display status

	mov Pack.NumRtr, 0	; Clear the retry count
	call Show_retries	;  then display it

	call ClrFln		; Clear filename and position us there
	mov dx, OFFSET TFile	; Give him the filename pointer
	call Write_to_standard_output ; Use common routine to type filename

	mov bx, PortVal		; Port structure
	mov al, [bx].floflg	; Save current flow control setting
	push ax
	mov [bx].floflg, 0	; Turn it off for XMODEM

	call Do_XMODEM_receive	; Use other routine to do the work

	call EOT_bells		; Make an optional noise

	mov bx, PortVal		; Port structure
	pop ax
	mov [bx].floflg, al	; Restore it to what it was

	call Close_transfer_screen ; Make screen go normal again

	mov ah, CLOSE2		; Code to close a file
	mov bx, Handle		; The file handle
	int Dos			; Close the file

	cmp Abort_flag, 0	; Did we abort this transfer?
	 je XRE_9		;  No

	mov ah, 41h		; Code to unlink (delete) a file
	mov dx, OFFSET TFile	; Filename used to create file
	int Dos			; Delete the file

    PUBLIC XRE_9

XRE_9:	jmp RSkp		; Done here

XReceive ENDP


; XSend -- XMODEM send routine, called at command level

XSend	PROC

	mov ah, CMTXT		; Read in a line of text
	mov bx, OFFSET TFile	; Temp area for text, max 100 chars
	mov dx, OFFSET File_help ; File specification with optional path name
	call Comnd		; Do it
	 jmp RSkp

	mov si, OFFSET TFile	; Point to start of user input
	mov bl, ah		; Copy length to bl
	sub bh, bh		; Clear high half
	mov BYTE PTR [si+bx], 0	; Make it ASCIZ

	mov ax, si		; Point to name again
	call SPath		; Is it around?
	 jc XSE_2		;  No, go complain

	mov Filename_ptr, ax	; Save pointer to filename for later
	mov dx, ax		; Point to name from SPath
	mov ax, (Open2*256) + 0	; DOS 2.0 open call for read
	int Dos
	 jnc XSE_3		; Open ok, keep going

XSE_2:	mov ah, PrStr		; Code to type a message
	mov dx, OFFSET ErMs31	; ? File not found
	int Dos			; Type it

	jmp RSkp

XSE_3:	mov Handle, ax		; Save file descriptor
	mov bx, ax		; Need descriptor here

	mov ax, (LSeek*256) + 2	; Seek 0 bytes from end
	sub cx, cx
	sub dx, dx
	int Dos

	mov WORD PTR File_size, ax ; Store length
	mov WORD PTR File_size + 2, dx ;  (doubleword)

	mov cx, ax		; Copy low order part to cx
	or cx, dx		; Merge the two words
	 jnz XSE_4		;  There is at least one character

	mov ah, PrStr		; Code to type a message
	mov dx, OFFSET No_data_in_file ; ? File is empty
	int Dos			; Type the message

	jmp RSkp		; Give up

XSE_4:	mov bx, 100		; Get a 100 into bx
	div bx			; Convert file size for percentage calculation
	mov OFilSz, ax		; Save it for PerPr

	mov ax, (LSeek*256) + 0	; Seek back to the beginning
	mov bx, Handle		; Get back the file handle
	sub cx, cx
	sub dx, dx
	int Dos

	call Read_a_bufferfull	; Use other routine to load the buffer

	call SerRst		; Unhook serial interrupt first just-in-case
	call SerIni		; Set up serial port as required

	call RRInit		; Set up counters
	call Init		; Set up screen for file transfer

	mov dx, OFFSET XM_send_status ; "XMODEM send: Waiting for ..."
	call Show_status	; Display status

	mov Pack.NumRtr, 0	; Clear the retry count
	call Show_retries	;  then display it

	call ClrFln		; Clear filename and position us there
	mov dx, Filename_ptr	; Give him the filename pointer
	call Write_to_standard_output ; Use common routine to type filename

	mov bx, PortVal		; Port structure
	mov al, [bx].floflg	; Save current flow control setting
	push ax
	mov [bx].floflg, 0	; Turn it off for XMODEM

	call Do_XMODEM_send	; Use other routine to do the work

	call EOT_bells		; Make an optional noise

	mov bx, PortVal		; Port structure
	pop ax
	mov [bx].floflg, al	; Restore it to what it was

	call Close_transfer_screen ; Make screen go normal again

	mov ah, CLOSE2		; Code to close a file
	mov bx, Handle		; The file handle
	int Dos			; Close the file

	jmp RSkp		; Done here

XSend	ENDP


    PUBLIC Write_a_bufferfull

; Write_a_bufferfull -- Write to HANDLE from BUFFER

Write_a_bufferfull PROC

	cmp Buffer_count, 0	; Are chars to dump?
	 jz WAB_1		;  Nothing to do, just exit

	mov ah, WRITEF2		; DOS 2.0 file handle write
	mov bx, Handle		; File handle to use
	mov cx, Buffer_count	; Amount of stuff in buffer
	mov dx, OFFSET Buffer	; Ptr to buffer
	int Dos			; Write a buffer full

	mov Buffer_count, 0	; Clear count of chars in buffer

WAB_1:	mov Buffer_ptr, OFFSET Buffer ; Set up initial buffer pointer
	ret			; Done here

Write_a_bufferfull ENDP


; Read_a_bufferfull -- Read from HANDLE into BUFFER

Read_a_bufferfull PROC

	mov si, OFFSET Buffer	; Ptr to start of buffer
	mov di, OFFSET Buffer + 1 ; Next byte
	mov cx, (SIZE Buffer)-1	; How many to copy
	mov BYTE PTR [si], CtlZ	; Clear first element of array with control-Z
	rep movsb		; Clear rest of array

	mov ah, ReadF2		; DOS 2.0 file handle read
	mov bx, Handle		; File handle to use
	mov cx, SIZE Buffer	; Size of buffer
	mov dx, OFFSET Buffer	; Ptr to buffer
	int Dos			; Read a buffer full, or whatever we can get

	mov Buffer_count, ax	; Store number of chars read
	sub WORD PTR File_size, ax ; Account for what we have just read
	sbb WORD PTR File_size + 2, 0 ; Do high order half also
	mov Buffer_ptr, OFFSET Buffer ; Set up initial buffer pointer
	ret			; Done here

Read_a_bufferfull ENDP


; Do_XMODEM_receive -- Worker XMODEM file receive routine

Do_XMODEM_receive PROC

	call ClrBuf		; Flush any chars already received

	mov EOT_flag, 0		; Clear this flag
	mov Pack.NumPkt, 0	; Clear count of transmitted packets
	mov Pack.PktNum, 1	; XMODEM starts with packet number 1
	mov TFilSz, 0		; Clear out transfered file size ...
	mov TFilSz + 2, 0	;  ... low half too (this is backwards)

	mov ah, ASCII_NAK	; Load up the char to send
	call OutChr		; Send it once
	 nop
	 nop
	 nop

DXR_1:	mov Pack.NumTry, 0	; Clear this counter
	mov dx, OFFSET In_progress ; "XMODEM receive: In progress"
	call Show_status	; Display status

DXR_2:	call Receive_a_packet	; Receive a packet
	 jnc DXR_4		;  Got a packet with no timeout or error

	cmp Abort_flag, 0	; Aborted?
	 jz DXR_No_abort	;  No

	mov dx, OFFSET Failed	; Say transfer bombed
	jmp Show_status

DXR_No_abort:
	inc Pack.NumRtr		; Bump global count
	call Show_retries	; Show the user

	inc Pack.NumTry		; Bump the count of retries on this packet
	cmp Pack.NumTry, 10	; Hit 10 failures yet?
	 jl DXR_2		;  No, keep going

	mov dx, OFFSET Failed
	call Show_status

	mov dx, OFFSET Too_many_retries ; Too many retries
	jmp Show_error		; Complain on-screen, ret from there

DXR_4:	cmp EOT_flag, 0		; Receive an EOT?
	 jne DXR_EOT		;  Yes, ACK it and close file

	mov Pack.NumTry, 0	; Clear per-packet error counter
	inc Pack.PktNum		; Advance to next packet number
	inc Pack.NumPkt		; Bump the number of packets
	call Show_packets	; Display the number of packets

	add TFilSz + 2, 128	; Bump amount transmitted
	adc TFilSz, 0		;  and high half (done wrong for compatibility)

	call KbPr		; Show how we're doing ...

	add Buffer_ptr, 128	; Advance this ptr
	add Buffer_count, 128	; This counter too

	cmp Buffer_count, SIZE Buffer ; Is the buffer now full?
	 jl DXR_2		;  Still room, get another packet


 %OUT >> About half way through source file


	call Write_a_bufferfull	; Dump out the buffer
	jmp DXR_2		; Go do another packet

DXR_EOT:
	call Write_a_bufferfull	; Dump out the buffer
	mov dx, OFFSET Completed ; "XMODEM receive: Completed"
	jmp Show_status		; Display status and go home

Do_XMODEM_receive ENDP


; Do_XMODEM_send -- Worker XMODEM file send routine

Do_XMODEM_send PROC

	call ClrBuf		; Flush any chars already received

	mov Pack.NumPkt, 0	; Clear count of transmitted packets
	mov Pack.PktNum, 1	; XMODEM starts with packet number 1
	mov TFilSz, 0		; Clear out transfered file size ...
	mov TFilSz + 2, 0	;  ... low half too (this is backwards)
	call Wait_for_NAK	; Give receiver time to start up and NAK us
	 jnc DXS_1		;  We are OK

	ret			; We never got one

DXS_1:	mov Pack.NumTry, 0	; Clear this counter
	mov dx, OFFSET In_progress ; "XMODEM send: In progress"
	call Show_status	; Display status

DXS_2:	call Send_a_packet	; Send a packet
	call Wait_for_ACK	; Get an ACK if we can, fail on timeout or NAK
	 jnc DXS_4		;  Got our ACK, move on ...

	cmp Abort_flag, 0	; Aborted?
	 jz DXS_No_abort	;  No

	mov dx, OFFSET Failed	; Say transfer bombed
	jmp Show_status

DXS_No_abort:
	inc Pack.NumRtr		; Bump global count
	call Show_retries	; Show the user

	inc Pack.NumTry		; Bump the count of retries on this packet
	cmp Pack.NumTry, 10	; Hit 10 failures yet?
	 jl DXS_2		;  No, keep going

	mov dx, OFFSET Failed
	call Show_status

	mov dx, OFFSET Too_many_retries ; Too many retries
	jmp Show_error		; Complain on-screen, ret from there

DXS_4:	add Buffer_ptr, 128	; Advance this ptr
	sub Buffer_count, 128	; Drop this counter

	mov Pack.NumTry, 0	; Clear per-packet error counter
	inc Pack.PktNum		; Advance to next packet number
	inc Pack.NumPkt		; Bump the number of packets
	call Show_packets	; Display the number of packets

	add TFilSz + 2, 128	; Bump amount transmitted
	adc TFilSz, 0		;  and high half (done wrong for compatibility)

	call KbPr		; Show how we're doing ...
	call PerPr		; Percentage-wise also

	cmp Buffer_count, 0	; Is the buffer now empty?
	 jg DXS_2		;  Not empty, do more data
	 jl DXS_Send_EOT	;  "Over-empty", Buffer_count wasn't 1024
				;   this go round and we used them all up,
				;   so this must be end-of-file

	mov ax, WORD PTR File_size ; Pick up half of file size
	or ax, WORD PTR File_size + 2 ; Merge in other half
	 jz DXS_Send_EOT	;  No chars to read, close it out

	call Read_a_bufferfull	; Load up the buffer
	jmp DXS_2		; Go do another packet

DXS_Send_EOT:
	mov Pack.NumTry, 0	; Clear per-packet retry counter

DXS_EOT_loop:
	mov ah, EOT		; Get an ACK
	call OutChr		; Send it out
	 nop
	 nop
	 nop

	call Wait_for_ACK	; Wait for ACK, NAK or timeout
	 jnc DXS_EOT_1		;  Got our ACK, move on ...

	cmp Abort_flag, 0	; Aborted?
	 jz DXS_EOT_no_abort

	mov dx, OFFSET Failed	; Say transfer bombed
	jmp Show_status

DXS_EOT_no_abort:
	inc Pack.NumRtr		; Bump global count
	call Show_retries	; Show the user

	inc Pack.NumTry		; Bump the count of retries on this packet
	cmp Pack.NumTry, 10	; Hit 10 failures yet?
	 jl DXS_EOT_loop	;  No, keep going

	mov dx, OFFSET Failed
	call Show_status

	mov dx, OFFSET Too_many_retries ; Message
	jmp Show_error		; Complain on-screen, ret from there

DXS_EOT_1:
	mov dx, OFFSET Completed ; "XMODEM send: Completed"
	jmp Show_status		; Display status

Do_XMODEM_send ENDP


Wait_for_NAK PROC

	mov Abort_flag, 0	; Clear abort flag
	mov ah, 2Ch		; Code to Get Time
	int Dos			; Get it

	inc cl			; Bump the minutes
	cmp cl, 59		; Too many?
	 jbe WFN_doit		;  No

	sub cl, 60		; Pull down the minutes
	inc ch			; Bump the hours

WFN_doit:
	mov HrMn, cx		; Save hours and minutes
	mov ScHn, dx		; Save seconds and hundredths

WFN_Loop:
	cmp Count, 0		; Any characters to read?
	 jnz WFN_Got_char	;  Found one

	mov dl, 0FFh		; Want input mode
	mov ah, DConIO		; Code for direct console input w/o echo
	int Dos			; Get a char if any is there
	 jz WFN_No_keyboard	;  User hasn't typed anything

	cmp al, 3		; User type ^C?
	 jne WFN_Not_Ctrl_C	;  No

	mov Abort_flag, 0FFh	; Flag the abort
	jmp Say_Aborted		; Standard exit message for ^C

WFN_Not_Ctrl_C:
	or al, al		; Zero?
	 jne WFN_Beep		;  No

	int Dos			; Eat the second part of the funny char

WFN_Beep:
	call Beep		; User hit wrong key, make a noise

WFN_No_keyboard:
	call Carry_if_expired	; See if time has hit yet
	 jnc WFN_loop		;  Not yet

	mov dx, OFFSET Failed
	call Show_status

	mov dx, OFFSET No_initiating_NAK
	jmp Show_error		; Timeout, complain and return with carry on

WFN_Got_char:
	call PrtChr		; Pick up the character
	cmp al, ASCII_NAK	; Did we get what we wanted?
	 je WFN_Done		;  Yes

	inc Pack.NumRtr		; Call every noise character a retry
	call Show_retries	; Display it
	jmp WFN_Loop

WFN_Done:
	clc			; Found a NAK, clear the error flag
	ret			; Go home happy

Wait_for_NAK ENDP


Wait_for_ACK PROC

	mov Abort_flag, 0	; Clear abort flag
	mov ah, 2Ch		; Code to Get Time
	int Dos			; Get it

	inc cl			; Bump the minutes
	cmp cl, 59		; Too many?
	 jbe WFA_doit		;  No

	sub cl, 60		; Pull down the minutes
	inc ch			; Bump the hours

WFA_doit:
	mov HrMn, cx		; Save hours and minutes
	mov ScHn, dx		; Save seconds and hundredths

WFA_Loop:
	cmp Count, 0		; Any characters to read?
	 jz WFA_No_char		;  None

	call PrtChr		; Pick up the character
	cmp al, ACK		; Did we get an ACK?
	 jne WFA_Not_ACK	;  No

	clc			; Clear the error flag
	ret			; Go home happy

WFA_Not_ACK:
	cmp al, ASCII_NAK	; Did we get a NAK?
	 jne WFA_Not_NAK	;  Yes

	stc			; Flag the error
	ret			; Go home

WFA_Not_NAK:
	inc Pack.NumRtr		; Call every noise character a retry
	call Show_retries	; Display it
	jmp WFA_Loop

WFA_No_char:
	mov ah, DConIO		; Code for direct console input w/o echo
	mov dl, 0FFh		; Want input mode
	int Dos			; Get a char if any is there
	 jz WFA_No_keyboard	;  User hasn't typed anything

	cmp al, 3		; User type ^C?
	 jne WFA_Not_Ctrl_C	;  No

	mov Abort_flag, 0FFh	; Flag the abort
	jmp Say_Aborted		; Standard exit message for ^C

WFA_Not_Ctrl_C:
	or al, al		; Zero?
	 jne WFA_Beep		;  No

	int Dos			; Eat the second part of the funny char

WFA_Beep:
	call Beep		; User hit wrong key, make a noise

WFA_No_keyboard:
	call Carry_if_expired	; See if time has hit yet
	 jnc WFA_Loop		;  Not yet

	ret			; Return with carry set

Wait_for_ACK ENDP


; Routine to set the Carry Flag if the SLEEP timer has expired, clear it if not

Carry_if_expired PROC

	mov ah, 2Ch		; Code to Get Time
	int Dos			; Get it

	cmp cx, HrMn		; Is the hour/minute too early?
	 jb CIE_Not_expired	;  Yes
	 ja CIE_Expired		;  NO!

				;  Maybe ...

	cmp dx, ScHn		; How about the seconds?
	 jb CIE_Not_expired	;  Too early

CIE_Expired:
	stc			; Set the carry flag
	ret			; Return

CIE_Not_expired:
	clc			; Clear the carry flag
	ret			; Return

Carry_if_expired ENDP


    PUBLIC Receive_a_packet

; Receive_a_packet -- Routine to receive and disassemble an XMODEM data packet

Receive_a_packet PROC

	mov Timeout, 10		; Start with a ten second timeout
	call Get_char		; Get a char or timeout
	 jc RAP_empty		;  Timeout
	cmp al, SOH		; Proper start?
	 je RAP_1		;  Yes
	cmp al, EOT		; EOT?
	 jne RAP_err		;  No, error

	mov EOT_flag, 1		; Turn on this flag
	jmp SHORT RAP_OK	; Acknowledge it like a packet

RAP_1:	mov Timeout, 1		; Switch to a quicker timeout
	call Get_char		; Get a char or timeout, 1 second
	 jc RAP_empty		;  Timeout
	cmp al, BYTE PTR Pack.PktNum ; Right packet number?
	 jne RAP_err

	call Get_char		; Get a char or timeout
	 jc RAP_empty		;  Timeout
	not al			; Flip the bits
	cmp al, BYTE PTR Pack.PktNum ; Still look right?
	 jne RAP_err

	mov cx, 128		; Number of chars to get
	mov di, Buffer_ptr	; Pick up current ptr
	mov Tally, 0		; Clear counter

RAP_Loop:
	call Get_char		; Get a char or timeout
	 jc RAP_empty		;  Timeout

	stosb			; Lay down the byte
	sub ah, ah		; Clear high half
	add Tally, ax		; Add in the new character
	loop RAP_Loop		; Do 128 characters

	call Get_char		; Get a char or timeout
	 jc RAP_empty		;  Timeout
	cmp al, BYTE PTR Tally	; Does the checksum match?
	 jne RAP_err		;  No good

RAP_OK:	mov ah, ACK		; Code to ACK a good packet
	call OutChr		; Send it out
	 nop
	 nop
	 nop

	clc			; No errors, right block number and everything
	ret			; Go home

RAP_err:
	mov cx, 130		; Max chars to tolerate in a row

RAP_err_2:
	call Get_char		; Try for another char
	 jc RAP_empty		;  Line is idle

	loop RAP_err_2		; Eat another char

RAP_empty:
	mov ah, ASCII_NAK	; That nasty character
	call OutChr		; Send it
	 nop
	 nop
	 nop

	stc			; Flag an error
	ret			; Go home

Receive_a_packet ENDP


; Send_a_packet -- Routine to assemble and send an XMODEM data packet

Send_a_packet PROC

	call ClrBuf		; Flush any chars already received

	mov ah, SOH		; Start each packet with SOH
	call OutChr		; Send it along
	 nop
	 nop
	 nop

	mov ah, BYTE PTR Pack.PktNum ; Copy packet number to ah
	call OutChr		; Send it along
	 nop
	 nop
	 nop

	mov ah, 255		; Funny packet number encoding
	sub ah, BYTE PTR Pack.PktNum ; One's complement
	call OutChr		; It too
	 nop
	 nop
	 nop

	mov cx, 128		; Number of chars to send
	mov si, Buffer_ptr	; Pick up current ptr
	mov Tally, 0		; Clear counter

SAP_Loop:
	lodsb			; Pick up the byte
	sub ah, ah		; Clear high half
	add Tally, ax		; Add in the new character
	mov ah, al		; Copy char to ah
	call OutChr		; Send it out
	 nop
	 nop
	 nop

	loop SAP_Loop		; Do 128 characters

	mov ah, BYTE PTR Tally	; Use low order half of total as checksum
	call OutChr		; Send the checksum (remainder)
	 nop
	 nop
	 nop

	ret			; Go home

Send_a_packet ENDP


Get_char PROC

	push cx			; Save cx reg
	mov Abort_flag, 0	; Clear abort flag
	mov ah, 2Ch		; Code to Get Time
	int Dos			; Get it

	add dh, Timeout		; Add in the timeout time
	cmp dh, 59		; Too many?
	 jle GCH_doit

	sub dh, 60		; Pull down the seconds
	inc cl			; Bump the minutes

	cmp cl, 59		; Too many?
	 jle GCH_doit		;  No

	sub cl, 60		; Pull down the minutes
	inc ch			; Bump the hours

GCH_doit:
	mov HrMn, cx		; Save hours and minutes
	mov ScHn, dx		; Save seconds and hundredths

GCH_Loop:
	cmp Count, 0		; Any characters to read?
	 jnz GCH_Got_char	;  Found one

	mov dl, 0FFh		; Want input mode
	mov ah, DConIO		; Code for direct console input w/o echo
	int Dos			; Get a char if any is there
	 jz GCH_No_keyboard	;  User hasn't typed anything

	cmp al, 3		; User type ^C?
	 jne GCH_Not_Ctrl_C	;  No

	mov Abort_flag, 0FFh	; Flag the abort
	call Say_Aborted	; Standard exit message for ^C

	jmp SHORT GCH_done	; Done here

GCH_Not_Ctrl_C:
	or al, al		; Zero?
	 jne GCH_Beep		;  No

	int Dos			; Eat the second part of the funny char

GCH_Beep:
	call Beep		; User hit wrong key, make a noise

GCH_No_keyboard:
	call Carry_if_expired	; See if time has hit yet
	 jnc GCH_loop		;  Not yet

	jmp SHORT GCH_done	; Timeout

GCH_Got_char:
	call PrtChr		; Pick up the character
	clc			; Got a character, clear error flag

GCH_done:
	pop cx			; Restore cx reg
	ret			; Go home happy

Get_char ENDP


; Jumping to this location is like retskp.  It assumes the instruction
;   after the call is a jmp addr

RSKP	PROC	NEAR
	pop bp
	add bp,3
	push bp
;	ret
RSKP	ENDP

; Jumping here is the same as a ret

R	PROC	NEAR
	ret
R	ENDP

Code	ENDS

	END

