COMMENT %

Here sent is a TSR which does it all... Activated at TIMERBASE, KEYBASE and
MSDOSBASE .. The program entered in the TSR saves all needed, like front
PSP, extended error info, only activates when it's really safe.... Just
delete the parts not needed, and keep track of the programming style..
It's pretty solid....

Remember my name, and give me some credits or a copy when your program
is finished.. I worked hard on this "completely MSDOS-like TSR" ..


              .o
------------ oOO ------------------------------------------------
            oOOo	  jbodde@euronet.nl
       .   oOOOoOO    o   http://www.euronet.nl/~jbodde
      oO. .OOOOOOOooOOO
     oOOo .o  oOOOOOOOo   A! JW2 YK++ WK+++^i DT++" P&B+
     OOOO     oo oOOOo    S&S+ GDF+++PS++ HIP--- GP+++^
    .OOOo   .oo   OOo     HN+++! MM++ MS-- CO+++ P+ $+
   oOOOOOOo.OO    o       E15a Ee45a Eee61a Ay72 M
  oOOOOOOOOOOO

COMMENT ENDS %

.286
.MODEL LARGE
LOCALS
JUMPS

;==============================================================================
;
; TOTAL.ASM
;	Total fixed TSR program (28/02/96)
;	Version 1.0
;
; Made by: Jorgen Bodde (c) '96
;
; *** Revision history ***
;
; V1.0 : 280296\JB
;	Created
;
; FUTURE: checkINTS also needed to check for ISR08 and DOS..
;	  change callAPP to callREAL.. Make IFNDEF's for
;	  timer and hotkey preservations.
;
;==============================================================================


; *******************************************************************
; *************************** EQUATES *******************************
; *******************************************************************

		; console constants

CR		equ	0dh
LF		equ	0ah
TAB		equ     09h
BLANK		equ     20h
BELL		equ	07h
EOS		equ	'$'

		; condition constants

FALSE		equ	0
TRUE		equ	-1

		; assembly equates

B		equ	byte ptr
W		equ	word ptr
D		equ	dword ptr
N		equ	near ptr
F		equ	far ptr
S		equ	seg
O		equ	offset

HIGHsign	equ	8740h			; these strings are used
LOWsign		equ	03e5h			; for tsr identification
PRG_name	EQU	'TOTAL'			; leave them..
PRG_ver		EQU	'1.1'
PRG_date	EQU	'28/02/96'

argMIN		equ	1d
STACKsize	equ	100h

TSR_INSTALL	equ	00h
TSR_UNHOOK	equ	01h
TSR_FREEZE	equ	02h
TSR_ACTIVATE	equ	03h
TSR_RUNAPP	equ	04h

KBins		equ	80h
KBcaps		equ	40h
KBnum		equ	20h
KBscroll	equ	10h
KBalt		equ	8h
KBctrl		equ	4h
KBshleft	equ	2h
KBshright	equ	1h

KBmask		equ	KBshright + KBshleft	; Mask for key to activate
timerSTART	equ	125H			; Timer countdown value

ARGCASEINS	equ	TRUE		; remove when CASESENSITIVE arguments
TIMERBASE	equ	TRUE		; remove when no TIMER activations
HOTKEYBASE	equ	TRUE		; remove when no HOTKEY activations
;MSDOSBASE	equ	TRUE		; remove when no DOS activations

; *******************************************************************
; *************************** MACRO'S *******************************
; *******************************************************************

DOSB		macro	DOSByte
		mov	ah,DOSByte
		int	21h
		endm

DOSW		macro	DOSWord
		mov	ax,DOSWord
		int	21h
		endm

ADDINT		macro	name, INTnr
		name&amount = name&amount + 1
      		db	INTnr&h
inISR&INTnr	db	FALSE
prevISR&INTnr	dd	00000000h
		dw	O mytsr:my&name&INTnr
		endm

; *******************************************************************
; ************************** TSR DATA *******************************
; *******************************************************************

mytsr		SEGMENT	para public
		ASSUME cs:mytsr, ds:mytsr, es:mytsr

ISRamount = 0				; is increased every new ISR
TRAPamount = 0				; is increased every new TRAP

ISRstart	LABEL	BYTE
		ADDINT	ISR,08		; timer int
		ADDINT	ISR,09		; keyboard int
		ADDINT	ISR,10		; video int
		ADDINT	ISR,13		; diskdrive int
		IFDEF	MSDOSBASE
		ADDINT	ISR,21		; msdos function INT
		ENDIF
		ADDINT	ISR,28		; dos IDLE int

TRAPstart	LABEL	BYTE
		ADDINT	TRAP,1b		; rom BIOS cntrl-break interrupt
		ADDINT	TRAP,23		; ctrl-break interrupt
		ADDINT	TRAP,24		; critical ERROR handler

ERRMODEaddr	dd	?		; Errormode adres
INDOSaddr	dd	?		; InDos flag
IObreak		db	?		; IObreak flag
DTAaddr		dd	?		; Original DTA address
EEinfo		label	byte		; Extended error info
EEax		dw	?		; ax : Extended Error Code
EEbx		dw	?		; bx : Error Class
EEcx		dw	?		; cl : Suggested Action, ch : Error Src
EEdx		dw	?		; dx : undefined
EEsi		dw	?		; si : undefined
EEdi		dw	?		; di : undefined
EEds		dw	?		; ds : undefined
EEes		dw	?		; es : undefined
		dw	3 dup (0)

