;
;Routines to set up a program as TSR, by hooking the keyboard interrupt
;locally and testing the scancodes against given scancode hot key.
;This module was tested in TINY model only.
;This module is based on "MSDOS encyclopedia" snap.asm example pp 360-380.
;You may want to refer to that reference for more information.
;Unlike the original article we assume here DOS 3.1 or above.
;
; Defined functions (see also TSR.H):
;
; 1. int HookKbdInterrupt(void (*Func)(), unsigned int ScanCode);
;    Hook the keyboard interrupts so that Func is called if ScanCode is
;    detected. Returns:
;    0 - everything O.K..
;    1 - already installed.
;    2 - can not install (int 02fh ID is in use).
;
; Compile as "tasm tsr.asm/mx"
;
;			Written by Gershon Elber, Sep. 1990
;
	dosseg

	public	_HookKbdInterrupt
	public  _InstalledTSRSegment

	.model	tiny			;select tiny model

	.code				;TC-compatible code segment

MultiplexID	equ	'G'		;Hopefully unique int 2FH ID value.
TSRStackSize	equ	0100h		;Resident stack size in bytes.

KB_FLAG		equ	017h

KBIns		equ	080h
KBCaps		equ	040h
KBNum		equ	020h
KBScroll	equ	010h
KBAlt		equ	008h
KBCtl		equ	004h
KBLeft		equ	002h
KBRight		equ	001h

TRUE		equ	-1
FALSE		equ	0

;****************************************************************************
;Hook to activate the TSR application routine.				    *
;****************************************************************************
DoTSRApplication proc	near
;
	push	ds
	push	ax
;
	push	cs
	pop	ds
	mov	ax,cs
;
	mov	PrevSP,sp		;Save previous SS:SP and set ours.
	mov	PrevSS,ss
	mov	ss,ax
	mov	sp,offset StackTop
;
	push	es
	push	bx
	push	cx
	push	dx
	push	si
	push	di
	push	bp
;
	cld
;
;Set up the interrupt trapping routines.
;
	mov	cx,NTrap
	mov	si,offset StartTrapList
DoTSR1:	lodsb
;
	mov	byte ptr [si],FALSE	;Zero the trap flag.
;
	push	ax
	mov	ah,035h			;Get old interrupt vector.
	int	021h			;/
	mov	[si+1],bx		;And save it.
	mov	[si+3],es		;/
	pop	ax
;
	mov	dx,[si+5]		;ds:dx hold out interrupt handler.
	mov	ah,025h			;Set new interrupt vector.
	int	021h			;/
;
	add	si,007
	loop	DoTSR1
;
	mov	ax,03300h		;Disable break during disk I/O.
	int	021h			; |
	mov	PrevBreak,dl		; |
	xor	dl,dl			; |
	mov	ax,03301h		; |
	int	021h			;/
;
;Preserve previous extrended error information.
;
	push	ds
	xor	bx,bx
	mov	ah,059h			;Get extrended error info
	int	021h
	mov	cs:PrevExtErrDs,ds
	pop	ds
	mov	PrevExtErrAx,ax
	mov	PrevExtErrBx,bx
	mov	PrevExtErrCx,cx
	mov	PrevExtErrDx,dx
	mov	PrevExtErrSi,si
	mov	PrevExtErrDi,di
	mov	PrevExtErrEs,es
;
	mov	ah,051h			;Get current PSP
	int	021h			; |
	mov	PrevPSP,bx		;/
;
	mov	bx,TSRPSP		;Set our PSP
	mov	ah,050h			; |
	int	021h			;/
;
	mov	ah,02fh			;Get DTA address
	int	021h			; |
	mov	PrevDTAoffs,bx		; |
	mov	PrevDTAseg,es		;/
;
	push	ds
	mov	ds,TSRPSP
	mov	dx,00080h		;ds:dx is new DTA.
	mov	ah,01ah			;Set DTA address.
	int	021h			;/
	pop	ds
;
	mov	byte ptr cs:ActiveTSR,TRUE
;
	mov	ax,00e07h		;Do some noise
	int	010h			;/
;
	call	word ptr [FuncAddr]
;
	mov	ax,00e07h		;Do some noise
	int	010h			;/
	mov	byte ptr cs:ActiveTSR,FALSE
;
	push	ds
	lds	dx,PrevDTA		;Restore DTA.
	mov	ah,01ah			;Set DTA address.
	int	021h			;/
	pop	ds
