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