PSPtsr		dw	?		; PSP tsr segment
PSPfront	dw	?		; PSP front app segment
frontSS		dw	?		; STACK-SEG front app
frontSP		dw	?		; STACK-OFF front app
tsrSS		dw	S mytsr		; New STACK-SEG and STACK-OFF
tsrSP		dw	O mytsr:TSRstack + (STACKsize - 1)

; Multiplex jumps (INT2F)
MUXJumps	LABEL	BYTE
		dw	O MUXinstall	; Check TSR installation (00)
		dw	O MUXunhook	; Request TSR unhook (01)
		dw	O MUXfreeze	; Request freeze TSR (02)
		dw	O MUXactive	; Request activate TSR (03)
		dw	O MUXrunapp	; Force running TSR (04)
MUXAmount	EQU	($ - MUXJumps) / 2

hotAPP		db	FALSE		; Toggle want to start APP
inAPP		db	FALSE		; Toggle in APP
residentAPP	db	FALSE		; Toggle resident
freezeTSR	db	FALSE		; Toggle APP freeze
APPstatus	db	TRUE		; Status of executed APP


TSRname		db	PRG_name,PRG_ver,PRG_date,0d
TSRstack	db	STACKsize dup ('?')

		IFDEF	HOTKEYBASE
scanKEY		db	?		; last pressed key
		ENDIF
		IFDEF	TIMERBASE
TIMERcount	dw	timerSTART	; countdown timer
		ENDIF
		IFDEF	MSDOSBASE
DOSfunc		db	0h		; last activated DOS function
		ENDIF

; *******************************************************************
; ************************ CUSTOM DATA ******************************
; *******************************************************************

; *******************************************************************
; ************************** TSR CODE *******************************
; *******************************************************************

; safeDOS
;  This procedure checks if DOS is ready to be entered. When the INDOS-flag
;  is unequal to 0, it can't be done. Except when ISR28 is executed, because
;  DOS is then only waiting for a key, and it's safe to enter it.
;
; - returns -
;  CARRY = 0 : everything is ok
;  CARRY = 1 : not safe to enter DOS
;
safeDOS		proc	near

		push	ds
		push	bx
		push	ax

		lds	bx,cs:ERRMODEaddr	; check errormode
		mov	ah,ds:[bx]
		lds	bx,cs:INDOSaddr		; check indos
		mov	al,ds:[bx]
		xor	bx,bx
		cmp	bl,cs:inISR28
		rcl	bl,01h			; bl = 1 when in INT28
		cmp	bx,ax			; when carry=0, all ok

		pop	ax
		pop	bx
		pop	ds
		ret

safeDOS		endp

;------------------------------------------------------------------------------

; checkINTS
;  Checks if one of the hooked interrupts isn't already accessed. If one of
;  them is accessed, it's not safe to enter the application if it uses one
;  or more of the interrupts.
;
; - returns -
;  CARRY = 0 : When it's ok to enter the app
;  CARRY = 1 : When it's not safe to do so
;
checkINTS	proc	near

		push	ax

		mov	ax,00001011b		; check 8259A pic for any int
		out	20h,al			; request
		jmp	short @@waitcycle
@@waitcycle:	in	al,20h			; get int-mask

		cmp	ah,al			; when some set, not safe
		jc	@@intactive

		xor	al,al			; already in kbint ?
		cmp	al,cs:inISR09
		jc 	@@intactive

		cmp	al,cs:inISR10		; already in videoint ?
		jc	@@intactive

		cmp	al,cs:inISR13		; already in diskdrive-int ?

@@intactive:	pop	ax			; when CARRY=1, not safe
		ret

checkINTS	endp

;------------------------------------------------------------------------------

; TSRcheck
;  Checks if it is safe to run the application. Also when the application is
;  already executed, it's reported.
;
; - returns -
;  CARRY = 0 : It's safe to run the application
;  CARRY = 1 : It's not safe to run the application
;
TSRcheck	proc	near

		rol	B cs:freezeTSR,1h	; when app is freezed, no exec
		jc	@@noexec

		ror	cs:inAPP,1h		; carry=1 when appl
		jc	@@noexec		; already active

		IFDEF	TIMERBASE
		cmp	w cs:TIMERcount,0h	; when timer is zero
		jg	@@check_hot		; I want to activate
		mov	cs:hotAPP,TRUE		; application
		ENDIF

@@check_hot:	rol	B cs:hotAPP,1h		; when carry = 1 then
		cmc				; not want app to start
		jc	@@noexec

		call	safeDOS			; carry = 1 when dos
		jc	@@noexec		; busy

		call	checkINTS 		; carry = 1, ints active
@@noexec:	ret

TSRcheck	endp

;------------------------------------------------------------------------------

; TSRunhook
;  This procedure unhooks the TSR and frees the memory used.
;
; WARNING: Only safe when TSRcheck returns a CARRY = 0.
;
; - returns -
;  Nothing
;
TSRunhook	proc	near

		mov	cx,ISRamount		; number of ISR to unhook
		mov	si,O mytsr:ISRstart

		call	TSRfreeints		; unhook the vectors

		mov	W es,cs:PSPtsr		; es = tsrpsp
		DOSB	049h			; free pspseg

		mov	W es,cs:PSPtsr		; ax = tsrpsp
		mov	es,es:[02ch]		; get program segment
		DOSB	049h			; release program

		ret

