	NAME	MSXRMX
; Generic MS DOS Kermit module
$INCLUDE(msdefs.h86)

	public	serini, serrst, clrbuf, outchr, coms, vts, dodel, bddat, inbaud
	public	ctlu, cmblnk, locate, lclini, prtchr, dobaud, clearl, dstflg
	public	dodisk, getbaud, beep, rdbaud, dstmbx, srcptr, source
	public	count, xofsnt, puthlp, putmod, clrmod, poscur, savsi, portatt
	public	sendbr, term, machnam, setktab, setkhlp, showkey, hangup

false	equ	0
true	equ	1

; external variables used:
; drives - # of disk drives on system
; flags - global flags as per flginfo structure defined in pcdefs
; trans - global transmission parameters, trinfo struct defined in pcdefs
; portval - pointer to current portinfo structure (currently either port1
;    or port2)
; port1, port2 - portinfo structures for the corresponding ports

; global variables defined in this module:
; xofsnt, xofrcv - tell whether we saw or sent an xoff.

datas 	segment	public 'datas'
	public	prttab, sintok, soutok
	extrn	drives:byte, flags:byte, trans:byte, citok:word, cotok:word
	extrn	portval:word, port1:byte, port2:byte, packet:byte
	extrn	mbox:word, tmbox:word, sigpair:word, sematok:word
	
machnam	db	21 dup(' ')				; filled in by rmxkerm
erms40	db	cr,lf,'?Warning: Unrecognized baud rate',cr,lf,'$'
erms60	db	cr,lf,':COM2: not found'
	db	cr,lf,'use RUN ATTACH. . .AS COM2. . .$'
	db	cr,lf,'& try again$'
badbd	db	cr,lf,'Unimplemented baud rate$'
noimp	db	cr,lf,'Command not implemented.$'
shkmsg	db	'Not implemented.'
shklen	equ	$-shkmsg
setktab	db	0
setkhlp	db	0
crlf    db      cr,lf,'$'
delstr  db	BS,' ',BS,'$'		; Delete string. [21d]
clrlin  db      cr			; Clear line (just the cr part).
clreol	db	ESC,'[0K'		; Clear line.
clscr	db	ESC,'[2J'		; clear screen
cursor	db	ESC,'['			; position cursor
row	dw	?			;  part
	db	';'			;   of
col	dw	?			; above
	db	'H'			; last of above
mode	db	ESC,'[24;1H',ESC,'[0K'	; put cursor on mode line & clear it
model	db	80 dup(?)		; mode line contents
huosc	db	ESC,']M:H',ESC,'\'	; hangup OSC
temp	db	?			; buffer for term

xofsnt	db	0		; Say if we sent an XOFF.
xofrcv	db	0		; Say if we received an XOFF.
com2	db	6,':COM2:'

; Entries for choosing communications port. [19b]
comptab	db	04H
	db	01H,'1$'
	dw	01H
	db	01H,'2$'
	dw	00H
	db	04H,'COM1$'
	dw	01H
 	db	04H,'COM2$'
	dw	00H

ourarg	termarg	<>

	even

count	dw	0ffffh		; set to keep MSCOMM out of the XON/XOFF act
savsi	dw	source		; points to next char in buffer
srcptr	dw	source		; points to next quadrant of buffer
prttab	dw	com2,0		; RMXKERM fills in 2nd word
sintok	dw	0
soutok	dw	0
nucom2	dw	0
portatt	dw	4,4,0ffh,119h	; serial I/O port attributes for rqaspecial
inbaud	dw	0,1		; part of above
termatt	dw	2,2,2 dup (?)	; terminal port attributes for special
getptat	dw	3,3,2 dup(?)	; structure to read baud rate
getinbd	dw	?		; part of above
dbufno	dw	0		; serial output buffer: 0=dest1, 2=dest2
dstptr	dw	dest1,dest2	; indexed by dbufno
dstmbx	dw	2 dup (?)	; indexed by dbufno.  filled in by RMXKERM
dstflg	dw	2 dup (0)	; indexed by dbufno.  0 means don't waitio
dest1	db	256 dup(?)	; serial output buffer
dest2	db	256 dup(?)	; serial output buffer
dest	db	2 dup (?)	; outchr's buffer

