; RemKey 1.00
; Assemble with ML.EXE (from MASM 6.0), not MASM.EXE
	.MODEL tiny

; Some of the logic in this program was lifted from KEY100.ASM by
; Andy Gryc and PUSHKEYS.COM by Raan Young and Dave Suvak, all of
; HP Corvallis.
;
; Many thanks to the beta-test team for testing and
; suggesting improvements:
; 
;   Siroos Afshar
;   Conrad D. Cox
;   Ron Crain
;   James Dean
;   Stanley Dobrowski
;   Bruce Holmen
;   Ed Keefe
;   Gilles Kohl
;   David J. Marsh
;   Thomas Rundel
;   Mark Scardina
;   David N. Smith
;   Jorge M. Trevino
;   Steve Zweibel
; 

debug equ 0		; non-zero enables debug output

; Leave this off. The send-file code didn't work out well.
; About 3 out of 500 characters were dropped.
fileOpt equ 0

localStack equ 1	; non-zero enables use of local stack

fixedKeys equ 1

bcdVersion equ 0100h

HotKey equ 7f00h	; Alt 8

; Timer chip equates
timer0ModeCmd equ 36h
timer2ModeCmd equ 0B6h
timer2LatchCmd equ 80h
timer2ReadBackStatusCmd equ 0E8h

timerModeReg equ 43h
timer0CountReg equ 40h
timer2CountReg equ 42h

timerOutputFlag equ 80h

portB equ 61h

; bits defined for reading and writing of port B
timer2gate equ 1
speaker2gate equ 2
gate2AndSpeaker2 equ timer2gate or speaker2gate

VHiToneFreq equ 128
hiToneFreq equ  64
midToneFreq equ 32
loToneFreq equ 16

VHiToneDivisor equ (115200 / VHiToneFreq)
hiToneDivisor equ (115200 / hiToneFreq)
midToneDivisor equ (115200 / midToneFreq)
loToneDivisor equ (115200 / loToneFreq)

VHiToneClicks equ VHiToneFreq / 2		; 1/2 second
hiToneClicks equ hiToneFreq / 2		; 1/2 second
midToneClicks equ midToneFreq / 2	; 1/2 second
loToneClicks equ loToneFreq / 2		; 1/2 second

MenuKeyCode equ 0c800h

; These keys are sent with a high byte of 0f5h and the int 9 scan code in
; the low byte. 80h is added for a released key.
RShiftScan equ 36h	; int 9 scan code of the right shift key
LShiftScan equ 2ah	; int 9 scan code of the left shift key
LCtrlScan  equ 1dh	; int 9 scan code of the left Ctrl key
LAltScan   equ 38h	; int 9 scan code of the left Alt key

; These are handled as special cases and can be given any code but
; the above codes or the above plus 80h:

LockCode    equ 0f400h	; code sent over serial line when Caps Lock goes on
UnlockCode  equ 0f401h	; code sent over serial line when Caps Lock goes off

CapsScan   equ 3ah	; int 9 scan code of the Caps Lock key

baudRate equ 1200
baudRateDivisor equ (115200 / baudRate)

; Factor by which we increase the SysTick rate over the
; standard 18.2 ticks/sec
TickFactor equ baudRate/182+1

sioConfig equ 3		; 8 data, 1 stop, no parity

; Base I/O port of 4 serial ports
com1	equ 3f8h
com2	equ 2f8h
com3	equ 3e8h
com4	equ 2e8h

; Offsets from base of various UART registers
rx	equ 0
tx	equ 0
int_en	equ 1
int_id	equ 2
lcont	equ 3
mcont	equ 4
lstat	equ 5
mstat	equ 6
dlab_l	equ 0
dlab_h	equ 1

pcRecvDataAvailable	equ 1
pcOverrunError		equ 2
pcParityError		equ 4
pcFramingError		equ 8
pcBreakInterrupt	equ 10h
pcXmitBufferEmpty	equ 20h
pcXmitShiftRegEmpty	equ 40h
pcXmitAllEmpty		equ (pcXmitBufferEmpty OR pcXmitShiftRegEmpty)

video_int	equ	10h		;int for video output calls to bios
tty_out		equ	14		; put-char function # for video_int

MpxInt	equ	2fh			; the multiplex interupt
RemKeyMpxFn equ 93h			; Multiplex func code used by RemKey

; The BIOS variables at segment 40h
biosdata	segment at 40h		; rom bios data area
		org	17h
ShiftState	dw	?
		org	1ah
bufferHead	dw	?		; Head of keyboard buffer
bufferTail	dw	?		; Tail of keyboard buffer

		org	49h		; start of video data
crt_mode	db	?
crt_cols	dw	?
crt_len		dw	?
crt_start	dw	?
cursor_posn	dw	8 dup(?)
cursor_mode	dw	?
active_page	db	?
addr_6845	dw	?
crt_mode_set	db	?
crt_palette	db	?

		org	6bh		; Phoenix-specific
LastInterrupt	db	?		; bit-map, last interrupt

      		org	80h
; These words contain offsets from an assumed segment of 40h
bufferStart	dw	?		; Start of keyboard buffer
bufferEnd	dw	?		; End+1 of keyboard buffer

		org	0a1h
sleepCountdown	dw	?		; a countdown to zero triggers sleep
sleepTimeout	dw	?		; used to reload sleepCountdown

		org	0f1h		; 100LX-specific keyboard data
KbdFlgs		db	?
FnFlags		dw	?
SysFlags2	db	?
Debounce	db	?
LastIrq2	dw	?
LastKey		db	?
RptCnt		db	?
MiscFlags	db	?

biosdata	ends

       .CODE
       ORG 100h
Begin:
       jmp Main

if localStack
       	db	'<..RemKey 00 ..>'
       	db	'<..RemKey 10 ..>'
       	db	'<..RemKey 20 ..>'
       	db	'<..RemKey 30 ..>'
       	db	'<..RemKey 40 ..>'
       	db	'<..RemKey 50 ..>'
       	db	'<..RemKey 60 ..>'
       	db	'<..RemKey 70 ..>'
stack_top:

if debug
	db	'<< Stack Top <<<'
endif

InCheckUart db 0			; used to detect recursion

ss_save	dw	?
sp_save	dw	?
endif

version dw bcdVersion

EnableDef db 1		; non-zero to enable send or receive by default
Receive	db 1		; non-zero for receive mode, zero for send mode

UseEnDef db 1		; non-zero to copy EnableDef to Enabled

DoConfig db 0		; non-zero to trigger reconfig write

OldKey label dword	; Int 16h -- keyboard hook
OldKeyOff dw ?
OldKeySeg dw ?

OldTick label dword	; Int 8 -- timer hardware tick
OldTickOff dw ?
OldTickSeg dw ?

OldMpx label dword	; Int 2f -- multiplex interrupt
OldMpxOff dw ?
OldMpxSeg dw ?

Enabled db 0		; MUST BE 1 to enable send or receive
SendTSR	db 0		; Sender installs as a TSR
Is100LX db 0		; non-zero if running on a 100LX

UartBase dw com1	; default is com1

TickCount dw TickFactor	; counts real ticks

; Offset of our service routine for int 16h
NewKey	dw offset NewKeySend ; assume send-mode

ResidentMpxNum	db	0c0h	; multiplex number of resident code (01b3)
OurMpxNum	db	0	; our multiplex number (01b4)

if fileOpt
SendFileName	dw	0	; offset of path to file name to be sent
FileNameEnd	dw	0	; saved pointer to end of path
endif

; blinking, black on white (inverted)
; <<< RemKey Active, Alt-8 to exit >>>
banner	label word
	db ' ',  70h
	db '<', 0f0h
	db '<', 0f0h
	db '<', 0f0h
	db ' ', 0f0h
	db 'R', 0f0h
	db 'e', 0f0h
	db 'm', 0f0h
	db 'k', 0f0h
	db 'e', 0f0h
	db 'y', 0f0h
	db ' ', 0f0h
	db 'A', 0f0h
	db 'c', 0f0h
	db 't', 0f0h
	db 'i', 0f0h
	db 'v', 0f0h
	db 'e', 0f0h
	db ',', 0f0h
	db ' ', 0f0h
	db 'A', 0f0h
	db 'l', 0f0h
	db 't', 0f0h
	db '+', 0f0h
	db '8', 0f0h
	db ' ', 0f0h
	db 't', 0f0h
	db 'o', 0f0h
	db ' ', 0f0h
	db 'e', 0f0h
	db 'x', 0f0h
	db 'i', 0f0h
	db 't', 0f0h
	db ' ', 0f0h
	db '>', 0f0h
	db '>', 0f0h
	db '>', 0f0h
	db ' ',  70h
bannerLength equ ($-banner)/2	; length in words

; If we receive a byte such that:
;
;   LO(pending) XOR ROL(HI(pending)) = RecvdByte XOR 5ah
;
; then pending holds a valid ASCII/scan-code pair.
; In other words the vertical parity of a valid packet
; is 5ah
pending dw 0
pendingCount db 0

;=======================================================================

SpeedUp proc near
	assume ds:nothing,ss:nothing,es:nothing
	mov	al,timer0ModeCmd	; Prepare to set clock speed
	pushf				; save current interrupt enable state
	cli
	out	timerModeReg,al
	mov	ax,(65536/TickFactor)
	out	timer0CountReg,al
	mov	al,ah
	out	timer0CountReg,al
	popf				; restore interrupt enable state
	mov	TickCount,TickFactor
	ret
SpeedUp endp

SlowDown proc near
	assume ds:nothing,ss:nothing,es:nothing