;
	mov	bx,PrevPSP		;Restore PSP.
	mov	ah,050h			;Set PSP address.
	int	021h			;/
;
	mov	dx,offset PrevExtErrInfo ;Restore extended error information
	mov	ax,05d0ah		; |
	int	021h			;/
;
	mov	dl,PrevBreak		;Restore previous MSDOS break status.
	mov	ax,03301h		; |
	int	021h			;/
;
	mov	cx,NTrap
	mov	si,offset StartTrapList
	push	ds
;
DoTSR5: lods	byte ptr cs:[si]	;Restore previous traps.
	lds	dx,cs:[si+1]
	mov	ah,025h
	int	021h
	add	si,00007h
	loop	DoTSR5
;
	pop	ds
;
	pop	bp
	pop	di
	pop	si
	pop	dx
	pop	cx
	pop	bx
	pop	es
;
	mov	ss,PrevSS
	mov	sp,PrevSP
;
	pop	ax
	pop	ds
;
	mov	byte ptr cs:HotFlag,FALSE
;
	ret
;
DoTSRApplication	endp

;
;****************************************************************************
;This Function verifies we are not in DOS.				    *
;****************************************************************************
VerifyDOSState	proc	near
;
	push	ds
	push	bx
	push	ax
;
	lds	bx,cs:ErrorModeAddr
	mov	ah,[bx]
;
	lds	bx,cs:InDosAddr
	mov	al,[bx]
;
	xor	bx,bx
	cmp	bl,cs:InISR28
;
	rcl	bl,01h
;
	cmp	bx,ax
;
	pop	ax
	pop	bx
	pop	ds
;
	ret
;
VerifyDOSState	endp

;****************************************************************************
;This Function verifies we are not in BIOS.				    *
;****************************************************************************
VerifyIntState	proc	near
;
	push	ax
;
	mov	ax,000001011b	;RR = 1, RIS = 1.
	out	020h,al
	jmp	short VerInt1	;Buy some time.
VerInt1:in	al,020h
	cmp	ah,al
	jc	short VerInt2	;Hardware interrupt is been serviced?
;
	xor	al,al
	cmp	al,cs:InISR5
	jc	short VerInt2
;
	cmp	al,cs:InISR9
	jc	short VerInt2
;
	cmp	al,cs:InISR10
	jc	short VerInt2
;
	cmp	al,cs:InISR13
;
VerInt2:pop	ax
;
	ret
;
VerifyIntState	endp

;****************************************************************************
;This Function verifies we are not in DOS/BIOS.				    *
;****************************************************************************
VerifyTSRState	proc	near
;
	ror	cs:HotFlag,1	;Hot key has been pressed?
	cmc
	jc	short VerTSR1
;
	ror	cs:ActiveTSR,1	;Are we in the middle of serving our TSR?
	jc	short VerTSR1
;
	call	VerifyDOSState
	jc	short VerTSR1
;
	call	VerifyIntState
;
VerTSR1:ret
;
VerifyTSRState	endp

;****************************************************************************
;The following routines monitor activity in different interrupt vectors.    *
;****************************************************************************
ISR5	proc	far
;
	inc	cs:InISR5
;
	pushf
	cli
	call	cs:PrevISR5
;
	dec	cs:InISR5
;
	iret
;
ISR5	endp

;****************************************************************************
ISR8	proc	far
;
	pushf
	cli
	call	cs:PrevISR8
;
	cmp	cs:InISR8,0
	jne	short ISR8b		;Exit if already in this handler.
;
	inc	cs:InISR8
;
	sti
	call	VerifyTSRState
	jc	short ISR8a
;
	call	DoTSRApplication
;
ISR8a:	dec	cs:InISR8
;
ISR8b:	iret
;
ISR8	endp

;****************************************************************************
ISR9	proc	far
;
	push	ds
	push	ax
	push	bx
;
	push	cs
	pop	ds
;
	in	al,060h
;
	pushf
	cli
	call	cs:PrevISR9
;
	mov	ah,ds:InISR9
	or	ah,ds:HotFlag
	jnz	short ISR9d
;
	inc	ds:InISR9
	sti
;
	cmp	ds:HotSeqLen,0
	je	short ISR9a
;
	mov	bx,ds:HotIndex
	cmp	al,[bx+HotSequence]
	jne	short ISR9b