TSRunhook	endp

;------------------------------------------------------------------------------

; myISR08
;  This procedure checks if the application must be executed, and if it is
;  safe to execute. When it does, the application is executed.
;
; - returns -
;  Nothing
;
myISR08		proc	far

		pushf
		cli
		call	cs:prevISR08		; call old

		cmp	B cs:inISR08,0h		; already in 08 ?? quit!
		jne	@@quit08

		inc	B cs:inISR08		; now were in

		IFDEF	TIMERBASE
		sub	w cs:TIMERcount,1h	; decrease timer
		jnc	@@no_reset
		mov	w cs:TIMERcount,timerStart
		ENDIF

@@no_reset:	sti
		call	TSRcheck		; when not safe, no
		jc	@@leave08		; executing possible

@@startapp:	call	callAPP			; the twilight zone !

@@leave08:	dec	B cs:inISR08

@@quit08:	iret

myISR08		endp

;------------------------------------------------------------------------------

; myISR09
;  This procedure checks if the right key-sequence is pressed, and sets a
;  flag so that the TSR is executed when it's safe in myISR08 or myISR28.
;
; - returns -
;  Nothing
;
myISR09		proc	far

		push	ds
		push	ax
		push	bx

		push	cs
		pop	ds

		IFDEF	HOTKEYBASE
		in	al,60h			; get current scan-code
		mov	cs:scanKEY,al
		ENDIF

		pushf
		cli
		call	cs:prevISR09		; execute old kb-int

		cmp	B cs:inISR09,0h		; if already in kb-int
		jne	@@quit09

		ror	B cs:hotAPP,1h		; or already succeeded executing
		jc	@@quit09

		rol	B cs:freezeTSR,1h	; or freezed application
		jc	@@quit09		; stop further executing

		inc	B cs:inISR09		; now were in INT09
		sti

		IFDEF	HOTKEYBASE
		push	ds
		mov	ax,40h			; 040:017 is KB-FLAG pos
		mov	ds,ax
		mov	al,ds:[017h]		; get shift-scancode
		pop	ds
		and	al,KBmask
		cmp	al,KBmask		; check if desired bits are ok
		jne	@@leave09
		mov	B cs:hotAPP,TRUE	; i want to get in APP
		ENDIF

@@leave09:	dec	B cs:inISR09		; no more in ISR09

@@quit09:	pop	bx
		pop	ax
		pop	ds
		iret

myISR09		endp

;------------------------------------------------------------------------------

; myISR10
;  Sets a flag when ISR10 is accessed. So TSRcheck knows when it's not safe to
;  enter the application who uses this ISR.
;
myISR10		proc	far			; new video int

		inc	B cs:inISR10		; were now in INT10

		pushf
		cli
		call	cs:prevISR10

		dec	B cs:inISR10
		iret

myISR10		endp

;------------------------------------------------------------------------------

; myISR13
;  Sets a flag when ISR13 is accessed. So TSRcheck knows when it's not safe to
;  enter the application who uses this ISR.
;
; - returns -
;  Nothing
;
myISR13		proc	far			; new diskdrive int

		inc	B cs:inISR13		; were now in INT13

		pushf
		cli
		call	cs:prevISR13

		pushf
		dec	B cs:inISR13
		popf

		sti
		ret	2

myISR13		endp

;------------------------------------------------------------------------------

; myTRAP1b
;  When an error occured, the original flags from the original INT aren't
;  disturbed by the application.
;
; - returns -
;  Nothing
;
myTRAP1b	proc	far			; rom BIOS cntrl-break interrupt

		mov	B cs:inISR1b,TRUE
		iret

myTRAP1b	endp

;------------------------------------------------------------------------------

; myTRAP23
;  When an error occured, the original flags from the original INT aren't
;  disturbed by the application.
;
; - returns -
;  Nothing
;
myTRAP23	proc	far			; cntrl-BREAK interrupt

		mov	B cs:inISR23,TRUE
		iret

myTRAP23	endp

;------------------------------------------------------------------------------

; myTRAP24
;  When an error occured, the original flags from the original INT aren't
;  disturbed by the application.
;
; - returns -
;  Nothing
;
myTRAP24	proc	far			; critical Error interrupt

		mov	B cs:inISR24,TRUE

		mov	al,3h			; fail MSDOS-call
		iret

myTRAP24	endp

;------------------------------------------------------------------------------

; myISR28
;  This procedure is called when DOS waits for a key or something. It's pretty
;  safe to enter the application from this point. So it's done when the rest
;  of DOS isn't accessed.
;
;  To access this TSR:
;	dx:bx - Unique value only known by front TSR installer
;	ds:si - Pointing to TSR name
;	cx    - Sum of dx and bx
;	ax    - Function number
;
; - returns -
;  Nothing
;
myISR28		proc	far

		cmp	bx,LOWsign		; check if LOW and HIGH
		jne	@@failed_pass1		; sign are OK
		cmp	dx,HIGHsign
		jne	@@failed_pass1

		push	ax			; and if CX is sum of
		mov	ax,bx			; both signs ...
		add	ax,dx
		cmp	cx,ax
		jne	@@failed_pass2

		push	si			; and ds:si contains
		push	di			; identical name ...
		push	es
		mov	di,O mytsr:TSRname
		mov	ax,S mytsr
		mov	es,ax

		cld