; restore normal systick rate, div = 65536 (0)
	mov	al,timer0ModeCmd	; Prepare to set clock speed
	pushf				; save current interrupt enable state
	cli
	out	timerModeReg,al
	mov	al,0
	out	timer0CountReg,al
	out	timer0CountReg,al
	popf				; restore interrupt enable state
	ret
SlowDown endp

StartTone proc near
	assume ds:nothing,ss:nothing,es:nothing
	push	bx
	mov	bx,ax			; save divide in bx

	pushf				; save current interrupt enable state
	cli				; disable interrupts

	mov	al,timer2ModeCmd
	out	timerModeReg,al
	mov	al,bl
	out	timer2CountReg,al
	mov	al,bh
	out	timer2CountReg,al

; enable speaker
	in	al,portB
	or	al,gate2AndSpeaker2
	out	portB,al

	popf				; restore interrupt enable state
	pop	bx
	ret
StartTone endp

EndTone proc near
	assume ds:nothing,ss:nothing,es:nothing
	push	ax

	pushf				; save current interrupt enable state
	cli				; disable interrupts

; disable speaker
	in	al,portB
	and	al, not gate2AndSpeaker2
	out	portB,al

	popf				; restore interrupt enable state
	pop	ax
	ret
EndTone endp

; delays for "ax" hi-lo transitions on the speaker
ToneDelay proc near
	assume ds:nothing,ss:nothing,es:nothing
	push	ax
	push	bx
	push	cx

	mov	bx,ax			; save count in bx
ToneLoop:
; wait for hi
	sub	cx,cx			; reset timeout counter
HiLoop:
	pushf				; save current interrupt enable state
	cli				; disable interrupts
	mov	al,timer2ReadBackStatusCmd
	out	timerModeReg,al
	in	al,timer2CountReg
	popf				; restore interrupt enable state
	and	al,timerOutputFlag
	jnz	LoWait
	loop	HiLoop
	jmp	short @@Exit		; exit early if we timeout

; wait for lo
LoWait:
	sub	cx,cx			; reset timeout counter
LoLoop:
	pushf				; save current interrupt enable state
	cli				; disable interrupts
	mov	al,timer2ReadBackStatusCmd
	out	timerModeReg,al
	in	al,timer2CountReg
	popf				; restore interrupt enable state
	and	al,timerOutputFlag
	jz	DecClicks
	loop	LoLoop
	jmp	short @@Exit		; exit early if we timeout

DecClicks:
	dec	bx			; drop click count
	jnz	ToneLoop
@@Exit:

	pop	cx
	pop	bx
	pop	ax
	ret
ToneDelay endp

; ax = divisor, bx = duration in clicks
Tone proc near
	assume ds:nothing,ss:nothing,es:nothing
	call	StartTone
	mov	ax,bx
	call	ToneDelay
	jmp	EndTone
Tone endp

VHiTone proc near
	assume ds:nothing,ss:nothing,es:nothing
	mov	ax,VHiToneDivisor
	mov	bx,VHiToneClicks
	jmp	Tone
VHiTone endp

HiTone proc near
	assume ds:nothing,ss:nothing,es:nothing
	mov	ax,hiToneDivisor
	mov	bx,hiToneClicks
	jmp	Tone
HiTone endp

MidTone proc near
	assume ds:nothing,ss:nothing,es:nothing
	mov	ax,midToneDivisor
	mov	bx,midToneClicks
	jmp	Tone
MidTone endp

LoTone proc near
	assume ds:nothing,ss:nothing,es:nothing
	mov	ax,loToneDivisor
	mov	bx,loToneClicks
	jmp	Tone
LoTone endp

Tweedle proc near
	assume ds:nothing,ss:nothing,es:nothing
	push	ax
	push	bx
	call	VHiTone
	call	HiTone
	call	VHiTone
	call	HiTone
	pop	bx
	pop	ax
	ret
Tweedle endp

Buzz proc near
	assume ds:nothing,ss:nothing,es:nothing
	push	ax
	push	bx
	call	MidTone
	call	LoTone
	call	MidTone
	call	LoTone
	pop	bx
	pop	ax
	ret
Buzz endp

if debug
; Display the character in al via a BIOS call
PutChar	proc near
	assume ds:nothing,ss:nothing,es:nothing
	push	ax
	push	bx
	push	si
	push	di
	push	bp
	mov	ah,tty_out
	mov	bl,0
	int	video_int
	pop	bp
	pop	di
	pop	si
	pop	bx
	pop	ax
	ret
PutChar	endp

; Display the byte in al in hex via a BIOS call
PutByte	proc near
	assume ds:nothing,ss:nothing,es:nothing
	push	ax
	shr	al,1
	shr	al,1
	shr	al,1
	shr	al,1
	call	put_nybble
	pop	ax
put_nybble:
	push	ax
	and	al,0fh
	add	al,'0'
	cmp	al,'9'
	jbe	put_digit
	add	al,'A'-'0'-10
put_digit:
	call	PutChar
	pop	ax
	ret
PutByte		endp

PutWord proc near
	assume ds:nothing,ss:nothing,es:nothing
	xchg	ah,al
	call	PutByte
	xchg	ah,al
	jmp	PutByte
PutWord endp

endif

; Save int 16h vector in OldKey and set to offset in NewKey.
HookKey	proc near
	assume ds:nothing,ss:nothing,es:nothing
	push	ds
	push	es

	mov	ax,3516h  ; Get keyboard interrupt
	int	21h

	mov	OldKeyOff,bx
	mov	OldKeySeg,es

	mov	dx,cs
	mov	ds,dx

	mov	dx,NewKey
	mov	ax,2516h
	int	21h

	pop	es
	pop	ds
	ret
HookKey endp

; "Press" Fn and stuff scan code in ch
WithFn proc near
	assume ds:nothing,ss:nothing,es:nothing
	push	es
	cmp	Is100LX,0
	je	@@Exit
	mov	ax,seg biosdata		; set es to the BIOS data seg at 40h
	mov	es,ax
	assume	es:biosdata
	or	KbdFlgs,10h		; Set flag to indicate next key is Fn
	or	MiscFlags,10h		; Set Keyboard flag for FN active
	and	MiscFlags,NOT 20h	; And clear the FN Clear flag
	mov	LastKey,79h		; Put the last key pressed to "Fn"
	mov	al,ch
	out	60h,al			; stuff the key into the KB i/o port
	int	9			; simulate a keyboard interrupt
	and	MiscFlags,NOT 10h	; Clear Keyboard flag for FN active
	or	MiscFlags,20h		; And set the FN Clear flag
@@Exit:
	pop	es
	assume es:nothing
	ret
WithFn endp

ForceUART proc near
	assume ds:nothing,ss:nothing,es:nothing

	cmp	Is100LX,0
	je	PowerOn		; assume the power is on for non-100LX

	pushf			; save interrupt state
	cli			; disable interrupts

	in	al,22h		; read current index register
	mov	ah,al		;   and save it in ah

	mov	al,51h		; select hidden reg 51h
	out	22h,al
	in	al,23h		; read hidden reg 51h
	and	al,21h		; 20h bit is rs232 pwr, 1 bit is IR on
	cmp	al,20h		; we want rs232 on and IR off
	je	RestoreIx
	mov	ax,4900h	; route serial port to wire
	int	15h
	mov	ax,4a01h	; turn serial port on
	int	15h
RestoreIx:
	mov	al,ah		; bring original index back to al
	out	22h,al		; restore original index
	popf			; restore interrupt state
; Fall into PowerOn...

PowerOn:
; check that UART has not been reconfigured
	mov	dx,UartBase		; get base address of UART
	add	dx,lcont		; move to the line control register
	in	al,dx			; read current line configuration
	cmp	al,sioConfig		; does it match ours?
	jne	ResetUART		; if not, reset UART
	or	al,80h			; raise Divisor Latch Access Bit (DLAB)
	out	dx,al

	add	dx,(dlab_l - lcont)	; move to low byte of rate divisor
	in	al,dx			; read it
	cmp	al,low baudRateDivisor	; right value?
	jne	ResetUart		; if not, reset UART

	inc	dx			; move to high byte of rate divisor
	in	al,dx			; read it
	cmp	al,high baudRateDivisor	; right value?
	jne	ResetUART		; if not, reset UART

	add	dx,(lcont - dlab_h)	; move back to line control
	mov	al,sioConfig		; drop DLAB and return to our settings
	out	dx,al
	jmp	short UartOk

ResetUart:
	call	InitUart		; force UART to our config
UartOk:
	ret
ForceUART endp

; This table maps a video index (as returned in bh by int 15h, ah=0dfh)
; to the next zoom-number (as used in al by int 15h, ah=0d0h). This only
; works for text modes which neatly prevents us from trying to zoom a
; SysMgr app - they all run in graphics mode 6. A value of zero says
; "no change". In terms of the index the three zoom "cycles" are:
;
;   2 -> 10 -> 14 -> 2         3 -> 11 -> 15 -> 3         7 -> 9 -> 7
;        12 -> 14                   13 -> 15
;
; The unsupported 40*25 modes zoom to the 40*16 modes
;
; The undocumented "Z" option changes this to include the 40*25 modes:
;
; 2 -> 10 -> 12 -> 14 -> 2     3 -> 11 -> 13 -> 15 -> 3   7 -> 9 -> 7
;

NextZoom label byte	; ix  mode zoom
	db	0	;  0    0    *   40*25  B&W   CGA Low Res Text
	db	0	;  1    1    *   40*25  Color CGA Low Res Text
	db	80h	;  2    2    2   80*25  B&W   CGA Hi Res Text
	db	81h	;  3    3    3   80*25  Color CGA Hi Res Text
	db	0	;  4    4    *  320*200 Color CGA Low Res Graphics
	db	0	;  5    5    *  320*200 B&W   CGA Low Res Graphics
	db	0	;  6    6    *  640*200 Color CGA Hi Res Graphics
	db	21h	;  7    7    7   40*16  B&W   MDA Zoom Text
	db	0	;  8   20h   *  240*128 B&W   95LX MDA graphics
	db	7	;  9    7   21h  80*25  B&W   MDA Text