;
	inc	bx
	cmp	bx,ds:HotSeqLen
	jb	short ISR9c
;
ISR9a:	push	ds
	mov	ax,0040h
	mov	ds,ax
	mov	al,ds:[KB_FLAG]
	pop	ds
;
	and	al,ds:HotKbMask
	cmp	al,ds:HotKBFlag
	jne	short ISR9c
;
	mov	byte ptr ds:HotFlag,TRUE
;
ISR9b:	xor	bx,bx
;
ISR9c:	mov	ds:HotIndex,bx
	dec	ds:InISR9
;
ISR9d:	pop	bx
	pop	ax
	pop	ds
;
	iret
;
ISR9	endp

;****************************************************************************
ISR10	proc	far
;
	inc	cs:InISR10
;
	pushf
	cli
	call	cs:PrevISR10
;
	dec	cs:InISR10
;
	iret
;
ISR10	endp

;****************************************************************************
ISR13	proc	far
;
	inc	cs:InISR13
;
	pushf
	cli
	call	cs:PrevISR13
;
	pushf
	dec	cs:InISR13
	popf
;
	sti
	ret	2
;
ISR13	endp

;****************************************************************************
ISR1B	proc	far
;
	mov	byte ptr cs:Trap1B,TRUE
;
	iret
;
ISR1B	endp

;****************************************************************************
ISR23	proc	far
;
	mov	byte ptr cs:Trap23,TRUE
;
	iret
;
ISR23	endp

;****************************************************************************
ISR24	proc	far
;
	mov	byte ptr cs:Trap24,TRUE
;
	mov	al,3			;fail the MSDOS call.
;
	iret
;
ISR24	endp

;****************************************************************************
ISR28	proc	far
;
	pushf
	cli
	call	cs:PrevISR28
;
	cmp	cs:InISR28,0
	jne	short ISR28b
;
	inc	cs:InISR28
;
	call	VerifyTSRState
	jc	short ISR28a
;
	call	DoTSRApplication
;
ISR28a:	dec	cs:InISR28
;
ISR28b:	iret
;
ISR28	endp

;****************************************************************************
ISR2F	proc	far
;
	cmp	ah,MultiplexID
	je	short ISR2Fa
;
	jmp	cs:PrevISR2F
;
ISR2Fa:	test	al,al
	jnz	short ISR2Fb
;
	mov	al,0ffh
	mov	bx,cs			;Return the installed TSR segment.
;
ISR2Fb:	iret
;
ISR2F	endp

;****************************************************************************
; Data section:								    *
;****************************************************************************
	.data?				;TC-comp. uninitialized data segment.

_InstalledTSRSegment	dw	?	;If already installed - segment addr.

ErrorModeAddr	dd	?		;MSDOS ErrorMode flag address.
InDosAddr	dd	?		;MSDOS InDOS flag address.
;
NISR		dw	(EndISRList-StartISRList) / 8	;# of installed ISRs.
;
StartISRList	db	005h		;Int number.
InISR5		db	FALSE
PrevISR5	dd	?
		dw	offset ISR5
;
		db	008h		;Int number.
InISR8		db	FALSE
PrevISR8	dd	?
		dw	offset ISR8
;
		db	009h		;Int number.
InISR9		db	FALSE
PrevISR9	dd	?
		dw	offset ISR9
;
		db	010h		;Int number.
InISR10		db	FALSE
PrevISR10	dd	?
		dw	offset ISR10
;
		db	013h		;Int number.
InISR13		db	FALSE
PrevISR13	dd	?
		dw	offset ISR13
;
		db	028h		;Int number.
InISR28		db	FALSE
PrevISR28	dd	?
		dw	offset ISR28
;
		db	02fh		;Int number.
InISR2f		db	FALSE
PrevISR2f	dd	?
		dw	offset ISR2f
;
EndISRList	label	byte
;
TSRPSP		dw	?		;Resident PSP
PrevPSP		dw	?		;Previous PSP
PrevSP		dw	?		;Previous SS:SP
PrevSS		dw	?
;
PrevBreak	db	?
;
PrevDTA		label	dword
PrevDTAoffs	dw	?
PrevDTAseg	dw	?
;
HotIndex	dw	0		;Index of next scancode in seq.
HotSeqLen	dw	EndHotSeq-HotSequence	;Length of hot keys seq.
;
HotSequence	db	?		;Hot sequence of scan codes.
EndHotSeq	label	byte
;
HotKBFlag	db	?
HotKBMask	db	(KBIns or KBCaps or KBNum or KBScroll) xor 0ffh
HotFlag		db	FALSE
;
ActiveTSR	db	FALSE
;
DOSVersion	label	word
		db	?		;Minor version number.