@@pass3_loop:	lodsb				; compare string and
		cmp	al,b es:[di]		; when OK, we're in !!
		jne	@@failed_pass3
		inc	di
		or	al,al
		jnz	@@pass3_loop

		pop	es
		pop	di
		pop	si
		pop	ax

		call	FRONTinterface		; talk to FRONT app
		jmp	@@quit28		; and remember altered regs

@@failed_pass3:	pop	es
		pop	di
		pop	si
@@failed_pass2:	pop	ax

@@failed_pass1:	pushf
		cli
		call	cs:prevISR28		; do previous IDLE int

		cmp	B cs:inISR28,0h
		jne	@@quit28

		inc	B cs:inISR28

		call	TSRcheck		; check it's safe to exec
		jc	@@leave28

		call	callAPP			; do APP

@@leave28:	dec	B cs:inISR28
@@quit28:	iret

myISR28		endp

;------------------------------------------------------------------------------
; FRONTinterface
;  This routine is used to talk to the TSR using a front application.
;  A complex identification procedure makes sure it's our front APP
;  which wants to communicate with us..
;
; - assumes -
;	AX - Function number
;
; - returns -
;	AX    - Contains the value TRUE
;	BX    - Returned as internal status (TRUE = OK, FALSE = ERROR)
;	CL    - Returned as status
;			TSR_UNHOOK gives FALSE when can't be unhooked
;			TSR_RUNAPP gives FALSE when error inside APP
;	DS:SI - Contains the address of the TSR real app
;	DS:DX - Contains the data area of the TSR
;
; Functions are:
;	TSR_INSTALL	Request installation check (dummy)
;	TSR_UNHOOK	Request for unhook
;	TSR_FREEZE	Request for freezing application
;	TSR_ACTIVATE	Request activating again
;	TSR_RUNAPP	Force running application
;
FRONTinterface	proc	near

		cmp	ax,0h			; when wrong number
		jl	@@front_err		; exit
		cmp	ax,MUXamount
		jl	@@front_jump

@@front_err:	mov	bx,FALSE		; give error
		mov	ax,TRUE			; and exit
@@prepare_ptrs:	push	cs
		pop	ds
		mov	si,O mytsr:realAPP
		mov	dx,O mytsr:ISRstart
		ret

@@front_jump:	mov	cx,TRUE			; cx = status ok (assume)
		mov	bx,O mytsr:MUXjumps	; jump to label
		shl	ax,1d
		add	bx,ax
		jmp	w cs:[bx]

MUXinstall:	jmp	@@front_ok		; just send some info

MUXunhook:	call	originalINT		; check if can be unhooked
		jnc	@@unhook_tsr

		mov	cl,FALSE  	     	; status: can't unhook!
		jmp	@@front_ok

@@unhook_tsr:	mov	b cs:freezeTSR,TRUE	; freeze and
		call	TSRunhook		; unhook TSR !
		mov	cl,TRUE
		jmp	@@front_ok

MUXfreeze: 	mov	b cs:freezeTSR,TRUE	; try to freeze TSR
		jmp	@@front_ok

MUXactive: 	mov	b cs:freezeTSR,FALSE	; try to activate TSR
		jmp	@@front_ok

MUXrunapp:	call	callAPP			; run application
		mov	cl,b cs:APPstatus	; and return status

@@front_ok:	mov	ax,TRUE			; all went ok
		mov	bx,TRUE
		jmp	@@prepare_ptrs

FRONTinterface	endp

;------------------------------------------------------------------------------

; originalINT
;  This procedure checks if the TSR-vectors in the table match the vectors
;  in the vector-table. If not, it's not safe to unhook, because one or
;  more TSR's might loose their hooked vectors if the original vectors out
;  of the TSR's table are reset.
;
; - returns -
;  CARRY = 0 : If it's safe to do
;  CARRY = 1 : If it's not safe
;
originalINT	proc	near

		push	cx
		push	si
		push	es

		cld
		mov	cx,ISRamount		; number of ISR to inst
		mov	si,O mytsr:ISRstart
		push	cs			; ds = MYTSR
		pop	ds

@@check_next_isr:
		lods	B cs:[si]		; get int-nr

		DOSB	35h			; get int-nr vector

		cmp	cs:[si+5],bx		; compare offset with old one
		jne	@@nope_unhook
		push	cs
		pop	bx
		push	es
		pop	ax
		xor	ax,bx			; compare segment with old one
		jnz	@@nope_unhook

		add	si,7h			; next isr-list
		loop	@@check_next_isr
		clc

@@unhook_leave:	pop	es
		pop	si
		pop	cx
		ret

@@nope_unhook:	stc
		jc	@@unhook_leave

originalINT	endp

;------------------------------------------------------------------------------

; myISR21
;
IFDEF		MSDOSBASE
myISR21		proc	far

		mov	b cs:DOSfunc,ah		; remember DOS function

		pushf				; push flags already !
		cli				; call the old DOS
		call	cs:prevISR21

		pushf
		cmp	b cs:DOSfunc,3dh
		jne	@@normalend

		mov	b cs:hotAPP,TRUE	; always activate to check

		sti
		call	TSRcheck		; when not safe, no
		jc	@@normalend		; executing possible
		call	callAPP			; the twilight zone !