OptZa	db	84h	; 10    2   80h  64*18  B&W   CGA Zoom Text
OptZb	db	85h	; 11    3   81h  64*18  Color CGA Zoom Text
	db	84h	; 12    2   82h  40*25  B&W   CGA Zoom Text
	db	85h	; 13    3   83h  40*25  Color CGA Zoom Text
	db	2	; 14    2   84h  40*16  B&W   CGA Zoom Text
	db	3	; 15    3   85h  40*16  Color CGA Zoom Text

; First check that UART is powered and in the configuration we need.
; Then if we are in receive mode check for a received character.
CheckUart proc near
	assume ds:nothing,ss:nothing,es:nothing

	push	ax

if localStack
	mov	al,1
	xchg	InCheckUart,al			; test and set
	or	al,al
	jne	EarlyExit

	mov	ss_save,ss			; save caller's ss
	mov	sp_save,sp			;  and sp
	mov	ax,cs				; use local stack
	mov	ss,ax
	mov	sp,offset stack_top
endif

	push	bx
	push	cx
	push	dx
	push	si
	push	di
	push	bp
	push	ds
	push	es

	mov	ax,seg biosdata		; set ds to the BIOS data seg at 40h
	mov	ds,ax
	assume	ds:biosdata

	call	ForceUART

	cmp	Receive,0		; if we are in send mode don't
	je	@@Exit			;   check the UART
	mov	dx,UartBase		; get base address of UART
	add	dx,lstat		; move to the line status register
	in	al,dx			;  and read it
if debug
; any errors?
	test	al,(pcOverrunError OR pcParityError OR pcFramingError)
	jz	NoLineError
	push	ax
	mov	al,"E"
	call	PutChar
	pop	ax
	push	ax
	call	PutByte
	mov	al," "
	call	PutChar
	pop	ax
NoLineError:
endif
	test	al,pcRecvDataAvailable	; received data ready?
	jz	@@Exit
	add	dx,(rx - lstat)		; move to the receive data register
	in	al,dx			; read the sent byte
if debug
	push	ax
	mov	al,"R"
	call	PutChar
	pop	ax
	push	ax
	call	PutByte
	mov	al," "
	call	PutChar
	pop	ax
endif

; reset auto-sleep countdown
	mov	cx,sleepTimeout		; reload sleepCountdown
	mov	sleepCountdown,cx

	mov	ah,al			; save new char in ah
	mov	cx,pending		; get previously received bytes
	cmp	pendingCount,2		; two valid bytes in pending?
	jb	partialPacket
	mov	al,ch			; calc ROL(hi) XOR lo XOR new
	rol	al,1
	xor	al,cl
	xor	al,ah
	cmp	al,5ah
	je	ValidPacket
partialPacket:
	inc	pendingCount		; count the new byte
	mov	cl,ch			; push the new byte into pending
	mov	ch,ah
	mov	pending,cx
	jmp	@@Exit			; leave and wait for next byte

ValidPacket:
	mov	pendingCount,0		; mark pending bytes as consumed
	mov	pending,0		; not really needed if the code works
	mov	ax,cx			; bring scan-code/ASCII to ax
	cmp	ah,0f5h			; special code w/scan-code?
	jne	NotShift
; if the high byte is 0f5h then the low byte is the int 9 scan code
DoShift:
	cmp	Is100LX,0
	jne	DoStuff
	jmp	@@Exit			; can't stuff the key I/O on non-HP

DoStuff:
	out	60h,al			; stuff the key into the KB i/o port
	int	9			; simulate a keyboard interrupt
	jmp	@@Exit

NotShift:
	cmp	ah,0f4h			; other special codes?
	jne	StuffIt
	cmp	al,low LockCode		; turn Caps Lock on?
	jne	NotLock
	mov	cl,40h			; desired shift state w/caps Lock on
SetLock:
	mov	ah,2			; get shift status
	pushf				; go through old vector
	call	[OldKey]		;  to prevent recursion
	xor	al,cl
	test	al,40h
	jz	NotUnlock		; Caps already correct
	mov	al,LShiftScan	  	; "Press" the left shift key
	out	60h,al
	int	9
	mov	al,CapsScan	  	; "Press" the Caps key
	out	60h,al
	int	9
	mov	al,CapsScan or 80h	; "Release" the Caps key
	out	60h,al
	int	9
	mov	al,LShiftScan or 80h	; "Release" the left shift key
	jmp	short DoShift

NotLock:
	cmp	al,low UnlockCode	; turn Caps Lock off?
	jne	NotUnlock		; no known 0f5xx key, ignore it
	sub	cl,cl			; desired shift state w/caps Lock off
	jmp	SetLock

StuffIt:
	or	cl,cl			; ASCII = 0?
	jnz	NotFnFx
	cmp	cx,0db00h		; Fn F1 ?
	jb	NotFnFx
	cmp	cx,0e400h		; Fn F10 ?
	ja	NotFnFx
	sub	ch,0dbh-3bh		; 0dbh..0e4h -> 3bh..44h
	call	WithFn			; stuff scan code with Fn "pressed"
	jmp	@@Exit

NotFnFx:
; The scancode for Alt-downarrow is 0A000, this is the same scancode
; as the ON key. If you stuff a 0A000 and you are running on batteries
; the 100LX sort-of turns off: the screen goes blank.
; Solution: discard 0A000's on the LX
	cmp	Is100LX,0
	jz	NotOnKey
	cmp	cx,0a000h
	je	IgnoreKey
NotOnKey:
; The SysMgr applications handle the ZOOM scan code on their own. So
; stuffing the scancode works. But zooming in DOS is done below interrupts
; 9 and 16. So we have to do it ourselves.
	cmp	cx,0d000h	; scancode for ZOOM
	jne	NotZoom
	mov	ah,0dfh		; Get the video mode/zoom index in bh
	int	10h
	mov	bl,bh		; convert bh to a word in bx
	sub	bh,bh
	mov	al,NextZoom[bx]	; get next zoom number
	or	al,al		; non-zoomable?
	jz	NotZoom
	mov	ah,0d0h		; Text Zoom function
	int	10h
NotZoom:

; Very, very odd and very, very frustrating. We would like to stuff our
; keys by simply calling the "write key" function. This works fine in all
; cases EXCEPT when you run DOS or a DOS app from SysMgr (<&..>D). If you
; do then each key is duplicated 50-100% of the time. Directly manipulating
; the scan code buffer works in all cases so for now that is what we do.
if 0
	mov	ah,5			; ch = scan code, cl = ASCII
	int	16h			; add to typeahead buffer
else
	pushf				; save current interrupt enable state
	cli				; disable interrupts
	mov	bx,bufferTail
	mov	dx,bx			; advance the ptr before we store
	inc	dx			; to check for overflow
	inc	dx
	cmp	dx,bufferEnd
	jb	NoWrap
	mov	dx,bufferStart
NoWrap:
	cmp	dx,bufferHead
	je	Overflow
	mov	[bx],cx			; save scan code
	mov	bufferTail,dx		; save new tail ptr
	or	[LastInterrupt],2	; key int was last interrupt
					; (PUSHKEYS.COM does this)
Overflow:
	popf				; restore interrupt enable state
endif
if debug
	mov	al,"W"
	call	PutChar
	mov	ax,cx
	call	PutWord
	mov	al," "
	call	PutChar
endif

NotUnlock:			; no known 0f5xx key, ignore it
IgnoreKey:

@@Exit:
	pop	es
	pop	ds
	pop	bp
	pop	di
	pop	si
	pop	dx
	pop	cx
	pop	bx

if localStack
	mov	ss,ss_save
	mov	sp,sp_save
	mov	InCheckUart,0		; clear recursion flag
endif

EarlyExit:
	pop	ax

	ret
CheckUart endp

;=======================================================================

NewTick proc	far
	assume ds:nothing,ss:nothing,es:nothing
	push	ax
	cmp	Enabled,0
	jz	SlowTick
	call	CheckUart	; poll the UART every actual tick
	dec	TickCount
	jnz	cont6
	mov	TickCount,TickFactor
SlowTick:
	pushf
	call	[OldTick]
	jmp	short cont7
cont6:
	mov	al,20h		; EOI
	out	20h,al		; Send EOI to 8259 int. controller
cont7:
	pop	ax
	iret
NewTick endp

NewMpx proc near
	assume ds:nothing,ss:nothing,es:nothing
	cmp	ah,OurMpxNum		; our multiplex ID?
	jne	UseOldMpx
	or	al,al			; the generic ID function code?
	jnz	NotID
	dec	al			; 0 >> ff
	iret

NotID:
	cmp	al,RemKeyMpxFn		; our (only) function code
	jne	UseOldMpx
	mov	ax,bcdVersion
	mov	bx,"Re"
	mov	cx,"mK"
	mov	dx,"ey"
	push	cs			; return segment of resident code in es
	pop	es
	iret

UseOldMpx:
	jmp	[OldMpx]

NewMpx endp

;=======================================================================

InitUart proc near
	assume ds:nothing,ss:nothing,es:nothing

	mov	dx,UartBase		; get base address of UART
	add	dx,lcont		; move to line control register
	mov	al,80h			; enable DLAB
	out	dx,al

	add	dx,(dlab_l - lcont)	; move to low byte of DLAB
	mov	al,low baudRateDivisor
	out	dx,al

	inc	dx			; move to high byte of DLAB
	mov	al,high baudRateDivisor
	out	dx,al

	add	dx,(lcont - dlab_h)	; move back to line control register
	mov	al,sioConfig		; set our configuration
	out	dx,al

	add	dx,(mcont - lcont)	; move to modem control register
	mov	al,3			; raise dtr, rts
	out	dx,al

	mov	dx,UartBase		; get base address of UART
	in	al,dx			; flush the UART
	jmp	$+2			; delay
	in	al,dx

	ret