; This table is indexed by portinfo.baud.  Unsupported baud rates contain 0.
bddat	label	word
	dw	0		;  45.5 baud  N/A
	dw	50		;    50 baud
	dw	75		;    75 baud
	dw	110		;   110 baud
	dw	0		; 134.5 baud  N/A
	dw	150		;   150 baud
	dw	300		;   300 baud
	dw	600		;   600 baud
	dw	1200		;  1200 baud
	dw	1800		;  1800 baud
	dw	2000		;  2000 baud
	dw	2400		;  2400 baud
	dw	4800		;  4800 baud
	dw	9600		;  9600 baud
	dw	19200		; 19200 baud
	dw	38400		; 38400 baud

source	db	bufsiz dup(0)			; serial input buffer

datas	ends

code	segment	public 'code'
	extrn	comnd:near, dopar:near, prserr:near
	extrn	delcon:near, crfile:near, opfile:near, reader:near, writer:near
	extrn	aspcl:near, prstr_:near, outpkt:near, awrite:near
	extrn	waitio:near, sendms:near, aopen:near, special:near
	assume	cs:code,ds:datas

PATCH:	mov	bx,offset outpkt_	; used as data by lclini to replace
	jmp	bx			;  MSCOMM's outpkt with our's

; This is the HANGUP command.  It drops DTR momentarily, if the serial board
; and its driver support modem control.

HANGUP	PROC
	call	serini			; ensure port's initialized
	mov	ax,soutok		; enable OSC's on output
	mov	bx,offset portatt
	and	word ptr[bx+4],0bfh
	mov	cx,5
	assume	cs:cgroup
	call	aspcl
	mov	ax,soutok		; send hangup OSC
	mov	bx,offset huosc
	mov	byte ptr[bx+4],'H'
	mov	cx,size huosc
	mov	dx,tmbox
	call	awrite
	mov	ax,soutok		; wait for it
	mov	bx,tmbox
	call	waitio
	sub	cx,cx
	mov	ax,soutok
	mov	bx,offset huosc
HU1:	mov	byte ptr[bx+4],'A'
	loop	HU1			; kill time
	mov	cx,size huosc		; send answer OSC
	mov	dx,tmbox
	call	awrite
	mov	ax,soutok		; wait for it
	mov	bx,tmbox
	call	waitio
	mov	ax,soutok		; disable OSC's on output
	mov	bx,offset portatt
	or	word ptr[bx+4],40h
	mov	cx,5
	call	aspcl
	assume	cs:code
	call	serrst			; reset port
	jmp	RSKP
HANGUP	ENDP

; this is called by Kermit initialization.  It checks the
; number of disks on the system, sets the drives variable
; appropriately.  Returns normally.

DODISK	PROC	NEAR
	mov drives,1		; 1 "disk".  It's $, which can be reattachfiled
	ret
DODISK	ENDP

; Clear the input buffer. This throws away all the characters in the
; serial interrupt buffer.  This is particularly important when
; talking to servers, since NAKs can accumulate in the buffer.
; Do nothing since we are not interrupt driven.  Returns normally.

CLRBUF	PROC	NEAR
	push	es
	mov	ax,ds
	mov	es,ax
	cli
	cld
	mov	di,savsi			; scan from current ptr
	mov	cx,offset source+size source	; to end of buffer for NUL
	sub	cx,di
	sub	al,al
	repne scasb
	je	CLRB1				; jump if found
	mov	di,offset source		; scan from the beginning, if not
	mov	cx,size source
	repne scasb
CLRB1:	sti
	dec	di
	mov	savsi,di			; cuurent ptr points to NUL
	pop	es
	ret
CLRBUF	ENDP