@@normalend:	popf
		sti
		ret	2

myISR21		endp
ENDIF

;------------------------------------------------------------------------------
; callAPP
;  This procedure is called when the application must be called.
;  All necressary things are done to preserve everything needed,
;  and trap the ISR's which could be irritating when accessed.
;  When all preserved, realAPP is called, and after that all is
;  restored in it's original state.
;
; - returns -
;  cs:APPstatus : TRUE when all went OK
;                 FALSE when something went wrong
;
callAPP		proc	near

		rol	b cs:residentAPP,1h	; is the app already resident?
		jnc	@@start			; if not, skip inAPP check

		cmp	b cs:inAPP,TRUE		; already in APP ?
		jne	@@start			; when so, stop further exec
		ret

@@start:	mov	b cs:inAPP,TRUE

		mov	cs:frontSP,sp		; remember old stack
		mov	cs:frontSS,ss

		mov	ss,cs:tsrSS		; set new ss:sp value
		mov	sp,cs:tsrSP

		push	ds
		push	es
		pusha				; 286 and higher.. I am sorry!

		push	cs			; ds = mytsr
		pop	ds

		cld				; clear direction-flag
		mov	cx,TRAPamount		; get number of trap-ISR's
		mov	si,O mytsr:TRAPstart

@@set_traps:	lods	B cs:[si]		; AL = int-number

		mov	B cs:[si],FALSE		; reset the trap-flag

		push	ax
		DOSB	35h			; get interrupt-vector

		mov	cs:[si+1],bx		; store old handler in list
		mov	cs:[si+3],es

		pop	ax			; restore int-number
		mov	dx,cs:[si+5]		; offset of this TSR's trap
		DOSB	25h			; set interrupt-vector

		add	si,7h			; do next trap

		loop	@@set_traps

		DOSW	03300h			; get previous extd-break state
		mov	B cs:IObreak,dl

		xor	dl,dl
		inc	al			; set break state (assume ah=33)
		DOSB	033h

		xor	bx,bx
		DOSB	059h			; get extended error-info

		mov	cs:EEds,ds		; store all extended error regs
		mov	cs:EEax,ax
		mov	cs:EEbx,bx
		mov	cs:EEcx,cx
		mov	cs:EEdx,dx
		mov	cs:EEsi,si
		mov	cs:EEdi,di
		mov	cs:EEes,es

		DOSB	51h			; get current PSP-seg
		mov	cs:PSPfront,bx		; remember it

		mov	bx,cs:PSPtsr		; set my TSR's psp
		DOSB	50h

		DOSB	02fh			; get DTA-address -> ES:BX
		mov	si,O mytsr:DTAaddr
		mov	cs:[si],bx
		mov	cs:[si+2],es		; store it

		mov	ax,cs:PSPtsr		; at PSP:080h
		mov	ds,ax
		mov	dx,80h
		DOSB	01ah

		push	cs			; seg = mytsr
		pop	ds
		call	realAPP			; start real application

		lds	dx,cs:DTAaddr		; restore previous DTA
		DOSB	01ah			; DS:DX previous DTA

		mov	bx,cs:PSPfront		; restore PSPseg
		DOSB	050h

		push	cs
		pop	ds
		mov	dx,O mytsr:EEinfo	; restore extended error info
		DOSW	05d0ah

		mov	dl,cs:IObreak		; restore IO-break
		DOSW	3301h			; set it

		mov	cx,TRAPamount		; reset TRAP-vectors
		mov	si,O mytsr:TRAPstart

		call	TSRfreeints

@@leaveAPP:	popa				; restore all registers
		pop	es
		pop	ds

		mov	ss,cs:frontSS		; restore original stack
		mov	sp,cs:frontSP

		IFDEF	TIMERBASE
		mov	W cs:TIMERcount,timerSTART
		ENDIF

		mov	B cs:hotAPP,FALSE	; application finished!
		mov	B cs:inAPP,FALSE	; outside APP

		rol	b cs:residentAPP,1h	; is app resident ?
		jc	@@leavecall		; when so, return FAR
		retf

@@leavecall:	ret

callAPP		endp

;------------------------------------------------------------------------------

TSRfreeints	proc	near

		push	dx
		push	bx
		push	ds

		cld				; clear direction-flag
@@unhnextisr:	lods	B cs:[si]		; get int-nr

		mov	dx,cs:[si+1]		; get old offset
		mov	bx,cs:[si+3]		; get old segment
		mov	ds,bx
		DOSB	25h
		add	si,7h			; next interrupt

		loop	@@unhnextisr

		pop	ds
		pop	bx
		pop	dx
		ret

TSRfreeints	endp

;------------------------------------------------------------------------------

; REALapp
;  This is your application. Do with it what you like.
;
;  Set the application-status flag when you exit, to indicate
;  something went wrong, or all went great!
;
; - returns -
;  cs:APPstatus : TRUE when all went OK
;		  FALSE when something went wrong
;
REALapp		proc	near

		mov	ax,0b800h			; display funky
		mov	es,ax				;  screenshit
		mov	ax,es:[0000h]
		xor	ax,0ffffh
		mov	es:[0000h],ax
		mov	es:[0002h],ax

		mov	b cs:APPstatus,TRUE
		ret