InitUart endp

; Returns non-zero if swap fails
BannerSwap proc near
	assume ds:nothing,ss:nothing,es:nothing
	push	ax
	push	bx
	push	cx
	push	dx
	push	di
	push	si
	push	ds
	mov	ax,seg biosdata		; set ds to the
	mov	ds,ax			; BIOS data seg at 40h
	assume	ds:biosdata
	mov	cx,0b000h		;assume seg. of mono
	mov	al,crt_mode
	cmp	al,7			;mono mode ?
	je	mode_ok
	and	al,0feh			;test for 2 or 3
	cmp	al,2			;(80*25 b&w or color)
	jne	@@Exit			;return non-zero for unsupported mode
	mov	ch,0b8h			;color seg.
mode_ok:
	mov	ax,crt_cols		;1..80 -> 0..79
	dec	si
	mov	si,ax			; * 25 (19h) bytes, 12.5 lines
	add	ax,ax
	add	ax,ax
	add	ax,ax
	add	si,ax
	add	ax,ax
	add	si,ax
	add	si,crt_start		;start of buffer
	mov	ds,cx			;set the seg.
	assume ds:nothing
	mov	cx,bannerLength
	sub	si,cx			; center banner on line
	and	si, not 1		; force to even address
	lea	di,banner
	pushf				; save current interrupt enable state
	cli				; disable interrupts
BannerLoop:
	mov	ax,cs:[di]		;get char/attrib to swap in
	xchg	ax,[si]
	mov	cs:[di],ax		;save swapped char/attrib
	inc	si
	inc	si
	inc	di
	inc	di
	loop	BannerLoop
	popf				; restore interrupt enable state
	sub	ax,ax			; return zero flag for success
@@Exit:
	pop	ds
	assume ds:nothing
	pop	si
	pop	di
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	ret
BannerSwap endp

; Returns with zero flag if hot-key caught
CheckForHot proc near
	assume ds:nothing,ss:nothing,es:nothing
	cmp	ax,HotKey		; hot key?
	jne	@@Exit
	xor	Enabled,1		; toggle enable
	jz	Disabled
	call	SpeedUp
	call	Tweedle
	jmp	short @@ExitWithZero

Disabled:
	call	SlowDown
	call	Buzz
@@ExitWithZero:
	sub	ax,ax
@@Exit:
	ret
CheckForHot endp

NewKeyRecv	proc far
	assume ds:nothing,ss:nothing,es:nothing
	cmp	ah,0
	je	CheckRead	;  0 - Read Key
	cmp	ah,10h
	je	CheckRead	; 10 - Extended Read Key
	cmp	Is100LX,0	; for a PC the rest default to CheckBefore
	jz	CheckBefore
	cmp	ah,13h
	je	CheckEvent	; 13 - Event Wait
	cmp	ah,14h
	je	CheckEvent	; 14 - Event Wait with Timeout
; Otherwise for these and any unknown functions use CheckBefore:
;  1 - Check Key
;  2 - Get Shift Status
;  3 - Set Repeat Rate
;  5 - Stuff Key
; 11 - Extended Check Key
; 12 - Extended Shift Status
CheckBefore:
	push	ax			; save function code
; Peek at the next key. Use the old vector to prevent recursion.
	mov	ah,11h
; Simulate an int through the old vector:
	pushf
	call	[OldKey]
	jz	NotHot
	call	CheckForHot
	jnz	NotHot
	mov	ah,10h			; eat the hot-key
	pushf
	call	[OldKey]
NotHot:
	pop	ax			; recover original function code
	jmp	[OldKey]

CheckRead:
	push	ax			; save function code
; Simulate an int through the old vector:
	pushf
	call	[OldKey]		; do read operation
	call	CheckForHot
	jnz	DidNotReadHot
	pop	ax			; recover saved function code
	jmp	CheckRead		; read another key

DidNotReadHot:
	add	sp,2			; discard saved function code
	ret	2		; discard flags pushed by original int 16

CheckEvent:
; Simulate an int through the old vector:
	pushf
	call	[OldKey]		; do event-wait operation
	pushf				; save event flags
	push	ax			; save key or shift flags
	jz	NotHotEvent		; if zero no key so no hot key
	call	CheckForHot
NotHotEvent:
	pop	ax			; recover key or shift flags
	popf				; recover event flags
	ret	2		; discard flags pushed by original int 16


NewKeyRecv endp

;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

RecvEnd label byte      ; End of receiver TSR, all above is resident code

; We need to send two bytes (ASCII, then scan code) in a form that can
; be validated. To do this we send three bytes: low, high and low XOR
; ROL(high) XOR 5a.  The receiver collects bytes and knows that a valid
; word has been received when 1st XOR ROL(2nd) XOR = 5ah. This scheme
; is not perfect because the right pair of mis-sent words could look
; like a single valid packet. But in general it should tend to
; synchronize.

SendWord proc near
	assume ds:nothing,ss:nothing,es:nothing
	push	ax
	push	cx

	mov	cx,ax			; save word to send in cx
	mov	al,cl			; send the low byte
	call	SendByte
	mov	al,ch			; send the high byte
	call	SendByte
; send low XOR rol(high) XOR 5a as the end of a valid packet
	mov	al,ch
	rol	al,1
	xor	al,cl
	xor	al,5ah
	call	SendByte

	pop	cx
	pop	ax
	ret
SendWord endp

; Send the byte in al out the serial port
SendByte proc near
	assume ds:nothing,ss:nothing,es:nothing

if debug
	push	ax
	mov	al,"S"
	call	PutChar
	pop	ax
	push	ax
	call	PutByte
	mov	al," "
	call	PutChar
	pop	ax
endif

	push	dx
	push	ax
	mov	dx,UartBase	; get base address of UART
	add	dx,lstat	; move to line status register
UartWait:
	in	al,dx
	test	al,20h		; is the transmit buffer empty?
	je	UartWait
	pop	ax
	add	dx,(tx - lstat)	; move to the transmit buffer register
	out	dx,al
	pop	dx
	ret
SendByte endp

NewKeySend proc far
	assume ds:nothing,ss:nothing,es:nothing

	push	ax
	push	bx
	push	cx
	push	dx
	push	es

; Peek at the next key. Use the old vector to prevent recursion.
	mov	ah,11h
	pushf
	call	[OldKey]
	jz	@@Exit
	cmp	ax,HotKey		; hot key?
	jne	@@Exit
	mov	ah,10h			; eat the hot-key
	pushf
	call	[OldKey]
	call	BannerSwap		; put up "RemKey Active" message
	jz	BannerUp
	call	Tweedle			; give audible signal if banner fails
	call	SendKeys
	call	Buzz			; give audible signal on exit
	jmp	short @@Exit

BannerUp:
	call	SendKeys
	call	BannerSwap		; restore screen
@@Exit:
	pop	es
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	jmp	[OldKey]
NewKeySend endp

; Special PC scan codes and what they map to on the 100LX
ScanMap label word
;		  PC    100LX
	dw	03300h,	0d100h	; Alt "," to DATE
	dw	03400h,	0d200h	; Alt "." to TIME
	dw	0a300h,	0d400h	; Alt Del to CUT
	dw	00e00h,	0d500h	; Alt Bsp to COPY
	dw	0a200h,	0d600h	; Alt Ins to PASTE
	dw	 7800h,	0a800h	; Alt ! to Filer
	dw	 7900h,	0ac00h	; Alt @ to cc:Mail
	dw	 7a00h,	0b000h	; Alt # to Appt
	dw	 7b00h,	0b400h	; Alt $ to Phone
	dw	 7d00h,	0b800h	; Alt ^ to Memo
	dw	 7e00h,	0bc00h	; Alt & to Lotus
	dw	 8000h,	0c000h	; Alt ( to HP Calc
	dw	 8100h,	0a400h	; Alt ) to More

; The Ctrl-Blue keys:
; 
; The 0f5h in the lower byte of these PC scan codes encodes the
; fact that these pairs are used when Ctrl and Alt are pushed.

	dw	 78f5h,	0ae00h	; Ctrl Alt ! to Setup
	dw	 79f5h,	0b200h	; Ctrl Alt @ to Data Comm
	dw	 7af5h,	0b600h	; Ctrl Alt # to Stopwatch
	dw	 7bf5h,	0ba00h	; Ctrl Alt $ to Database
	dw	 7df5h,	0be00h	; Ctrl Alt ^ to Note Taker
	dw	 7ef5h,	0c200h	; Ctrl Alt & to DOS
	dw	 80f5h,	0c600h	; Ctrl Alt ( to World Time
	dw	 81f5h,	0aa00h	; Ctrl Alt ) to System Macros

	dw	 68f5h, 0db00h	; Ctrl Alt F1  to Fn F1,  macro 1
	dw	 69f5h, 0dc00h	; Ctrl Alt F2  to Fn F2,  macro 2
	dw	 6af5h, 0dd00h	; Ctrl Alt F3  to Fn F3,  macro 3
	dw	 6bf5h, 0de00h	; Ctrl Alt F4  to Fn F4,  macro 4
	dw	 6cf5h, 0df00h	; Ctrl Alt F5  to Fn F5,  macro 5
	dw	 6df5h, 0e000h	; Ctrl Alt F6  to Fn F6,  macro 6
	dw	 6ef5h, 0e100h	; Ctrl Alt F7  to Fn F7,  macro 7
	dw	 6ff5h, 0e200h	; Ctrl Alt F8  to Fn F8,  macro 8
	dw	 70f5h, 0e300h	; Ctrl Alt F9  to Fn F9,  macro 9
	dw	 71f5h, 0e400h	; Ctrl Alt F10 to Fn F10, macro 10

