; This code illustrates how to install and deinstall a TSR and under what
; circumstances the body of a TSR should be given control.
;
; The hot keys in this example are the Alt + Left Shift keys.  This hot key
; combination is defined in the variable hot_keys and can be changed to suit
; the user by changing the bit pattern.
;
; This example is intended to illustrate the recommendations made in the
; following articles:
;
;       "DOS: Terminate and Stay Resident", M. Steven Baker
;       Programmer's Journal, Nov/Dec 1986, pages 26 - 30
;
;       "Coding Guidelines for Resident Programs", Graham Pearson
;       Programmer's Journal, Mar/Apr 1987, pages 34 - 38
;
;
; The author can be reached at the following address:
;       Kudos Software,  9111 Cadawac Road,  Houston, Texas 77074
;       Tel: (713) 774-6108

;******************************************************************************

DELAY_COUNT     EQU     36              ;Timer counts between hot key triggers
                                        ;36 ticks = approx. 2 seconds

; Locations of BIOS Data needed by the resident program

bios_data       segment at 40H
        org             17H             ;Keyboard status flags
        kbd_status      dw      ?
        org             6CH             ;Timer count - low word. This rolls
        low_timer       dw      ?       ;around 0FFFFH once per hour
bios_data       ends

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

code            segment
        assume  cs:code, ds:nothing, es:nothing, ss:nothing
        org     100H                    ;Define starting point for .COM file

entry_point:
        jmp     install                 ;Skip over the code to be made resident

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

        hot_keys        dw      01010B  ;Hot key bits -> Alt + Left Shift

        this_time       dw      ?       ;Most-recent time hot keys detected
        trig_time       dw      ?       ;Last time resident code was triggered

        dos_busy        label   dword   ;Pointer to "DOS is busy" flag
        dos_busy_off    dw      ?       ;These values are initialized during
        dos_busy_seg    dw      ?       ;the "install" process

        criterr_flag    dw      ?       ;Used by diverted INT 24H

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

; Replacement for INT 24H - critical DOS error

diverted_int24:

        mov     cs:criterr_flag,0FFFFH  ;Switch error flag ON
        xor     al,al                   ;Tell DOS to ignore the error
        iret                            ;Return to DOS

        int_24_vect     label   dword
        int_24_off      dw      ?       ;These values are initialized each time
        int_24_seg      dw      ?       ;the TSR body takes control

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

; Replacement for INT 9 - keyboard hardware interrupt

diverted_int9:

        pushf                           ;Simulate an INT 9 to pass through the
        call_int9       db      09AH    ;original BIOS interrupt handler
        int_9_vect      label   dword
        int_9_off       dw      ?       ;These values are initialized during
        int_9_seg       dw      ?       ;the "install" process

        push    ds                      ;Save all registers used by this
        push    bx                      ;section of code
        lds     bx,dos_busy             ;See if DOS is busy
        cmp     byte ptr [bx],0
        pop     bx                      ;Restore all registers
        pop     ds
        jz      get_bios_data           ;Zero means DOS is not busy

dos_is_busy:
        iret                            ;Return control to interrupted process

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

; Replacement for INT 28 - generated by DOS, esp. during keyboard I/O functions

diverted_int28:

        pushf                           ;Simulate an INT 28H to pass through
        call_int28      db      09AH    ;the original DOS interrupt handler
        int_28_vect     label   dword
        int_28_off      dw      ?       ;These values are initialized during
        int_28_seg      dw      ?       ;the "install" process

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

get_bios_data:

        sti                             ;Restore interrupts
        push    ds                      ;Save all registers used by this
        push    bx                      ;section of code

        mov     bx,bios_data            ;Retrieve BIOS Data values
        mov     ds,bx
        assume  ds:bios_data

        mov     bx,low_timer            ;Retrieve and save timer count
        mov     cs:this_time,bx
        mov     bx,kbd_status           ;Retrieve keyboard status bits

        push    cs                      ;Point DS to our code segment
        pop     ds
        assume  ds:code

chk_keys:
        and     bx,hot_keys             ;See if hot keys are pressed
        cmp     bx,hot_keys
        jne     back_to_applic

chk_timer:
        mov     bx,this_time
        cmp     bx,trig_time            ;Enter resident routine if timer
        jb      time_is_right           ;count has rolled around the hour

        sub     bx,trig_time
        sub     bx,DELAY_COUNT          ;Transfer to resident code only if
        jnc     time_is_right           ;elapsed time between hot keys is
                                        ;greater than delay count
back_to_applic:
        pop     bx                      ;Restore all registers
        pop     ds
        iret                            ;Return control to interrupted process

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

time_is_right:
        mov     bx,this_time            ;Update latest trigger time
        mov     trig_time,bx
        pop     bx                      ;Restore all registers
        pop     ds

;******************************************************************************

; This is the start of the application-dependent resident code

start_program:

        push    ax                      ;Save all registers used by the
        push    bx                      ;resident application
        push    cx
        push    dx
        push    si
        push    di
        push    bp
        push    ds
        push    es

        push    cs                      ;Point DS to our CS
        pop     ds

        mov     ax,3524H                ;Get current INT 24H vector
        int     21H
        mov     int_24_off,bx
        mov     int_24_seg,es
        mov     ax,2524H                ;Redirect INT 24H
        mov     dx,offset diverted_int24
        int     21H


;******************************************************************************

        ; Include application specific code here

;******************************************************************************


restore_int24:
        lds     dx,int_24_vect          ;Restore INT 24H
        mov     ax,2524H                ;Error in PJ listing (was 2509H)
        int     21H

        pop     es                      ;Restore all registers
        pop     ds
        pop     bp
        pop     di
        pop     si
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        iret                            ;Return control to interrupted process