REALapp		endp

;------------------------------------------------------------------------------

mytsr		ends

myinstall	SEGMENT para public

		assume	cs:myinstall,ds:myinstall,es:myinstall

; *******************************************************************
; ************************ INSTALL DATA *****************************
; *******************************************************************

startsign	db	cr,lf,'- ',PRG_name,' (c) Jorgen Bodde, V',PRG_ver,' (',PRG_date,') -',cr,lf,cr,lf,eos
err_presign	db	'ERROR: ',bell,eos
err_wrongdosver	db	'Wrong MSDOS version (must be 3.1 or higher) !!',cr,lf,eos
err_installed	db	PRG_name,' already installed !!',cr,lf,eos
err_notinstall	db	PRG_name,' not yet installed !!',cr,lf,eos
err_unhook	db	'One or more installed TSR''s altered the original vectors.',cr,lf
		db	PRG_name,' can''t unhook right now, try later !',cr,lf,eos
err_badarg	db	'Unknown argument (Use option ''-?'' for HELP) !',cr,lf,eos
err_invalidargc	db	'Invalid amount of arguments (Use option ''-?'' for HELP)',cr,lf,eos
err_insidetsr	db	'Illegal interface operation between TSR and INSTALLER !',cr,lf,eos
err_runapp	db	'While executing (resident) application !',cr,lf,eos

ok_unhooked	db	'Unhook-request for ',PRG_name,' passed !!',cr,lf,eos
ok_installed	db	'Resident part of ',PRG_name,' installed (use option ''-?'' for HELP).',cr,lf,eos
ok_freeze	db	PRG_name,' is now temporarely frozen !!',cr,lf,eos
ok_active	db	PRG_name,' is now active again !!',cr,lf,eos
ok_runapp	db	'Execution of (resident) application went OK !',cr,lf,eos
ok_usage	db	'USAGE: ',PRG_name,' [option]',cr,lf,cr,lf
		db	'       Options are : -? -h : This HELP',cr,lf
		db	'                     -u    : Unhook installed TSR',cr,lf
		db	'                     -f    : Temporarely freeze active TSR',cr,lf
		db	'                     -a    : Making frozen TSR active again',cr,lf
		db	'                     -r    : Run (resident) application without install',cr,lf,cr,lf,eos

ARGlist		db	'?hufar',0	; LOWERCASE argument list to execute
ARGcalls	dw	O ARGhelp	; call-table for args
		dw	O ARGhelp
		dw	O ARGunhook
		dw	O ARGfreeze
		dw	O ARGreactivate
		dw	O ARGrunapp

APPname		db	PRG_name,PRG_ver,PRG_date,0

TSRinstalled	db	FALSE

;******************************************************************************
;****************************** INSTALL CODE **********************************
;******************************************************************************

main		proc	far

		mov	ax,cs			; display invitation
		mov	ds,ax

		mov	dx,O myinstall:startsign
		DOSB	09h

		assume	ds:mytsr
		mov	ax,S mytsr		; ds = mytsr
		mov	ds,ax

		mov	w ds:PSPtsr,es		; save pspseg

		DOSW	3000h			; get DOS-version
		cmp	al,02h			; jump when > 2
		ja	@@good_dosver

		stc
		mov	dx,O myinstall:err_wrongdosver
		call	exitAPP			; display wrong version of DOS

@@good_dosver:	push	ds
		xor	ax,ax			; check if our TSR is here
		call	TSRinterface
		pop	ds

		rol	ax,1h
		jnc	@@check_arguments

		mov	B cs:TSRinstalled,TRUE	; our TSR is installed

@@check_arguments:
		mov	ax,w ds:PSPtsr		; es:bx = pointer arglist
		mov	es,ax
		mov	bx,080h

		call	argc			; minimum amount of arguments
		mov	cx,ax			; cx = amount of arguments
		cmp	ax,argMIN		; when none, try install
		jge	@@ok_arg

		stc
		mov	dx,O myinstall:err_invalidargc
		call	exitAPP			; report invalid amount

@@ok_arg:	cmp	ax,1h			; when no arguments
		je	@@install_tsr		; try installing TSR

		mov	ax,1h			; get argv[1] (argument)
		call	argv

		cmp	b es:[bx],'-'		; check if option
		je	@@search_arg		; if not, it's a custom
		cmp	b es:[bx],'/'		;  argument
		je	@@search_arg

@@do_custom:	push	bx
		push	cx
		push	es
		call	ARGcustom		; do CUSTOM argument
		pop	es
		pop	cx
		pop	bx
		jmp	@@install_tsr

@@search_arg:	push	cs			; check which argument
		pop	ds			; is the correct one
		assume	ds:myinstall

		cld
		mov	di,O ARGcalls
		mov	si,O ARGlist

		IFDEF	ARGCASEINS		; when defined case insens
		cmp	b es:[bx+1],65d 	; adjust to LOWERCASE
		jl	@@arg_loop
		cmp	b es:[bx+1],90d
		jg	@@arg_loop
		add	b es:[bx+1],32d
		ENDIF