; The Alt-Blue keys:
; 
; The 0f6h in the lower byte of these PC scan codes encodes the
; fact that these pairs are used when Shift and Alt are pushed.

	dw	 78f6h,	0ab00h	; Shift Alt ! to Alt Filer
	dw	 79f6h,	0af00h	; Shift Alt @ to Alt cc:Mail
	dw	 7af6h,	0b300h	; Shift Alt # to Alt Appt
	dw	 7bf6h,	0b700h	; Shift Alt $ to Alt Phone
	dw	 7df6h,	0bb00h	; Shift Alt ^ to Alt Memo
	dw	 7ef6h,	0bf00h	; Shift Alt & to Alt Lotus
	dw	 80f6h,	0c300h	; Shift Alt ( to Alt HP Calc
	dw	 81f6h,	0a700h	; Shift Alt ) to Alt More

 
; Shifted cursor keys:
; 
; In a number of places in the 100LX apps a cursor key combined with
; shift is used. Most commonly this is used in a multi-line text field
; to select or highlight a portion of the text. Memo, note fields and
; the system macro edit screen are all examples of this. Also in Appt
; shift up-cursor and shift down-cursor can move to the previous or
; next week.
; 
; The scan codes that these apps are checking for appear to be the scan
; codes of the digits on a normal PCs numeric pad. This makes sense as
; that is what this pad will produce when NumLock is off, shift is held
; and a digit is pressed. But what about the dedicated cursor keys in
; the inverted "T" on an extended keyboard?  These produce the scancode
; of a cursor key in the high byte and 0e0h in the low byte. RemKey
; just replaces the 0e0h with 0 which produces the scancodes of the
; numeric pad cursor keys. Most apps in the 100LX will check if the
; shift key is down and treat such an event as a "shift selection".
; Memo and note fields behave as expected.
; 
; But the system macro editor and the caledar in Appt appear to only
; check for the shift bit and the numeric pad scancodes. So the
; following entries map the dedicated up, down, left, right, home, end,
; Page Up and Page Down keys to the numeric pad scan codes when the
; shift key is down.  The 0f7h in the low byte of the first
; scancode/ASCII pair artificially endcodes that only the shift key was
; pressed.

	dw	4ff7h, 4f31h	; Shift end   (pad 1)
	dw	50f7h, 5032h	; Shift down  (pad 2)
	dw	51f7h, 5133h	; Shift pg dn (pad 3)
	dw	4bf7h, 4b34h	; Shift left  (pad 4)

	dw	4df7h, 4d36h	; Shift right (pad 6)
	dw	47f7h, 4737h	; Shift home  (pad 7)
	dw	48f7h, 4838h	; Shift up    (pad 8)
	dw	49f7h, 4939h	; Shift pg up (pad 9)

ScanEntries equ ($-ScanMap)/4	; 4 bytes, 2 word per entry

; These are stuffed via int 9 when the corresponding code is received.
;
;	40:17	byte	Keyboard flag byte 0
;
;		і7і6і5і4і3і2і1і0і keyboard flag byte 0
;		 і і і і і і і АДДД right shift key depressed
;		 і і і і і і АДДДД left shift key depressed
;		 і і і і і АДДДДД CTRL key depressed
;		 і і і і АДДДДДД ALT key depressed
;		 і і і АДДДДДДД scroll-lock is active
;		 і і АДДДДДДДД num-lock is active
;		 і АДДДДДДДДД caps-lock is active
;		 АДДДДДДДДДД insert is active
;
ShiftMap label byte

; codes for released keys must come first
	db	RShiftScan+80h	; stuffed via int 9 when UnRShiftCode received
	db	LShiftScan+80h	;    "     "   "  "  "   UnLShiftCode  "

; followed by codes for pressed keys
	db	RShiftScan	; stuffed via int 9 when RShiftCode received
	db	LShiftScan	;    "     "   "  "  "   LShiftCode  "

ShiftEntries equ ($-ShiftMap)

; Send keys pressed on the local keyboard out the serial port until the
; hot key is pressed.
;
; The dl reg holds the current shift state, dh holds the previous.
; bh holds bits which have changed from 0 to 1, bl from 1 to 0
;
; The si register is used to detect a press and release of either Alt-key:
;   bit 0 - either Alt-key was pressed

SendKeys proc near
	assume ds:nothing,ss:nothing,es:nothing
	push	ax
	push	bx
	push	cx
	push	dx
	push	di
	push	si
	push	ds
	push	es

	call	ForceUART

	mov	ah,2		; get shift status
	pushf			; go through old vector to prevent recursion
	call	[OldKey]
	mov	dl,al		; put current shift state in dl

KeyLoop:
	mov	dh,dl		; save previous shift state in dh
	mov	ah,2		; get shift status
	pushf			; go through old vector to prevent recursion
	call	[OldKey]
	mov	dl,al		; save new shift state in dl

; Calculate which bits have changed from 0 to 1 in bh and from 1 to 0 in bl
	mov	bl,dh		; get last shift state
	xor	bl,dl		; calculate which bits changed either way
	mov	bh,bl		; save in bh
	and	bh,dl		; isolate bits that changed from 0 to 1
	and	bl,dh		; isolate bits that changed from 1 to 0

; Although the ASCII and scan codes we send already contains our shift state
; we need to explicitly set the shift state on the remote 100LX because
; some of the built-in apps directly test the shift state. Examples are
; shift-cursor in Memo and Appt. Other DOS programs also directly test the
; state of Ctrl and Alt so we send changes on these also.

; The first half of the loops look for released keys, the second for
; pressed keys.
	mov	cl,bl		; gets bits that have changed from 1 to 0
	sub	di,di		; initial offset into ShiftMap is zero
ShiftMapLoop:
	cmp	di,ShiftEntries/2
	jne	NotHalfway
	mov	cl,bh		; gets bits that have changed from 0 to 1
NotHalfway:
	test	cl,1		; did bit change?
	jz	NoShiftChg
	mov	al,ShiftMap[di]
	mov	ah,0f5h
	call	SendWord	; send scan code to effect shift change
NoShiftChg:
	shr	cl,1
	inc	di
	cmp	di,ShiftEntries
	jb	ShiftMapLoop

; has Caps Lock state changed?
	mov	ax,LockCode	; assume Caps Lock is on
	test	bh,40h		; did Caps Lock bit change to 1?
	jnz	SendLock
	mov	ax,UnlockCode	; assume Caps Lock is off
	test	bl,40h		; did Caps Lock bit change to 0?
	jz	NoLockChange
SendLock:
	call	SendWord	; send encoding of Caps (Un)Lock
NoLockChange:
; If either "Alt" is pressed and released then we simulate a press of the
; MENU key on the remote 100LX

	test	bh,8		; either Alt-key changed to pressed?
	jz	CheckAltUp	; jump if no change
	mov	si,1		; remember that Alt-key state changed
	jmp	short CheckBIOS

CheckAltUp:
	test	bl,8		; either Alt-key changed to released?
	jz	CheckBIOS	; jump if no change
; Was Alt just pressed and released with no intervening keystroke?
	or	si,si
	mov	si,0		;  (reset the flags no matter what)
	jz	CheckBIOS
	mov	ax,MenuKeyCode	; scan code for menu key
	jmp	SendIt

CheckBIOS:
	mov	ah,11h		; key waiting ?
	pushf
	call	[OldKey]
	jz	KeyLoop
	mov	ah,10h		; read key
	pushf
	call	[OldKey]
if debug
	push	ax
	mov	al,"K"
	call	PutChar
	pop	ax
	push	ax
	call	PutWord
	mov	al," "
	call	PutChar
	pop	ax
endif
	sub	si,si		; Any key cancels Alt key down-tap state
	test	dl,8		; Alt key down?
	jz	NotAltSpace
	cmp	ax,3920h	; space bar ?
	jne	NotAltSpace
	mov	ax,0d000h	; Send Alt-space as ZOOM
	jmp	SendIt

NotAltSpace:
	cmp	ax,HotKey
	je	@@Exit

; Some funny keys like the "/" and the <enter> on the numeric pad have a
; scan code of 0e0h in the high byte with the correct ASCII in the low byte.
; But this somehow conflicts with the 100LX's use of scan code 0e0h for
; Fn F6 and is not recognized. The hack is to replace the 0e0h scan code
; with zero.
	cmp	ah,0e0h		; fold extended scan codes into normal ones
	jne	NotHiE0
	or	al,al		; Let Fn F6 (0e000h) through
	je	NotHiE0		; (this will only happen when 100LX -> 100LX)
	sub	ah,ah		; LX apps don't recognize 0E0xx scan codes
NotHiE0:

; Encode special keys by searching for them in the "exception" table

; If a Shift and Alt key is pressed and the low byte is zero then
; set the low byte to 0f6h.
;
; If a Ctrl and Alt key is pressed and the low byte is zero then
; set the low byte to 0f5h.
; 
; If a Shift is pressed and the low byte is 0e0h then
; set the low byte to 0f7h.
;
; These artificial encodings allows us to have entries for an
; Alt-key, Shift-key and Ctrl-Alt-key in the same table.

	mov	cx,ax		; save unmodified scan/ASCII pair

	test	dl,8		; Alt key pressed?
	jz	NotAlt
	or	al,al		; low byte of scan/ASCII zero?
	jnz	NotAlt
	test	dl,4		; Ctrl key pressed?
	jz	NotCtrlAlt
	mov	al,0f5h		; Mark key as Ctrl-Alt'ed
	jmp	short MatchScancode