; Put the char in AH to the serial port.  This assumes the
; port has been initialized.  Should honor xon/xoff.  Skip returns on
; success, returns normally if the character cannot be written.

outchr	proc	near
	push	bx
	mov	al,ah
	call	dopar		; set parity, as req'd
	mov	dest,al
	mov	bx,offset dest	; write it
	mov	cx,1
	sub	dx,dx
	assume	cs:cgroup
	call	awrite
	assume	cs:code
	pop	bx
	jcxz	OUTCH1		; OK return
	ret			; NG return
OUTCH1:	jmp	rskp
outchr	endp

; Set the baud rate for the current port, based on the value
; in the portinfo structure.  Returns normally.

DOBAUD	PROC	NEAR
	mov	bx,portval		; see if new baud rate is OK
	mov	si,[bx].baud
	shl	si,1
	mov	cx,bddat[si]
	jcxz	DOBD1			; nope
	call	serrst			; yeah, force serini to set it
	ret
DOBD1:	mov	[bx].baud,ax		; restore previous baud rate
	mov	ah,prstr		; send bad baud msg
	mov	dx,offset badbd
	int	dos
	ret
DOBAUD	ENDP

; Get the current baud rate from the serial card and set it
; in the potinfo structure for the current port.  Returns normally.
; This is used during initialization.

GETBAUD	PROC	NEAR
	ret			; It's all taken care of, so what's to do?
GETBAUD	ENDP


; Use for DOS 2.0 and above.  Check the port status.  If no data, skip
; return.  Else, read in a char and return.
PRTCHR	PROC    NEAR
	cmp	xofsnt,true		; if XOF has been sent,
	je	PRTCH4			; see if there's room in buffer for XON
PRTCH1:	mov	si,savsi		; current serial input buffer ptr
	cld
	lodsb
	test	al,al			; got one?
	jz	PRTCH6			; nope
PRTCH2:	cmp	si,offset source+size source	; edge of the universe?
	jl	PRTCH3			; no
	mov	si,offset source	; yeah, point to beginning
PRTCH3:	mov	savsi,si		; update current ptr
	ret				; got one return
PRTCH4:	mov	ax,savsi
	sub	ax,srcptr		; if next quadrant is above current ptr,
	js	PRTCH5			; there's room to XON
	cmp	ax,bufsiz/4		; if there's not a clear quadrant below,
	jl	PRTCH1			; there isn't
PRTCH5:	mov	bx,portval		; send XON
	mov	ax,[bx].flowc
	call	outchr
	nop
	nop
	nop
	mov	xofsnt,false		; reset
	jmp	PRTCH1
PRTCH6:	jmp	RSKP			; no data return
PRTCHR	ENDP

; Send a break out the current serial port.  Returns normally.
SENDBR	PROC	NEAR
	mov	ah,prstr	; send intel's appologies
	mov	dx,offset noimp	; for not having OS support
	int	dos		; for their hardware's capabilities
	ret
SENDBR	ENDP

; Clear to the end of the current line.  Returns normally.