@@arg_loop:	lodsb				; get argument case

		cmp	b es:[bx+1d],al		; when same, make call
		je	@@make_call

		add	di,2h			; next call in list

		or	al,al			; when no zero, check
		jnz	@@arg_loop		; next

		stc				; unknown argument
		mov	dx,O myinstall:err_badarg
		call	exitAPP

@@make_call:	push	bx
		push	cx
		push	es
		call	W ds:[di]		; make argument call
		pop	es
		pop	cx
		pop	bx

		cmp	cx,2h			; when more than one
		jg	@@do_custom

@@install_tsr:	rol	cs:TSRinstalled,1h	; when TSR already
		jnc	@@try_install		; installed

		mov	dx,O myinstall:err_installed	; CARRY SET
		call	exitAPP			; report already installed

@@try_install:	assume	ds:mytsr		; set correct pointer
		mov	ax,S mytsr
		mov	ds,ax

		DOSB	34h			; get indosflag
		mov	W ds:INDOSaddr,bx	; store address
		mov	W ds:INDOSaddr+2,es

		mov	W ds:ERRMODEaddr+2,es	; assume errormode = same seg
		dec	bx
		mov	W ds:ERRMODEaddr,bx	; and one byte before indos

		cld
		mov	cx,ISRamount		; number of ISR to install
		mov	si,O mytsr:ISRstart

@@donextisr:	lodsb				; get int-nr

		push	ax
		DOSB	35h			; get int-nr vector
		mov	[si+1],bx		; store old one
		mov	[si+3],es
		pop	ax

		push	ds
		mov	dx,[si+5]		; get new off
		mov	bx,S mytsr		; get new seg
		mov	ds,bx
		DOSB	25h
		pop	ds
		add	si,7h			; next interrupt
		loop	@@donextisr

		clc
		push	cs			; display OK!
		pop	ds
		mov	dx,O myinstall:ok_installed
		DOSB	09h

		mov	ax,S mytsr		; my tsr again!
		mov	ds,ax

		mov	b ds:residentAPP,TRUE	; tsr is resident !

		mov	es,ds:PSPtsr		; get PSPseg back

		push	es
		mov	es,es:[02ch]
		DOSB	049h			; free segment environment

		pop	ax			; ax = pspseg
		mov	dx,cs			; para start of inst-part
		sub	dx,ax			; DX = size of resident
		DOSW	3100h			; exitAPP and stay res

main		endp

;------------------------------------------------------------------------------
; ARG_____ procedures called with ES:BX pointing to argumentlist
; You can use ARGC and ARGV to get the necressary argument info
;
; ES:BX = pointer to arguments
; CX = amount of arguments

ARGcustom	proc	near

		;
		; Place your custom code here, when you want to
		; check for a filename or more than one argument in
		; the TSR..
		;
		; ARGcustom is called when the number of arguments
		; is greater then one, or when the first argument
		; isn't an option.
		;
		ret

ARGcustom	endp

;------------------------------------------------------------------------------

ARGhelp		proc	near

		clc
		mov	dx,O myinstall:ok_usage
		call	exitAPP

ARGhelp		endp

;------------------------------------------------------------------------------

ARGunhook	proc	near

		call	CheckInstall

		mov	al,TSR_UNHOOK		; --> unhook yourself!
		call	TSRinterface

		rol	cl,1h			; if ah = ff, other tsr above
		jc	@@ok_unhook		; our tsr

		cmc
		mov	dx,O myinstall:err_unhook
		call	exitAPP			; report TSR above ours

@@ok_unhook:	cmc
		mov	dx,O myinstall:ok_unhooked
		call	exitAPP			; report unhook-request

ARGunhook	endp

;------------------------------------------------------------------------------

ARGfreeze	proc	near

		call	CheckInstall

		mov	al,TSR_FREEZE		; freeze TSR
		call	TSRinterface

		clc		 		; freeze sign
		mov	dx,O myinstall:ok_freeze
		call	exitAPP

ARGfreeze	endp

;------------------------------------------------------------------------------

ARGreactivate	proc	near

		call	CheckInstall

		mov	al,TSR_ACTIVATE		; reactivate TSR
		call	TSRinterface

		clc		 		; active sign
		mov	dx,O myinstall:ok_active
		call	exitAPP

ARGreactivate	endp

;------------------------------------------------------------------------------

ARGrunapp	proc	near

		rol	cs:TSRinstalled,1h	; when installed
		jnc	@@no_tsr		; execute already resident

		mov	al,TSR_RUNAPP		; try to run TSR
		call	TSRinterface

		mov	dx,O myinstall:ok_runapp

		rol	cl,1h			; display status of app
		jc	@@display		; runned
		jmp	@@err_tsr

@@no_tsr:	call	F mytsr:callAPP		; call APP and start

		mov	dx,O myinstall:ok_runapp
		mov	ax,S mytsr		; display status
		mov	ds,ax			; afterwards
		rol	b ds:APPstatus,1h
		jc	@@display

@@err_tsr:	mov	dx,O myinstall:err_runapp

@@display:	cmc
		call	exitAPP
		ret

ARGrunapp	endp

;------------------------------------------------------------------------------

ARGforceexec	proc	near



ARGforceexec	endp

;------------------------------------------------------------------------------

; CheckInstall
;	This routine checks if the tsr is installed, and
;	gives an error when not.
;
; - assumes -
;	cs:TSRinstalled : TRUE when installed (OK)
;			  FALSE when not (ERROR)
;
; - returns -
;	Only when TSR is installed
;
CheckInstall	proc	near

		rol	cs:TSRinstalled,1h	; when installed
		jnc	@@no_tsr		; return normally
		ret