NotCtrlAlt:
	test	dl,3		; either shift key pressed?
	jz	MatchScancode
	mov	al,0f6h		; Mark key as Shift-Alt'ed
	jmp	short MatchScancode

NotAlt:
; Test for the dedicated cursor keys with shift

; Some funny keys like the cursor inverted "T" pad contain the expected
; scan code in the high byte but have 0e0h instead of zero in the low byte.
	cmp	al,0e0h		; fold extended scan codes into normal ones
	jne	NotLoE0
	or	ah,ah		; let alpha (Alt-224 or 0e0h) through
	je	NotLoE0
	sub	cl,cl		; LX apps don't recognize xxE0 scan codes
	test	dl,3		; either shift key pressed?
	jz	NonMapped	; if not shifted just pass through
	mov	al,0f7h		; Mark key as Shift-cursor
	jmp	short MatchScancode

NotLoE0:

MatchScancode:
	mov	di,(ScanEntries-1)*4 ; offset of last entry
ScanMapLoop:
	cmp	ax,ScanMap[di]
	jne	NoMatch
	mov	ax,ScanMap[di+2]
	jmp	short SendIt
NoMatch:
	sub	di,4		; move to next pair of words
	cmp	di,-4
	jne	ScanMapLoop
NonMapped:
	mov	ax,cx		; recover saved unmodified scancode/ASCII
; if the scan-code/ASCII pair are not in the exception list and we
; fall out of the loop then just send them as is:

SendIt:
	call	SendWord
IgnoreKey:
	jmp	KeyLoop

@@Exit:
; The only way to exit is with the hot key so...
	mov	Enabled,0		; set enabled false
	pop	es
	pop	ds
	pop	si
	pop	di
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	ret
SendKeys endp

;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

SendEnd label byte      ; End of sender TSR, all above is resident code

if fileOpt
AscToScan label byte
	db	03h	; Control "@"
	db	1eh	; Control "A"
	db	30h	; Control "B"
	db	2eh	; Control "C"
	db	20h	; Control "D"
	db	12h	; Control "E"
	db	21h	; Control "F"
	db	22h	; Control "G"
	db	0eh	; Backspace
	db	0fh	; Tab
	db	24h	; Control "J"
	db	25h	; Control "K"
	db	26h	; Control "L"
	db	1ch	; Enter
	db	31h	; Control "N"
	db	18h	; Control "O"
	db	19h	; Control "P"
	db	10h	; Control "Q"
	db	13h	; Control "R"
	db	1fh	; Control "S"
	db	14h	; Control "T"
	db	16h	; Control "U"
	db	2fh	; Control "V"
	db	11h	; Control "W"
	db	2dh	; Control "X"
	db	15h	; Control "Y"
	db	2ch	; Control "Z"
	db	01h	; Esc
	db	2bh	; Control "\"
	db	1bh	; Control "]"
	db	07h	; Control "^"
	db	0ch	; Control "_"
	db	39h	; SPACE
	db	02h	; "!"
	db	28h	; Double Quote
	db	04h	; "#"
	db	05h	; "$"
	db	06h	; "%"
	db	08h	; "&"
	db	28h	; "'"
	db	0ah	; "("
	db	0bh	; ")"
	db	09h	; "*"
	db	0dh	; "+"
	db	33h	; ","
	db	0ch	; "-"
	db	34h	; "."
	db	35h	; "/"
	db	0bh	; "0"
	db	02h	; "1"
	db	03h	; "2"
	db	04h	; "3"
	db	05h	; "4"
	db	06h	; "5"
	db	07h	; "6"
	db	08h	; "7"
	db	09h	; "8"
	db	0ah	; "9"
	db	27h	; ":"
	db	27h	; ";"
	db	33h	; "<"
	db	0dh	; "="
	db	34h	; ">"
	db	35h	; "?"
	db	03h	; "@"
	db	1eh	; "A"
	db	30h	; "B"
	db	2eh	; "C"
	db	20h	; "D"
	db	12h	; "E"
	db	21h	; "F"
	db	22h	; "G"
	db	23h	; "H"
	db	17h	; "I"
	db	24h	; "J"
	db	25h	; "K"
	db	26h	; "L"
	db	32h	; "M"
	db	31h	; "N"
	db	18h	; "O"
	db	19h	; "P"
	db	10h	; "Q"
	db	13h	; "R"
	db	1fh	; "S"
	db	14h	; "T"
	db	16h	; "U"
	db	2fh	; "V"
	db	11h	; "W"
	db	2dh	; "X"
	db	15h	; "Y"
	db	2ch	; "Z"
	db	1ah	; "["
	db	2bh	; "\"
	db	1bh	; "]"
	db	07h	; "^"
	db	0ch	; "_"
	db	29h	; "`"
	db	1eh	; "a"
	db	30h	; "b"
	db	2eh	; "c"
	db	20h	; "d"
	db	12h	; "e"
	db	21h	; "f"
	db	22h	; "g"
	db	23h	; "h"
	db	17h	; "i"
	db	24h	; "j"
	db	25h	; "k"
	db	26h	; "l"
	db	32h	; "m"
	db	31h	; "n"
	db	18h	; "o"
	db	19h	; "p"
	db	10h	; "q"
	db	13h	; "r"
	db	1fh	; "s"
	db	14h	; "t"
	db	16h	; "u"
	db	2fh	; "v"
	db	11h	; "w"
	db	2dh	; "x"
	db	15h	; "y"
	db	2ch	; "z"
	db	1ah	; "{"
	db	2bh	; "|"
	db	1bh	; "}"
	db	29h	; "~"
	db	0eh	; Delete (Ctrl-Backspace)
endif

Logon label byte
	db 13,10
	db 'RemKey V1.00 - Remote Keyboard Via Serial Port.'
	db 13,10
	db '$'

UninstMess label byte
	db 'RemKey has been uninstalled.'
	db 13,10
	db '$'

MpxErrMess label byte
	db 'Multiplex error.'
	db 13,10
	db '$'

Not100LXMess label byte
	db 'RemKey only works on a standard PC or the HP 100LX palmtop.'
	db 13,10
	db '$'

NotInstMess label byte
	db 'RemKey is not already installed, it can not be uninstalled.'
	db 13,10
	db '$'

Installed label byte
	db 'RemKey has been installed, press Alt 8 to toggle enable.'
	db 13,10
	db 'Type "REMKEY /U" to uninstall.'
	db 13,10,'$'

SendHelp label byte
	db 13,10
	db 'Ctrl+Alt: Setup  Data   Stop   Data   Note    DOS   World  System',13,10
	db 'і                Comm   watch  base   Taker         Time   Macros',13,10
	db 'і',13,10
	db 'і    Alt: Filer   cc:   Appt   Phone   Memo  Lotus   HP     App',13,10
	db 'і    і           Mail                         123   Calc    Mgr',13,10
	db 'і    і',13,10
	db 'і    і      !      @      #      $      ^      &      (      )',13,10
	db 'АДДДДБДДДДД 1      2      3      4      6      7      9      0',13,10
	db 13,10
	db 'Press Shift+Alt+[12346790] for Alt+Blue keys.',13,10
	db 13,10
	db '           Zoom   Date   Time    Cut  Copy  Paste',13,10
	db 'Press Alt+ Space  Comma  Period  Del  BSpc  Ins',13,10
	db 13,10
	db 'Press Ctrl+Alt+F1..F10 to play back macro, ',13,10
	db '      Shift+Ctrl+Alt+F1..F10 to record.',13,10
	db 13,10
	db 'Tap either Alt key for the Menu key, press Alt+8 to exit.'
	db 13,10,0

HelpMess label byte
	db 13,10
	db 'Usage: RemKey /[1,2,3,4] /[S,R,T] /[E,D] /C /U',13,10
	db '  /1,2,3,4: Comm Port, default is configured as '
HelpCommDef label byte
	db '1',13,10
	db 13,10
	db '  /S Send keys to remote computer (default on PC).',13,10
	db '  /R Receive keys, install as TSR (default on 100LX).',13,10
	db '  /T TSR sender, the hot-key is Alt+8.',13,10
	db 13,10
	db '  /E Enable this or already installed program (default for receive).',13,10
	db '  /D Disable this or already installed program (default for send).',13,10
	db 13,10
	db '  /C Configure specified serial port as default and exit.',13,10
	db '  /U Unload a previously loaded copy and exit.',13,10
	db 13,10
	db 'Example - RemKey receiving on COM3, start disabled:',13,10
	db '  remkey /r/3/d',13,10
CrLf label byte
	db 13,10
	db '$'

ComMess label byte
	db 'Using serial port COM'
ComNum	db '1.'
	db 13,10
	db '$'

ResMess label byte
	db 'RemKey is currently installed.'
	db 13,10,'$'

UpdateMess label byte
	db 'The parameters have been updated.'
	db 13,10,'$'

IncompatMess label byte
	db 'Incompatible versions.'
	db 13,10,'$'

NoUninstMess label byte
	db "Can't uninstall RemKey, try exiting AppMgr completely."
	db 13,10,'$'

NoInstMess label byte
	db "Can't install RemKey, try exiting AppMgr completely."
	db 13,10,'$'

ConfigMess label byte
	db 'Configuring '
; Nothing between ConfigMess and OurName, please!
OurName db 128 dup (0)

WrtErrMess label byte
	db 'Error updating file.'
	db 13,10,'$'

if fileOpt
ReadErrMess label byte
	db 'Error reading file.'
	db 13,10,'$'

SendAbortMess label byte
	db 'File send terminated.'
	db 13,10,'$'
endif

UartBases dw com1, com2, com3, com4

FindMpxNum proc near
	assume ds:nothing,ss:nothing,es:nothing