CLEARL	PROC	NEAR
	mov	bx,offset clreol	; send ESC[0K
SCRO1:	mov	cx,4			; to clear to end of line
SCRO2:	mov	ax,cotok
	push	es
	push	si
	assume	cs:cgroup
	call	writer
	assume	cs:code
	pop	si
	pop	es
	ret
CLEARL	ENDP

; This routine blanks the screen.  Returns normally.

CMBLNK	PROC	NEAR
	mov	bx,offset clscr		; send ESC[2J for clear screen
	jmp	SCRO1
CMBLNK	ENDP

; Position the cursor according to contents of DX:
; DH contains row, DL contains column.  Returns normally.
POSCUR	PROC	NEAR
	add	dx,101h		; KERMIT starts w/0, RMX w/1
	mov	al,dh		; convert row to decimal
	sub	ah,ah
	mov	cl,10
	div	cl
	add	ax,'00'		; covert to ASCII
	mov	row,ax		; stash in row portion of escape sequence
	mov	al,dl		; convert column to decimal
	sub	ah,ah
	div	cl
	add	ax,'00'		; convert to ASCII
	mov	col,ax		; to column portion of escape sequence
	mov	bx,offset cursor
	mov	cx,8		; send ESC[row;colH to position cursor
	jmp	SCRO2
POSCUR	ENDP

; Move the cursor to the left margin, then clear to end of line.
; Returns normally.

CTLU	PROC	NEAR
	mov	bx,offset clrlin	; send CR,ESC[0K to clear entire line
	mov	cx,5
	jmp	SCRO2
CTLU	ENDP

; Homes the cursor.  Returns normally.

LOCATE	PROC	NEAR
	mov	dx,0		; Go to top left corner of screen.
	jmp	poscur
LOCATE	ENDP

; Write a line at the bottom of the screen...
; the line is passed in dx, terminated by a $.  Returns normally.
putmod	proc	near
	mov	bx,es		; save es
	mov	ax,ds
	mov	es,ax
	mov	di,dx
	mov	cx,0ffffh	; compute # bytes in line
	mov	al,'$'
	repne scasb
	neg	cx
	add	cx,0fffeh
	mov	si,dx
	mov	di,offset model
	mov	dx,cx		; save # bytes
	rep movsb		; move line after mode line escape sequence
	mov	cx,length mode	; mode line escape sequence length
	add	cx,dx		; plus line length
	mov	es,bx		; restore es
	mov	bx,offset mode	; write line prefixed w/escape sequence
	jmp	SCRO2
putmod	endp

; clear the mode line written by putmod.  Returns normally.
clrmod	proc	near
	mov	bx,offset mode	; mode line escape sequence
	mov	cx,length mode	; ESC[24;1HESC[0K
	jmp	SCRO2		; clears mode line
clrmod	endp

; Put a help message on the screen.
; Pass the message in ax, terminated by a null.  Returns normally.
puthlp	proc	near
	push	ax		; preserve this
	mov 	ah,prstr
	mov 	dx,offset crlf
	int 	dos
	pop	si		; point to string again
puthl3:	lodsb			; get a byte
	cmp	al,0		; end of string?
	je	puthl4		; yes, stop
	mov 	dl,al
	mov	ah,dconio
	int	dos		; else write to screen
	jmp	puthl3		; and keep going
puthl4:	mov 	ah,prstr
	mov 	dx,offset crlf
	int 	dos
	ret
puthlp	endp

; Delete a character from the terminal.  This works by printing
; backspaces and spaces.  Returns normally.

DODEL	PROC	NEAR
	mov ah,prstr
	mov dx,offset delstr	; Erase weird character.
	int dos			
	ret
DODEL	ENDP

; Set the current port.
COMS	PROC	NEAR
	call	serrst			; reset the old port
	mov	dx,offset comptab
	mov	bx,0
	mov	ah,cmkey
	call	comnd			; parse for the new one
	jmp	r
	push	bx		; save port #
	mov	ah,cmcfm
	call	comnd		; Get a confirm.
	jmp	short comx	; Didn't get a confirm.
	nop
	pop	cx		; get port #
	mov	ax,nucom2
	or	ax,cx		; is it COM2 and the first time
	jnz	COMS2		; neither
	mov	di,prttab	; yes, create it
	assume	cs:cgroup
	call	crfile
	assume	cs:code
	jcxz	COMS1		; OK
	mov	dx,offset erms60	; no luck, bitch
	mov	ah,prstr
	int	dos
	ret
COMS1:	mov	sintok,ax	; open COM2
	mov	bx,1
	assume	cs:cgroup
	call	aopen
	assume	cs:code
	call	rdbaud		; read it's baud rate
	mov	port2.baud,cx	; memorize it
	call	serrst		; reset COM2
	inc	nucom2		; set flag to prevent deja vu
	sub	cx,cx		; 0 means COM2
COMS2:	mov	flags.comflg,cl	; remember port #
	mov	ax,offset port2
	jcxz	COMS3		; is it COM2
	mov	ax,offset port1	; nope, COM1
COMS3:	mov	portval,ax	; set portinfo ptr
	ret
COMX:	pop	bx
	ret
COMS	ENDP

; Reads serial port's baud rate.  Returns baud rate index in cx if recognizable
; by KERMIT, if not, cx=0ffffh.

rdbaud	proc	near
	push	es
	mov	ax,sintok	; use aspecial function 4 to read baud rate
	mov	bx,offset getptat
	mov	cx,4
	assume	cs:cgroup
	call	aspcl
	mov	ax,ds
	mov	es,ax
	mov	ax,getinbd	; search bddat table for it
	mov	cx,baudsiz
	mov	di,offset bddat+2*(baudsiz-1)
	std
	repne scasw
	cld
	je	RDB1		; found it, cx=index
	mov	dx,offset erms40
	call	prstr_		; not found, say so
	mov	cx,0ffffh	; return unrecognizable
RDB1:	pop	es
	assume	cs:code
	ret
rdbaud	endp

; Set heath emulation on/off.

VTS	PROC	NEAR
	jmp	notimp
VTS	ENDP

notimp:	mov	ah,prstr
	mov	dx,offset noimp
	int	dos
	jmp	prserr

; Initialize variables to values used by the generic MS DOS version.

lclini:	mov	flags.vtflg,0		; Don't do terminal emulation.
	mov	ax,word ptr patch	; Substitute our outpkt for MSCOMM's
	mov	word ptr outpkt,ax
	mov	ax,word ptr patch+2
	mov	word ptr outpkt+2,ax
	mov	ax,word ptr patch+4
	mov	word ptr outpkt+4,ax
	ret

showkey:
	mov ax,offset shkmsg
	mov cx,shklen
	ret

; Initialization for using serial port.  Returns normally.
SERINI	PROC	NEAR
	cmp	sintok,0	; is this trip necessary?
	jne	SIN1		; no
	assume	cs:cgroup
	push	es
	mov	bl,flags.comflg	; get port #
	sub	bh,bh
	shl	bx,1		; convert to port name table index
	mov	di,prttab[bx]	; create it
	call	crfile
	mov	soutok,ax	; set serial output token
	mov	bx,2		; open for writing
	call	aopen
	mov	ax,soutok	; set baud rate & attributes
	mov	bx,offset portatt
	mov	cx,5
	call	aspcl
	mov	ax,mbox		; when the message sent to mbox
	mov	bx,ax		; is the mbox token itself
	call	sendms		; it tells the serial input task
	pop	es		; to initialize & start input
	assume	cs:code		; burma-shave
SIN1:	ret
SERINI	ENDP

; Reset the serial port.  This is the opposite of serini.  Calling
; this twice without intervening calls to serini should be harmless.
; Returns normally.

SERRST	PROC	NEAR
	assume	cs:cgroup
	mov	bx,sintok	; serial input token
	test	bx,bx
	jz	SERST1		; unecessary
	mov	sintok,0	; MUST be done 1st, cuz siotsk is gonna get ints
	call	delcon		; before we get back from delete connection
SERST1:	mov	bx,soutok	; serial output token
	test	bx,bx
	jz	SERST2		; don't bother
	mov	soutok,0	; see $-5 lines
	call	delcon		; ditto
SERST2:	ret
	assume	cs:code
SERRST	ENDP

; Bell ringer

BEEP	PROC	NEAR
	mov dl,bell
	mov ah,dconio
	int dos
	ret
BEEP	ENDP

; Dumb terminal emulator.  Doesn't work too well above 1200 baud (and
; even at 1200 baud you sometimes lose the first one or two characters
; on a line).
term	proc	near
	mov	si,ax			; this is source
	mov	di,offset ourarg	; place to store arguments
	mov	ax,ds
	mov	es,ax			; address destination segment
	mov	cx,size termarg
	rep movsb			; copy into our arg blk
	mov	ax,cotok		; save terminal attributes
	mov	bx,offset termatt
	mov	cx,4
	assume	cs:cgroup
	call	special
	push	termatt+6		; save
	and	termatt+6,0fdffh	; turn off translation
	push	termatt+4		; save
	or	termatt+4,0e0h		; disable OSC's
	mov	ax,cotok		; make it transparent as all get out
	mov	bx,offset termatt	; during terminal emulation
	mov	cx,5
	call	special
	pop	termatt+4		; restore
	pop	termatt+6		; restore
	sub	ax,ax			; turn off control C trap
	mov	sigpair,ax
	mov	ax,citok
	mov	bx,offset sigpair
	mov	cx,6
	call	special
	assume	cs:code
term1:	call	prtchr
	jmp	short term2		; have a char...
	nop
	nop
	jmp	short term3		; no char, go on
term2:	push	ax
	and	al,7fh			; mask off parity for terminal
	mov	temp,al
	mov	ax,cotok
	mov	bx,offset temp
	mov	cx,1
	assume	cs:cgroup
	call	writer			; go print it
	assume	cs:code
	pop	ax
	test	ourarg.flgs,capt	; capturing output?
	jz	short term3		; no, forget it
	call	ourarg.captr		; else call the routine
term3:	mov	ax,citok		; read 1 from keyboard
	mov	bx,offset temp
	mov	cx,1
	assume	cs:cgroup
	call	reader
	assume	cs:code
	test	ax,ax
	jz	term1			; no character, go on
	mov	al,temp
	cmp	al,ourarg.escc		; escape char?
	je	short term4		; yes, exit
	mov	ah,al
	call	outchr			; output the character
	nop
	nop
	nop
	test	ourarg.flgs,lclecho	; echoing?
	jz	term1			; no, continue loop
	mov	ax,cotok		; write it to CRT
	mov	bx,offset temp
	mov	cx,1
	assume	cs:cgroup
	call	writer
	assume	cs:code
	jmp	term1			; else echo and keep going
term4:	mov	ax,cotok		; restore terminal attributes
	mov	bx,offset termatt
	mov	cx,5
	assume	cs:cgroup
	call	special
	mov	ax,sematok		; restore control C trap
	mov	sigpair,ax
	mov	ax,citok
	mov	bx,offset sigpair
	mov	cx,6
	call	special
	assume	cs:code
	ret
term	endp

; MSCOMM's outpkt routine rewritten to eliminate character-at-a-time output

outpkt_	proc	near
	assume	cs:cgroup
	mov	ax,ds
	mov	es,ax
	push	bp		; save
	mov	bp,dbufno	; output buffer number
	cmp	dstflg[bp],0	; has it been written
	je	OUTPK1		; no
	mov	bx,dstmbx[bp]	; yes, wait for it to complete
	mov	ax,soutok
	call	waitio
OUTPK1:	mov	di,dstptr[bp]	; output buffer ptr
	mov	dstflg[bp],di	; flag as written
	mov	cl,trans.spad	; number of pad chars
	sub	ch,ch
	mov	bx,cx		; save # pads
	cld
	jcxz	OUTPK2
	mov	al,trans.spadch	; pad character
	rep stosb		; put in buffer
OUTPK2:	mov	dx,di		; save buffer ptr
	mov	di,offset packet
	mov	cx,0ffffh	; compute # bytes in packet
	sub	al,al
	repne scasb
	neg	cx
	add	cx,0fffeh
	add	bx,cx		; add to # pad chars
	mov	si,offset packet
	mov	di,dx
	rep movsb		; move packet to buffer
	mov	cx,bx		; write pads & packet
	mov	bx,dstptr[bp]
	mov	dx,dstmbx[bp]
	call	awrite
	xor	bp,2		; ping-pong buffer
	mov	dbufno,bp	; for next time
	pop	bp		; restore
	assume	cs:code
	jcxz	OUTPK3		; OK if no awrite complaints
	ret			; else NG
OUTPK3:	jmp	rskp
outpkt_	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