@@no_tsr:	cmc
		mov	dx,O myinstall:err_notinstall
		call	exitAPP

CheckInstall	endp

;------------------------------------------------------------------------------
; exitAPP
;  This procedure prints a text pointed to by ds:dx and exitAPPs the program
;  with an error (carry = 1), or normally (carry = 0)
;
; - assumes -
;  CARRY = 1 : Error occured
;  CARRY = 0 : exitAPP normally
;  DX        : offset for message
;
; - returns -
;  To dos with errorlevel = 1 if carry = 1
;  else to dos with errorlevel = 0.
;
exitAPP		proc	near

		push	cs			; prepare pointer
		pop	ds

		mov	al,0h			; assume error without
		jnc	@@good			; disturbing CARRY

		inc	al

		push	ax			; push error
		push	dx
		mov	dx,O err_presign
		DOSB	09h			; print error-presign
		pop	dx
		pop	ax

@@good:		push	ax
		DOSB	09h			; exitAPP exit with returncode
		pop	ax

		DOSB	4ch

exitAPP		endp

;------------------------------------------------------------------------------
; TSRinterface
;  This function tries to talk to our TSR.
;
; - assumes -
;	AX = Function value (See function FRONTinterface for details)
;
; - returns -
;	(See function FRONTinterface for details)
;
TSRinterface	proc	near

		mov	dx,HIGHsign		; prepare KEY for TSR
		mov	bx,LOWsign		; to get through
		mov	cx,bx
		add	cx,dx
		push	cs
		pop	ds
		mov	si,O myinstall:APPname

		int	28h			; talk to our TSR

		cmp	ax,TRUE			; is our TSR present ?
		jne	@@no_tsr

		or	bx,bx			; when FALSE, error from TSR
		jnz	@@no_tsr

		stc
		mov	dx,O myinstall:err_insidetsr
		call	exitAPP		; illegal error inside TSR

@@no_tsr:	ret

TSRinterface	endp


;------------------------------------------------------------------------------

; Call with:  ES:BX = command line
;
; Returns:    AX    = argument count (always >=1)
;	      Other registers preserved

argc		proc	near

		push	bx
		push	cx
		mov	ax,1
@@argc1:	mov	cx,-1
@@argc2:	inc	bx
		cmp	byte ptr es:[bx],cr
		je	@@argc3
		cmp	byte ptr es:[bx],blank
		je	@@argc1
		cmp	byte ptr es:[bx],tab
		je	@@argc1
		jcxz	@@argc2
		inc	ax
		not	cx
		jmp	@@argc2
@@argc3:	pop	cx
		pop	bx
		ret

argc		endp

;------------------------------------------------------------------------------
;
; ARGV.ASM:  return address and length of specified
; command line argument or fully qualified program
; name.  Treats blanks and tabs as whitespace, carriage
; return as terminator.
;
; (C) 1987 Ray Duncan
;
; Call with:  ES:BX = command line address
;                     (implicit: ES=PSP segment)
;             AX    = argument number (0 based)
;
; Returns:    ES:BX = argument address
;             AX    = argument length
;                     (0=argument not found)
;             Other registers preserved.
;
; If called with AX=0 (argv[0]) and running under
; MS-DOS version 3.0 or later, returns ES:BX pointing
; to program name in environment block and AX=length,
; otherwise returns ES:BX unchanged and AX=0.
;

argv		proc		near

		push	cx
		push	di
		or	ax,ax
		jz	@@argv8
		xor	ah,ah
@@argv1:	mov	cx,-1
@@argv2:	inc	bx
		cmp	byte ptr es:[bx],cr
		je	@@argv7
		cmp	byte ptr es:[bx],blank
		je	@@argv1
		cmp	byte ptr es:[bx],tab
		je	@@argv1
		jcxz	@@argv2
		inc	ah
		cmp	ah,al
		je	@@argv4
		not	cx
		jmp	@@argv2
@@argv4:	mov	ax,bx
@@argv5:	inc	bx
		cmp	byte ptr es:[bx],cr
		je	@@argv6
		cmp	byte ptr es:[bx],blank
		je	@@argv6
		cmp	byte ptr es:[bx],tab
		jne	@@argv5
@@argv6:	xchg	bx,ax
		sub	ax,bx
		jmp	@@argvx
@@argv7:	xor	ax,ax
		jmp	@@argvx
@@argv8:	mov	ax,3000h
		int	21h
		cmp	al,3
		jb	@@argv7
		mov	es,es:[2ch]
		xor	di,di
		xor	al,al
		mov	cx,-1
		cld
@@argv9:	repne scasb
		scasb
		jne	@@argv9
		add	di,2
		mov	bx,di
		mov	cx,-1
		repne scasb
		not	cx
		dec	cx
		mov	ax,cx
@@argvx:	pop	di
		pop	cx
		ret

argv		endp

;------------------------------------------------------------------------------

myinstall	ends

mystack		SEGMENT WORD STACK 'stack'

; *******************************************************************
; ************************ INSTALL STACK ****************************
; *******************************************************************

		db	256 dup ('?')

mystack		ENDS

		END main