; Save all registers because the multiplex code we probe could change
; any register.

	push	ax
	push	bx
	push	cx
	push	dx
	push	si
	push	di
	push	bp
	push	ds
	push	es

FindMpxLoop:
	mov	ah,ResidentMpxNum
	mov	al,0			; MPX number in use?
	int	MpxInt
	cmp	al,0
	je	FreeMpxNum
	mov	ah,ResidentMpxNum
	mov	al,RemKeyMpxFn		; in use by RemKey?
	int	MpxInt
	cmp	bx,"Re"
	jne	NextMpxNum
	cmp	cx,"mK"
	jne	NextMpxNum
	cmp	dx,"ey"
	je	FoundResident		; exit if RemKey already present
NextMpxNum:
	inc	ResidentMpxNum
	jnz	FindMpxLoop		; keep going until ID wraps to zero
	stc
	jmp	short ExitFindMpx

FreeMpxNum:
	mov	al,ResidentMpxNum
	mov	OurMpxNum,al
FoundResident:
	clc	

ExitFindMpx:
	pop	es
	pop	ds
	pop	bp
	pop	di
	pop	si
	pop	dx
	pop	cx
	pop	bx
	pop	ax

	ret	

FindMpxNum endp

; Finds the name of the file that this program executed from and copies
; it to "OurName".
GetOurName proc near
	assume ds:nothing,ss:nothing,es:nothing
	push	es
	push	ax
	push	cx
	push	di
	push	si
	mov	ax,cs:[2ch]		; get the segment of our environment
	or	ax,ax			; no environment if zero
	jz	@@Exit
	mov	es,ax
	mov	di,0			; scan env from 0
	mov	cx,6000			; give up after 6000 bytes of env
SkipEnv:
	cmp	word ptr es:[di],0	; end of env is marked by the null
					; at the end of the last string 
					; followed by one more null
	je	FoundEnd
	inc	di
	loop	SkipEnv
	jmp	short @@Exit		; env too large to be real, give up

FoundEnd:
	add	di,2			; move past final pair of nulls
	cmp	word ptr es:[di],10	; is string count absurd (> 10)?
	ja	@@Exit
	add	di,2			; move past string count to first str
	lea	si,OurName		; copy string to OurName
	mov	cx,128			; only room for 127 chars plus null
CopyName:
	mov	al,es:[di]
	mov	cs:[si],al
	or	al,al
	jz	@@Exit			; exit if we copied final null
	inc	di
	inc	si
	loop	CopyName
	mov	OurName,0		; if we run out of room then the
					; name is partial and invalid.
					; Zap it.

@@Exit:
	pop	si
	pop	di
	pop	cx
	pop	ax
	pop	es
	ret
GetOurName endp

; Returns with zero flag set if this is an HP 100LX.
; Sets Is100LX
Chk100LX proc near
	assume ds:nothing,ss:nothing,es:nothing
	ret
Chk100LX endp

; Display a null-terminated string (can contain a "$").
StrOut proc near
	assume ds:nothing,ss:nothing,es:nothing
StrLoop:
	mov	bx,dx
	cmp	byte ptr [bx],0
	je	@@Exit
	mov	ah,40h		; write file
	mov	bx,1		; StdOut
	mov	cx,1
	int	21h
	inc	dx
	jmp	StrLoop

@@Exit:
	ret
StrOut endp

ToUpper proc near
	assume ds:nothing,ss:nothing,es:nothing
	cmp	al,'a'
	jb	NotLowerCase
	cmp	al,'z'
	ja	NotLowerCase
	sub	al,'a'-'A'		; convert to upper case
NotLowerCase:
	ret
ToUpper endp

; Convert '1'..'4' to com1..com4 I/O base
SetCom proc near
	assume ds:nothing,ss:nothing,es:nothing
	push	si
	mov	ComNum,al		; Save the ASCII of selected com port
	sub	al,'1'			; '1'..'4' -> 0..3
	sub	ah,ah			; byte to word
	add	ax,ax			; double to index table of words
	mov	si,ax			; move to an index register
	mov	ax,UartBases[si]	; fetch UART base from table
	mov	UartBase,ax
	pop	si
	ret
SetCom endp

if fileOpt
ScanFileName proc near
	assume ds:nothing,ss:nothing,es:nothing
	mov	SendFileName,bx		; save pointer to start of path
ScanLoop:
	mov	al,[bx]
	cmp	al,' '			; space or slash ends name
	je	ScanExit
	cmp	al,'/'
	je	ScanExit
	inc	bx
	loop	ScanLoop
ScanExit:
	mov	FileNameEnd,bx		; save pointer to end of path
	ret
ScanFileName endp
endif

; Call with es:cx pointing to code previously hooked to interrupt vector,
; al equal to interrupt number.
;
; Returns zero if that code is still hooked, non-zero if not.
CheckVector proc near
	push	bx
	push	dx
	push	es
	mov	dx,es			; save segment of resident code in dx
	mov	ah,35h			; read the interrupt vector to es:bx
	int	21h
	mov	ax,es
	cmp	ax,dx
	jne	VectorMismatch		; exit with not-zero
	cmp	cx,bx			; set zero or not-zero and exit
VectorMismatch:
	pop	es
	pop	dx
	pop	bx
	ret
CheckVector endp

;=======================================================================
Main proc near
	assume ds:_TEXT,ss:_TEXT,es:_TEXT
	call	GetOurName
	mov	ah,9
	lea	dx,Logon
	int	21h

; Test if we are already installed or choose a multiplex ID if we are not.
	call	FindMpxNum
	jnc	FoundID
	lea	dx,MpxErrMess
	jmp	ExitWithMess

FoundID:
	cmp	OurMpxNum,0
	jne	NotInstalled

	lea	dx,ResMess
	mov	ah,9
	int	21h

	mov	ah,ResidentMpxNum
	mov	al,RemKeyMpxFn
	int	MpxInt			; ES gets segment of resident code
					; AX gets version of resident code
	cmp	ax,bcdVersion
	je	AlreadyInstalled
IncompatExit:
	lea	dx,IncompatMess
	jmp	ExitWithMess

NotInstalled:
	mov	ax,cs		; make sure ES still points to our segment
	mov	es,ax
AlreadyInstalled:
	mov	ax,4dd4h		; check machine type
	int	15h
	cmp	bx,"HP"			; should return "H" in bh, "P" in bl
	jnz	Not100LX
	cmp	ch,1			; ch=1 is palmtop family
	jnz	Not100LX
	cmp	cl,2			; cl=1 is 95LX, 2 is 100LX
	je	On100LX
	lea	dx,Not100LXMess
	jmp	ExitWithMess

Not100LX:
	mov	Receive,0		; non-100LX defaults to sending
	mov	EnableDef,0		;   and not enabled
	jmp	short StartParse

On100LX:
	mov	Is100LX,1		; set to true
StartParse:
	mov	dx,100h			; keep 1 (true) in dh, 0 (false) in dl
	mov	bx,80h			; point bx at start of string
	mov	cl,[bx]			; get param string length
	inc	bx			; move past length byte
	sub	ch,ch			; convert to word
	jcxz	SetConfigX
ParseLoop:
	mov	al,[bx]			; get a char
	cmp	al,' '			; ignore blanks
	je	NextChar
	cmp	al,'/'			; start of an option?
	jne	Help
	inc	bx			; move through string
	loop	ParseLetter
	jmp	Help			; can't close with a slash

ParseLetter:
	mov	al,[bx]			; get an option letter (we hope)
	call	ToUpper
	cmp	al,'1'			; comm port number ("1".."4")?
	jb	Not1234
	cmp	al,'4'
	ja	Not1234
	call	SetCom
	jmp	short NextChar
	
Not1234:
if fileOpt
	cmp	al,'F'			; send a file?
	jne	NotSendFile
	inc	bx			; move past option letter "F"
	loop	ScanName
	jmp	Help			; something must follow "F"

ScanName:
	mov	Receive,dl		; /F forces send mode (dl = 0)
	call	ScanFileName
	jcxz	SetConfigX		; exit parse loop if no more chars
	jmp	short NextChar

NotSendFile:
endif
	cmp	al,'S'			; act as sender?
	jne	NotSender
	mov	Receive,dl		; dl = 0
	jmp	short NextChar

NotSender:
	cmp	al,'R'			; act as receiver?
	jne	NotReceiver
	mov	Receive,dh		; dh = 1
	mov	EnableDef,dh		; receiver default is enabled (dh = 1)
	jmp	short NextChar

NotReceiver:
	cmp	al,'E'			; enable?
	jne	NotEnable
	mov	Enabled,dh		; dh = 1
	mov	UseEnDef,dl		; do not default (dl = 0)
	jmp	short NextChar

SetConfigX:
	jmp	short SetConfig

ParseLoopX:
	jmp	ParseLoop

NotEnable:
	cmp	al,'D'			; disable?
	jne	NotDisable
	mov	Enabled,dl		; dl = 0)
	mov	UseEnDef,dl		; do not default (dl = 0)
	jmp	short NextChar

NotDisable:
	cmp	al,'T'			; Sender installs as a TSR ?
	jne	NotSendTSR
	mov	SendTSR,dh		; dh = 1
	mov	Receive,dl		; TSR implies Sender (dl = 0)
	mov	EnableDef,dl		; Send TSR default is disabled (dl = 0)
	jmp	short NextChar

NotSendTSR:
	cmp	al,'Z'			; enable 40*25 zoom?
	jne	NotZoomOpt
	mov	OptZa,82h		; modify linked-lists to include
	mov	OptZb,83h		;   40*25 zoom state
	jmp	short NextChar