; This is the end of the application-dependent resident code

        end_of_res      label   word

;******************************************************************************

; This procedure installs the resident section of code in memory.
; It performs the following tasks:
;
;    1  Make sure the operating system is at least DOS 2.00
;
;    2  Make sure the resident code is not already installed in memory
;       If it IS already installed, remove it from memory
;
;    3  Retrieve an available vector for installing the signature
;
;    4  Retrieve vector to "DOS is busy" and save it for use by resident code
;
;    5  Update the interrupt vectors required by resident code.
;
;    6  Install the signature in the available vector found in step 3
;
;    7  Terminate and stay resident - transfer control back to DOS

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

install:

; Make sure that the operating system is at least DOS 2.00

        mov     ah,30H
        int     21H
        or      al,al
        jnz     chk_vectors

        mov     dx,offset baddos_msg    ;DS:DX -> Display "bad DOS" message
        mov     ah,9
        int     21H

        int     20H                     ;Return to DOS the old-fashioned way

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

; See if the resident code is already installed in memory.
; If so, remove the resident code from memory.
; If not, retain an available interrupt vector for later use as a signature.

chk_vectors:
        push    ds                      ;Save DS -> CS

        xor     ax,ax                   ;Search 32 interrupt vectors starting
        mov     ds,ax                   ;at INT 60H - user interrupt area
        mov     si,60H * 4
        mov     cx,20H

next_vector:
        mov     ax,[si + 2]
        or      ax,ax                   ;If segment value is zero, get offset
        jz      get_vect_off            ;value

        cmp     ax,0D0C7H               ;Check interrupt segment value against
        jne     vector_loop             ;resident code signature

deinstall:
        mov     bx,[si]                 ;Retrieve original resident code
                                        ;segment value
        xor     ax,ax
        mov     [si],ax                 ;Remove signature from the
        mov     [si + 2],ax             ;interrupt vector

restore_int9:
        mov     ds,bx                   ;DS -> original resident code segment
        lds     dx,int_9_vect           ;Restore INT 9 - keyboard I/O
        mov     ax,2509H
        int     21H

restore_int28:
        mov     ds,bx                   ;DS -> original resident code segment
        lds     dx,int_28_vect          ;Restore INT 28 - DOS interrupt
        mov     ax,2528H
        int     21H

        mov     es,bx                   ;ES -> code segment to be deinstalled
        mov     ah,49H
        int     21H                     ;Free allocated memory

        mov     es,es:[2CH]             ;ES -> environment to be deinstalled
        mov     ah,49H
        int     21H                     ;Free allocated memory

        pop     ds                      ;Restore DS -> CS
        mov     dx,offset remove_msg    ;DS:DX -> Display "remove" message
        mov     ah,9
        int     21H

        mov     ax,4C01H                ;Return error code of 1
        int     21H                     ;Return to DOS with error code

get_vect_off:
        cmp     word ptr [si],0         ;If offset value is zero, this vector
        je      found_vector            ;is available for our use

vector_loop:
        add     si,4                    ;Interrogate next vector
        loop    next_vector

found_vector:

        ;Make sure we successfully complete the install process before
        ;installing the signature in this available interrupt vector

        pop     ds                      ;Restore Data Segment
        mov     free_vector,si          ;Save free vector for later

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

; Retrieve original interrupt vectors and install these in the resident code.
; Redirect the interrupt vectors to point to resident code.
; Install signature in available interrupt vector.

        push    es                      ;Save ES -> CS

        mov     ah,34H                  ;Retrieve segment + offset
        int     21H                     ;pointer to "DOS is busy" flag
        mov     dos_busy_off,bx
        mov     dos_busy_seg,es

        mov     ax,3509H                ;Get current INT 9H vector
        int     21H
        mov     int_9_off,bx
        mov     int_9_seg,es
        mov     ax,2509H                ;Redirect INT 9H
        mov     dx,offset diverted_int9
        int     21H

        mov     ax,3528H                ;Get current INT 28H vector
        int     21H
        mov     int_28_off,bx
        mov     int_28_seg,es
        mov     ax,2528H                ;Redirect INT 28H
        mov     dx,offset diverted_int28
        int     21H

        pop     es                      ;Restore ES -> CS
        mov     di,free_vector          ;Retrieve value found earlier

        push    ds                      ;Save DS -> CS
        xor     ax,ax                   ;DS -> interrupt vectors
        mov     ds,ax
        mov     [di],cs                 ;Install signature in the available
        mov     word ptr [di+2],0D0C7H  ;interrupt vector
        pop     ds                      ;Restore DS -> CS

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

; Display installed message

        mov     dx,offset success_msg   ;DS:DX -> Display "success" message
        mov     ah,9
        int     21H

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

; Prepare for program "terminate and stay resident"

        mov     dx,offset end_of_res    ;Calculate the amount of memory used
        dec     dx                      ;by the resident code
        or      dx,0FH
        inc     dx                      ;Round up to the nearest paragraph

        mov     cl,4                    ;Convert code size to paragraphs
        shr     dx,cl
        mov     ax,03100H               ;Return error code of 0
        int     21H                     ;Terminate but stay resident

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

        free_vector     dw      ?       ;Pointer to available interrupt vector

        copyright       db      '  Copyright (c) 1986 Kudos Software  '

        baddos_msg      db      'TSR will not run with DOS 1.XX'
                        db      0DH, 0AH, '$'

        remove_msg      db      'TSR is no longer resident in memory'
                        db      0DH, 0AH,'$'

        success_msg     db      'TSR successfully installed in memory'
                        db      0DH, 0AH, '$'

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

code            ends
                end     entry_point     ;Define entry point for .COM file