MajorVersion	db	?		;Major version number.
;
;Data used by the TSR application trapping.
;
PrevExtErrInfo	label	byte
PrevExtErrAx	dw	?
PrevExtErrBx	dw	?
PrevExtErrCx	dw	?
PrevExtErrDx	dw	?
PrevExtErrSi	dw	?
PrevExtErrDi	dw	?
PrevExtErrDs	dw	?
PrevExtErrEs	dw	?
		dw	3 dup(0)
;
NTrap		dw	(EndTrapList-StartTrapList) / 8
;
StartTrapList	db	01bh
Trap1B		db	FALSE
PrevISR1B	dd	?
		dw	offset ISR1B
;
		db	023h
Trap23		db	FALSE
PrevISR23	dd	?
		dw	offset ISR23
;
		db	024h
Trap24		db	FALSE
PrevISR24	dd	?
		dw	offset ISR24
;
EndTrapList	label	byte
;
FuncAddr	dw	?		;Address of TSR application.

;****************************************************************************
; Stack to be used by the resident section (in code segment...):	    *
;****************************************************************************
	.code

	db	TSRStackSize dup("Stack   ")
StackTop	label	word

;****************************************************************************
; Transient code - called only once in initialization process.		    *
;****************************************************************************
	.code

;****************************************************************************
;This Function hooks the keyboard interrupt handler to ours, and save	    *
;the requested scancode and function to call for it.			    *
;Returns 0 in ax if O.k. otherwise error code (see header of this file.     *
;****************************************************************************
_HookKbdInterrupt	proc	near
;
	push	bp
	mov	bp,sp
	push	bx
	push	cx
	push	dx
	push	si
	push	di
	push	es
	push	ds
;
	mov	ax,[bp+6]		;Get scan code to compare with.
	mov	byte ptr HotKBFlag,al	; |
	mov	byte ptr HotSequence,ah	;/
;
	mov	ax,[bp+4]		;Get Func address.
	mov	word ptr FuncAddr,ax	;/
;
	mov	ax,cs
	mov	ds,ax
;
	mov	TSRPSP,cs
;
	mov	ah,030h			;Get dos version.
	int	021h			; |
	xchg	ah,al			; |
	mov	DosVersion,ax		;/
;
	mov	ah,MultiplexID		;Verify its not already installed.
	xor	al,al			; |
	int	02fh			; |
	test	al,al			; |
	jz	short Hook3		; |
	cmp	al,0ffh			; |
	jne	short Hook2		; |
	mov	ax,00001h		; | Already installed.
	mov	_InstalledTSRSegment,bx ; | Save installed TSR segment.
	jmp	short Hook9		; |
Hook2:	mov	ax,00002h		; | Can not install.
	jmp	short Hook9		;/
;
Hook3:	mov	ah,034h			;Get InDOS address.
	int	021h			; |
	mov	word ptr InDOSAddr,bx	; |
	mov	word ptr InDOSAddr+2,es	; |
	dec	bx			; | Error mode is just before it.
	mov	word ptr ErrorModeAddr,bx	; |
	mov	word ptr ErrorModeAddr+2,es	;/
;
	mov	cx,NISR
	mov	si,offset StartISRList
Hook4:	lodsb
;
	push	ax
	mov	ah,035h			;Get old interrupt vector.
	int	021h			; |
	mov	[si+1],bx		; | And save it
	mov	[si+3],es		;/
	pop	ax
;
	push	ds
	mov	dx,[si+5]
	push	cs
	pop	ds
	mov	ah,025h			;Set our interrupt vector
	int	021h			;/
	pop	ds
	add	si,00007h
	loop	Hook4
;
	mov	es,cs:[02ch]		;Free the environment.
	mov	ah,049h			; |
	int	021h			;/
;
	xor	ax,ax
;
Hook9:	pop	ds
	pop	es
	pop	di
	pop	si
	pop	dx
	pop	cx
	pop	bx
	pop	bp
;
	ret
;
_HookKbdInterrupt	endp

	end