NotZoomOpt:
	cmp	al,'U'			; uninstall?
	je	Uninstall
	cmp	al,'C'			; Configure new defaults?
	jne	NotConfig
	mov	DoConfig,dh		; dh = 1
	jmp	short NextChar

NotConfig:
	jmp	Help			; unrecognized chars trigger help

NextChar:
	inc	bx			; move through string
	loop	ParseLoopX
SetConfig:
	cmp	DoConfig,0
	jnz	ConfigDefs
	cmp	UseEnDef,0		; use default?
	jz	NoDefault
	mov	al,EnableDef
	mov	Enabled,al
NoDefault:
	cmp	Enabled,0		; enable now?
	je	NotEnabled
if 1
	call	ForceUART		; force UART to our config
else
	cmp	Is100LX,0
	jz	NoInitialPower
	mov	ax,4900h		; route serial port to wire
	int	15h
	mov	ax,4a01h		; turn serial port on
	int	15h
NoInitialPower:
	call	InitUart		; force UART to our config
endif
NotEnabled:
	mov	ah,9			; display com port
	lea	dx,ComMess
	int	21h

	mov	ax,es			; is another copy already loaded?
	mov	bx,cs
	cmp	ax,bx
	je	JustUs
	cmp	es:version,bcdVersion
	jne	IncompatExit
	mov	ax,UartBase		; copy parameters
	mov	es:UartBase,ax
	mov	al,Enabled
	cmp	al,es:Enabled
	je	DoneEnChg		; nothing to do if they are the same
	mov	es:Enabled,al		; update resident enable flag
	or	al,al			; change to enabled?
	jz	ChgToDisabled
	call	SpeedUp
	jmp	short DoneEnChg

ChgToDisabled:
	call	SlowDown
DoneEnChg:
	lea	dx,UpdateMess
	jmp	ExitWithMess

JustUs:
	mov	ax,3516h  		; Get keyboard interrupt vector
	int	21h
	assume es:nothing

	mov	OldKeyOff,bx
	mov	OldKeySeg,es

	cmp	Receive,0		; send or receive mode?
	jz	Sender

Receiver:
	mov	NewKey,offset NewKeyRecv
	lea	dx,RecvEnd+15		; +15 to round to start of next segment
TsrExit:
	cmp	Is100LX,0
	jz	NoSysMgr
	mov	ax,5101h		; read mailbox word one
	int	15h
	jc	NoSysMgr		; if carry no SysMgr
	cmp	ax,7072h		; mailbox signature of SysMgr
	jne	NoSysMgr
	lea	dx,NoInstMess
	jmp	ExitWithMess

NoSysMgr:
	push	dx			; save end of resident code
	mov	es,cs:[2Ch]		; Grab our environment segment
					; from the PSP
	mov	ah,49h
	int	21h			; Free that darn environment

	lea	dx,Installed		; Print installed message
	mov	ah,9
	int	21h

	cmp	SendTSR,0
	jne	NoHookTick
	mov	ax,3508h  		; Get Tick -- we hook this to poll UART
	int	21h

	mov	OldTickOff,bx
	mov	OldTickSeg,es

	mov	ax,cs
	mov	ds,ax

	lea	dx,NewTick
	mov	ax,2508h
	int	21h
	cmp	Enabled,0		; enabled?
	je	NoSpeedUp
	call	SpeedUp			; reprogram systick timer for new rate
NoSpeedUp:
NoHookTick:

	call	HookKey			; hook into the keyboard BIOS int

	mov	ax,352fh  		; link into the multiplex int chain
	int	21h

	mov	OldMpxOff,bx
	mov	OldMpxSeg,es

	mov	ax,cs
	mov	ds,ax

	lea	dx,NewMpx
	mov	ax,252fh
	int	21h


; Calculate ending segment of TSR
	pop	dx			; recover end of resident code
	shr	dx,1			; convert offset to segment,
	shr	dx,1			;  (divide by 16)
	shr	dx,1
	shr	dx,1
	mov	ah,31h		; Terminate and stay resident
	int	21h

Help:
	lea	dx,HelpMess
ExitWithMess:
	mov	ah,9
	int	21h
	jmp	Exit

Sender:
if fileOpt
	mov	dx,SendFileName	; anything to send?
	or	dx,dx
	jnz	SendFile
endif
	lea	dx,SendHelp
	call	StrOut
	cmp	SendTSR,0
	je	SendAsProg
	lea	dx,SendEnd+15	; +15 to round to start of next segment
	jmp	TsrExit

SendAsProg:
	call	SendKeys
Exit:
	mov	ax,4c00h
	int	21h

if fileOpt
SendFile:
	mov	bx,FileNameEnd		; get pointer to end of path
	mov	byte ptr [bx],0		; terminate path with a null
	mov	ax,3d00h		; open code file for read
	int	21h
	jc	ReadError
	mov	bx,ax			; save handle in bx
SendFileLoop:
	mov	ah,3fh			; read the file in
	lea	dx,FileBuf
	mov	cx,1			; ask for more one byte
	int	21h
	jc	ReadError
	cmp	ax,1			; did we get our one byte?
	jne	ReadDone		; jump if EOF
	mov	al,FileBuf		; get the char
	cmp	al,0ah			; linefeed?
	je	SendFileLoop
	cmp	al,1ah			; EOF?
	je	ReadDone
	mov	ah,al			; save ASCII portion in ah
	push	bx			; save handle
	lea	bx,AscToScan		; recreate scan code for ASCII value
	xlat	AscToScan
	pop	bx
	xchg	al,ah			; scancode in ah, ASCII in al
	call	SendWord
	mov	ah,0bh			; check for key press
	int	21h
	cmp	al,0ffh
	je	AbortSend
; Just a kludge for our test, this produced about 11 cps on a 50MHz 486DX
	mov	cx,0
	mov	ax,4
SendDelay:
	jmp	$+2
	jmp	$+2
	loop	SendDelay
	dec	ax
	jnz	SendDelay
	jmp	SendFileLoop

AbortSend:
	mov	ah,8			; eat the abort key
	int	21h
	or	al,al			; extended char
	jnz	NotExtended
	mov	ah,8			; grab the second byte
	int	21h
NotExtended:
	lea	dx,SendAbortMess
	jmp	short ReadMess

ReadError:
	lea	dx,ReadErrMess
ReadMess:
	mov	ah,9
	int	21h
ReadDone:
	mov	ax,3e00h		; close the file
	int	21h
	jmp	short Exit
endif

Uninstall:
; Remember: ES is pointing to our old code seg
	mov	ax,cs
	mov	dx,es
	cmp	ax,dx
	jne	Uninst
	lea	dx,NotInstMess
	mov	ah,9
	int	21h
if fileOpt
	jmp	Exit
else
	jmp	short Exit
endif

Uninst:
; Do all the redirected interrupt vectors still point to our code?

	mov	al,16h
	mov	cx,es:NewKey		; fetch which NewKeyXXX
					; the resident code is using
	call	CheckVector
	jnz	CanNotUninst

	mov	al,2fh
	lea	cx,NewMpx
	call	CheckVector
	jnz	CanNotUninst

	cmp	es:SendTSR,0
	jne	DoNotCheckTick

	mov	al,8
	lea	cx,NewTick
	call	CheckVector
	jnz	CanNotUninst

DoNotCheckTick:

	push	ds			; save our ds

	mov	dx,es:OldKeyOff		; First restore the old key vector
	mov	ds,es:OldKeySeg		; by grabbing it out of resident CS
	mov	ax,2516h
	int	21h

	mov	dx,es:OldMpxOff		; Restore the old multiplex vector
	mov	ds,es:OldMpxSeg		; by grabbing it out of resident CS
	mov	ax,252fh
	int	21h

	pop	ds

	cmp	es:SendTSR,0
	jne	NoFreeTick

	push	ds			; save our ds

	mov	dx,es:OldTickOff	; restore the old Tick vector
	mov	ds,es:OldTickSeg	; by grabbing it out of resident CS
	mov	ax,2508h
	int	21h

	pop	ds

; restore normal systick rate, div = 65536 (0)
	call	SlowDown		

NoFreeTick:
; es is our old code segment -- that's the segment we free
	mov	ah,49h
	int	21h

	lea	dx,UninstMess
	jmp	ExitWithMess

CanNotUninst:
	lea	dx,NoUninstMess
	jmp	ExitWithMess

ConfigDefs:
	mov	ah,9			; display com port
	lea	dx,ComMess
	int	21h

	lea	dx,ConfigMess
	call	StrOut
	lea	dx,CrLf
	mov	ah,9
	int	21h

	mov	ax,3d02h		; open code file for R/W
	lea	dx,OurName
	int	21h
	jc	WriteError
	mov	bx,ax			; save handle in bx
	mov	ax,3f00h		; read the file in
	lea	dx,FileBuf
	mov	cx,8000h		; ask for more than there is
	int	21h
	jc	WriteError
	cmp	ax,CodeLength
	jne	WriteError
	mov	si,FileBuf-Begin
	cmp	version[si],bcdVersion
	jne	IncompatExit
	mov	al,ComNum		; Save the ASCII of selected com port
	mov	ComNum[si],al
	mov	HelpCommDef[si],al
	mov	ax,UartBase		; Save the actual base address
	mov	UartBase[si],ax
	mov	ax,4200h		; seek relative to start
	sub	cx,cx			; offset is zero
	mov	dx,cx
	int	21h
	jc	WriteError
	mov	ax,4000h		; write the code back
	lea	dx,FileBuf
	mov	cx,CodeLength
	int	21h
	jc	WriteError
	mov	ax,3e00h		; close the file
	int	21h
	jmp	Exit

WriteError:
	lea	dx,WrtErrMess
	jmp	ExitWithMess

Main endp

CodeLength equ $-100h

FileBuf label byte

    end Begin
