;WATCH.ASM
;resident routine watches programs going resident
;and keeps a list of interrupt vector changes in an internal data structure
;==============================================================================
; to be assembled by TASM
; Copyright (c) 1986,1993 Kim Kokkonen, TurboPower Software.
; May be freely distributed but not sold except by permission.
; telephone: 719-260-6641, Compuserve 76004,2611
;==============================================================================
; version 2.2  3/4/87
;   First release, version to be consistent with MAPMEM.PAS
; :
; long intervening history
; :
; version 3.0  9/24/91
;   add tracking for TSRs that unload themselves
;   add support for TSRs loaded high
;   WATCH may be loaded high
; version 3.1  11/4/91
;   rewrite again to solve problems with SWAPMM, FSP, DATAPATH, DATAMON
; version 3.2  11/22/91
;   change method of accessing high memory
;   deal with DOS 5 MODE int trapping (int seg < psp seg)
; version 3.3  1/8/92
;   relocate AddChain code so that it doesn't get overwritten if there
;     are lots of initial memory blocks
; version 3.4  2/14/92
;   no change
; version 3.5 10/18/93
;   new hybrid method for finding high memory
;==============================================================================
;
;uncomment following line to generate more publics in MAP file
;       debug   = 1

cseg    segment public para
        assume  cs:cseg, ds:nothing, es:nothing, ss:nothing
        locals  @@

        org     080H
cmdline label   byte                    ;pointer to command line

        org     100H
pentry: jmp     init

;always put the following in WATCH.MAP to update MEMU.PAS
public nextchange,emesg,changevectors,origvectors

;***********************************************************************
;data structures part of COM file
                even
nextchange      dw      0               ;next position to write in changes area

firstmcb        dw      ?               ;first MCB segment
firsthimcb      dw      0               ;first MCB segment in high memory

;temporary stack used by interrupt handler
newsp           dw      ?               ;initial stack pointer
newss           dw      ?               ;segment of our temporary stack (=cseg)
tmpret          dw      ?               ;used while switching stacks

;information saved about the calling program
oldsp           dw      ?               ;stack pointer
oldss           dw      ?               ;stack segment

;previous interrupt handlers
dos_int         label dword
old21           dw 2 dup (?)            ;old int21 vector
tsr_int         label dword
old27           dw 2 dup (?)            ;old int27 vector

;XMS access
xmsadr  label   dword                   ;XMS control address
xmsxxx  dw      2 dup (0)

;id code for a PSP data block
pspid           equ     0FFFFH          ;id used to indicate a PSP block

;structure of a changevectors data block
pspblock        struc
                id      dw      ?       ;id word, always pspid
                psp     dw      ?       ;psp segment
                len     dw      ?       ;length of psp
                unu1    dw      ?       ;unused
pspblock        ends
vecblock        struc
                vec     dw      ?       ;vector number 0..255
                veco    dw      ?       ;vector offset
                vecs    dw      ?       ;vector segment
                unu2    dw      ?       ;unused
vecblock        ends

;***********************************************************************
;resident data structures not part of COM file
changevectors   =       offset emesg            ;data area overwrites emesg & beyond
vrecsize        =       8                       ;number of bytes per vector change record
maxchanges      =       128                     ;maximum number of vector changes
vsize           =       maxchanges*vrecsize     ;size of vector change area in bytes

;vector table buffers
origvectors     =       offset changevectors+vsize ;location of original vector table
veclen          =       1024                    ;size of vector table in bytes
newstackpos     =       origvectors+veclen      ;location of newstack
ssize           =       128                     ;number of bytes in temporary stack
newloc          =       newstackpos+ssize       ;location for relocated installation code

;***********************************************************************
;int21 handler
;  traps functions 31, 49, 4C, and 7761
int21h  proc far
ifdef   debug
        public  int21h
endif
        assume ds:nothing
        pushf                           ;save flags
        sti                             ;allow interrupts

        cmp     ah,31H                  ;terminate and stay resident call?
        jne     @@1
        call    addcurrpsp              ;dx = paras to keep
        jmp     short @@4

@@1:    cmp     ah,49H                  ;deallocate block call?
        jne     @@2
        call    remblock                ;remove specified block if a psp
        jmp     short @@4

@@2:    cmp     ah,4CH                  ;normal program halt?
        jne     @@3
        call    checkblocks
        jmp     short @@4

@@3:    cmp     ax,7761H                ;"wa"tch ID call?
        jne     @@4
        call    checkblocks             ;assure change list up to date
        push    bp
        mov     bp,sp                   ;set up stack frame
        and     word ptr [bp+8],0FFFEH  ;clear carry flag
        pop     bp
        xchg    ah,al                   ;flip ah and al as a signature
        mov     bx,cs                   ;return WATCH psp in bx
        popf
        iret                            ;return to caller

@@4:    popf
        jmp     dos_int                 ;let DOS take over
int21h  endp

;***********************************************************************
;int27 handler
;  watches for programs going resident
int27h  proc far
ifdef   debug
        public int27h
endif
        assume ds:nothing
        pushf
        sti
        push   dx
        add    dx,15            ;pass size of block in paras to addcurrpsp
        shr    dx,1
        shr    dx,1
        shr    dx,1
        shr    dx,1
        call   addcurrpsp       ;get current psp and add block to list
        pop    dx
        popf
        jmp     tsr_int
int27h  endp

;***********************************************************************
;get current PSP in bx and add new block
;entry: dx = paragraphs to keep
addcurrpsp proc near
ifdef   debug
        public  addcurrpsp
endif
        assume  ds:nothing
        call    setup           ;switch stacks and save registers
        assume  ds:cseg
        mov     ah,51H          ;get current PSP in bx
        pushf
        call    dos_int
        call    addblock        ;add block at bx, length dx to changes
        call    shutdown        ;restore registers and switch stacks
        assume  ds:nothing
        ret
addcurrpsp endp

;***********************************************************************
;remove PSP block, if any, specified by es
remblock proc near
ifdef   debug
        public  remblock
endif
        assume  ds:nothing
        call    setup           ;switch stacks and save registers
        assume  ds:cseg
        mov     bx,es           ;save segment being deallocated in bx
        call    matchpsp        ;return offset in changevectors of segment
        or      si,si           ;any matching block?
        jz      @@1
        call    rempsp          ;remove psp
@@1:    call    shutdown        ;restore registers and switch stacks
        assume  ds:nothing
        ret
remblock endp

;***********************************************************************
;scan chain of mcbs starting at segment ax
;remove halting psp from change list if needed
checkchain proc near
ifdef   debug
        public  checkchain
endif
@@1:    mov     es,ax
        mov     bx,es:[0001h]           ;bx = psp of block
        mov     dx,es:[0003h]           ;dx = len of block
        inc     ax
        cmp     ax,bx                   ;does psp = mcb+1?
        jne     @@2                     ;jump if not
        cmp     ax,cx                   ;does psp = current program?
        je      @@2                     ;jump if so
        push    dx
        call    matchpsp                ;find matching psp in changevectors
        pop     dx
        or      si,si                   ;is there a matching psp?
        jnz     @@2                     ;jump if so
        push    ax
        push    cx
        push    dx
        call    addblock                ;add this psp
        pop     dx
        pop     cx
        pop     ax
@@2:    cmp     byte ptr es:[0000h],'Z' ;end of chain
        je      @@3
        add     ax,dx
        jmp     @@1
@@3:    ret
checkchain endp

;***********************************************************************
;check for new memory blocks and add if needed
;remove halting psp from change list if needed
checkblocks proc near
ifdef   debug
        public  checkblocks
endif
        assume  ds:nothing
        call    setup                   ;switch stacks and save registers
        assume  ds:cseg

        mov     ah,51H                  ;get current psp in bx
        pushf
        call    dos_int

        call    matchpsp                ;is current program in change list?
        or      si,si
        jz      @@0                     ;jump if not
        call    rempsp                  ;remove it if not

@@0:    mov     cx,bx                   ;cx = psp of halting program
        mov     ax,firstmcb             ;start with first mcb
        call    checkchain              ;check this chain
        mov     ax,firsthimcb           ;scan high memory
        or      ax,ax
        jz      @@1
        call    checkchain
@@1:    call    shutdown                ;restore registers and switch stacks
        assume  ds:nothing
        ret
checkblocks endp

;***********************************************************************
;setup routine for interrupt hook routines
; switches stacks, saves registers, sets ds=cs
setup   proc    near
ifdef   debug
        public  setup
endif
        assume  ds:nothing
        pop     cs:tmpret       ;save return address as we switch stacks
        mov     oldss,ss        ;save current stack
        mov     oldsp,sp
        cli                     ;switch to our stack
        mov     ss,newss
        mov     sp,newsp
        sti
        push    ax              ;store registers
        push    bx
        push    cx
        push    dx
        push    si
        push    di
        push    bp
        push    ds
        push    es
        push    cs              ;set ds=cs
        pop     ds
        assume  ds:cseg
        push    cs:tmpret       ;return
        ret
setup   endp

;***********************************************************************
;shutdown routine for interrupt hook routines
; restores registers, switches stacks
shutdown proc near
ifdef   debug
        public  shutdown
endif
        pop     cs:tmpret
        pop     es              ;restore registers
        pop     ds
        assume ds:nothing
        pop     bp
        pop     di
        pop     si
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        cli                     ;restore stack
        mov     ss,cs:oldss
        mov     sp,cs:oldsp
        sti
        push    cs:tmpret       ;return
        ret
shutdown endp

;***********************************************************************
;add specified block to changes
;  entry: bx = psp of block, dx = length of block in paras
addblock proc near
ifdef   debug
        public  addblock
endif
        assume  ds:cseg
        call    addhdr          ;add a psp header block
        call    addvecs         ;add blocks for each hooked vector
        ret
addblock endp

;***********************************************************************
;add header for a psp block
;  entry: bx = psp of block, dx = length of block in paras
;  exit:  alters di
addhdr  proc near
ifdef   debug
        public  addhdr
endif
        assume  ds:nothing
        mov     di,nextchange
        cmp     di,vsize-vrecsize       ;assure room for next record
        ja      @@1
        mov     word ptr cs:changevectors[di].id,pspid
        mov     cs:changevectors[di].psp,bx
        mov     cs:changevectors[di].len,dx
        add     di,vrecsize
        mov     nextchange,di
@@1:    ret
addhdr  endp

;***********************************************************************
;add vector blocks for each hooked vector
;  entry: bx = psp of block, dx = length of block in paras
;  exit:  alters ax,cx,dx,si,di,bp
addvecs proc    near
ifdef   debug
        public  addvecs
endif
        assume  ds:cseg
        push    ds
        add     dx,bx                   ;now dx points to end of block
        mov     di,nextchange           ;cs:changevectors[di] -> output area
        xor     si,si
        mov     ds,si                   ;ds:si -> vectors
        assume  ds:nothing
        xor     cx,cx                   ;cx = vector counter
        cld                             ;forward

@@1:    lodsw                           ;ax = vector offset
        mov     bp,ax                   ;save vector offset
        lodsw                           ;ax = vector segment

        cmp     ax,dx                   ;is vector above high limit?
        jae     @@3

        push    cx
        mov     cx,ax
        cmp     bp,800h                 ;don't add unless a small offset
        ja      @@1a                    ;(this is a trap for DOS 5 MODE)

        push    bp
        shr     bp,1
        shr     bp,1
        shr     bp,1
        shr     bp,1
        add     cx,bp                   ;ax = equivalent segment of interrupt
        pop     bp

@@1a:   cmp     cx,bx                   ;is vector above low limit?
        jb      @@2

        pop     cx
        cmp     di,vsize-vrecsize       ;room for another entry?
        ja      @@3
        mov     cs:changevectors[di].vec,cx ;save entry for this vector
        mov     cs:changevectors[di].veco,bp
        mov     cs:changevectors[di].vecs,ax
        add     di,vrecsize
        jmp     short @@3

@@2:    pop     cx
@@3:    inc     cx                      ;next vector
        cmp     cx,0FFh
        jbe     @@1

        mov     nextchange,di
        pop     ds
        assume  ds:cseg
        ret
addvecs endp

;***********************************************************************
;find changeblock matching psp
;  entry: bx = psp to match
;  exit: si = matching block, or 0 if none
;        destroys dx
matchpsp proc near
ifdef   debug
        public  matchpsp
endif
        assume  ds:cseg
        mov     si,offset changevectors
        mov     dx,si
        add     dx,nextchange           ;dx = next unused spot in changevectors
@@1:    cmp     si,dx                   ;end of table
        jae     @@3
        cmp     word ptr [si].id,pspid  ;psp indicator?
        jnz     @@2                     ;jump if not
        cmp     [si].psp,bx             ;matching psp?
        jnz     @@2                     ;jump if not
        ret                             ;else return with match
@@2:    add     si,vrecsize
        jmp     @@1
@@3:    xor     si,si                   ;no match if here
        ret
matchpsp endp

;***********************************************************************
;remove all blocks associated with psp at offset si
;  exit: alters cx,dx,si,di,es
rempsp  proc near
ifdef   debug
        public  rempsp
endif
        assume  ds:cseg
        mov     di,si                   ;save destination
        add     si,vrecsize             ;move to next record
        mov     dx,offset changevectors
        add     dx,nextchange           ;dx = address of next unused
@@1:    cmp     si,dx                   ;end of table?
        jae     @@2                     ;jump if so
        cmp     word ptr [si].id,pspid  ;next psp indicator?
        je      @@2                     ;jump if so
        add     si,vrecsize             ;next block
        jmp     @@1                     ;and loop
@@2:    mov     cx,dx
        sub     cx,si
        shr     cx,1                    ;cx = words to move
        push    cs
        pop     es                      ;es = ds = cs
        cld
        rep     movsw                   ;copy down remaining blocks
        sub     si,di
        sub     nextchange,si           ;update nextchange
        ret
rempsp  endp

;***********************************************************************
;resident portion above, temporary portion below
;***********************************************************************
                align 16
emesg   db      'Cannot install WATCH more than once....',13,10,36
mesg    db      'WATCH 3.5, Copyright 1993 TurboPower Software',13,10
        db      'Installed successfully',13,10,36
pname   db      'TSR WATCHER'
plen    equ     $-pname                 ;length of string

;***********************************************************************
init    proc    near
ifdef   debug
        public  init
endif
        assume  ds:cseg

;use int 21h test to check for previous installation
        mov     ax,7761H                ;special id function
        int     21H
        jc      @@1                     ;not installed if function fails
        cmp     ax,6177H
        jnz     @@1                     ;not installed if id code not returned

;error exit
        mov    dx,offset emesg          ;error message
        mov    ah,09H
        int    21H                      ;DOS print string
        mov    ax,4C01H                 ;exit with error
        int    21H

;not already installed
@@1:    mov    dx,offset mesg           ;success message
        mov    ah,09H
        int    21H                      ;DOS print string

;initialize location of WATCH stack
        mov     newsp,newstackpos+ssize
        mov     newss,cs                ;stack seg is code seg

;put an id label at offset 80H to allow other programs to recognize WATCH
        mov     cx,plen                 ;length of name string
        mov     si,offset pname         ;offset of name string
        mov     di,offset cmdline       ;offset of DOS command line
        cld                             ;transfer in forward direction
        mov     al,cl
        stosb                           ;store length byte first
        rep     movsb                   ;transfer characters

;relocate ourselves out of the way of the resident tables
        push    cs
        pop     es
        mov     di,newloc+10H
        push    di                      ;will act as a return address
        mov     si,offset @@2
        mov     cx,endcode-@@2
        rep     movsb                   ;move code
        ret                             ;"return" to the relocated code

@@2:
;add psp records for all blocks already resident
        call    adddummypsp

;store image of original vector table (overwrites messages and non-res code)
        push    cs
        pop     es
        mov     di,origvectors
        push    ds
        xor     si,si                   ;offset 0
        mov     ds,si                   ;source address segment 0
        mov     cx,200H                 ;512 words to store
        rep     movsw                   ;copy vectors to our table
        pop     ds

;store current int 21 and 27 vectors
        mov     ax,3527H
        int     21H
        mov     old27,bx
        mov     old27[2],es
        mov     ax,3521H
        int     21H
        mov     old21,bx
        mov     old21[2],es

;install new vectors
        mov    ax,2527H
        mov    dx,offset int27h
        int    21H
        mov    ax,2521H
        mov    dx,offset int21h
        int    21H

;terminate and stay resident
        mov    dx,newloc
        add    dx,15
        mov    cl,4
        shr    dx,cl
        mov    ax,3100H         ;return success code
        int    21H              ;note WATCH will track itself
@@3:
init    endp

;***********************************************************************
;add dummy changeblocks for all psps already resident in chain starting at ax
addchain proc near
ifdef   debug
        public  addchain
endif
        assume  ds:cseg
@@1:    mov     es,ax
        mov     bx,es:[0001h]           ;bx = psp of block
        mov     dx,es:[0003h]           ;dx = len of block
        inc     ax
        cmp     ax,bx                   ;does psp = mcb+1?
        jne     @@2                     ;jump if not
        cmp     ax,newss                ;does psp = WATCH itself?
        je      @@2                     ;jump if so
        mov     cx,offset addhdr
        call    cx
;        call    addhdr                  ;add a header for this block
@@2:    cmp     byte ptr es:[0000h],'Z' ;end of chain
        je      @@3
        add     ax,dx
        jmp     @@1
@@3:    ret
addchain endp

;***********************************************************************
findhidos proc near
;returns ax = first high mcb, or 0 if none found
        mov     ah,52H                  ;get first mcb segment
        int     21H
        mov     ax,es:[bx-2]            ;ax=first mcb

        push    ds
        assume  ds:nothing

@@1:    cmp     ax,9FFFH                ;above 640K?
        ja      @@2                     ;jump if so
        mov     ds,ax                   ;ds:[0] points to mcb
        xor     ax,ax                   ;set ax to zero in case we exit here
        cmp     byte ptr ds:[0],'Z'     ;end of mcb chain?
        je      @@2                     ;exit if so
        mov     ax,ds                   ;restore ax to mcb segment
        inc     ax                      ;skip over mcb itself
        add     ax,ds:[3]               ;add length of memory block
        jmp     @@1

@@2:    pop     ds                      ;ds = cs
        assume  ds:cseg
        ret
findhidos endp

;***********************************************************************
;return segment of first high memory mcb in ax
findhimemstart proc near
ifdef   debug
        public findhimemstart
endif

        mov     ax,5802h
        int     21H                     ;get umb link status
        or      al,al                   ;high memory already linked?
        jz      @@u1                    ;jump if not

        call    findhidos               ;find mcb using linked chain

        or      ax,ax                   ;valid mcb seg returned?
        jz      @@u9                    ;jump if not
        mov     cx,ax
        jmp     @@9                     ;found first umb

@@u1:   mov     ax,5803H
        mov     bx,1
        int     21H                     ;link umb
        jc      @@u9                    ;jump if failed

        call    findhidos               ;find mcb using linked chain

        push    ax                      ;save result
        mov     ax,5803H
        xor     bx,bx
        int     21H                     ;unlink umb
        pop     ax                      ;restore result

        or      ax,ax                   ;valid mcb seg returned?
        jz      @@u9                    ;jump if not
        mov     cx,ax
        jmp     @@9                     ;found first umb

@@u9:   mov     ax,3000h                ;get DOS version
        int     21H
        cmp     al,3
        jb      @@7                     ;no XMS driver possible
        mov     ax,4300h
        int     2Fh                     ;multiplex call for XMS
        cmp     al,80h                  ;proper signature?
        jne     @@7                     ;no XMS driver
        mov     ax,4310h
        int     2Fh
        mov     xmsxxx,bx               ;save XMS control address
        mov     xmsxxx[2],es
        mov     ah,10h
        mov     dx,0FFFFh
        call    xmsadr                  ;ask to allocate FFFF paras of UMB
        cmp     bl,0B0h                 ;will fail with B0 if UMBs avail
        je      @@0
        cmp     bl,0B1h                 ;will fail with B1 if UMBs all allocated
        jne     @@7                     ;no UMBs exist
@@0:    int     12H
        mov     cl,6
        shl     ax,cl                   ;get segment of top of memory

@@1:    mov     es,ax
        cmp     byte ptr es:[0000h],'M' ;potential mcb?
        jnz     @@6                     ;not an mcb, try next segment
@@2:    mov     cx,ax                   ;save potential start mcb in cx
@@3:    inc     ax
        add     ax,es:[0003h]           ;ax = start of next mcb
        jc      @@5                     ;can't be an mcb if we wrapped
        mov     es,ax                   ;address of next mcb
        mov     dl,es:[0000h]
        cmp     dl,'M'
        jz      @@3                     ;good start mcb
        cmp     dl,'Z'
        jz      @@9                     ;good end mcb
@@5:    mov     ax,cx                   ;restore last start segment
@@6:    cmp     ax,0FFFFh               ;top of memory?
        je      @@7
        inc     ax                      ;try next segment
        jmp     @@1

@@7:    xor     cx,cx                   ;no matching UMB
@@9:    mov     ax,cx                   ;return segment in ax
        ret
findhimemstart endp

;***********************************************************************
;add dummy changeblocks for all psps already resident
adddummypsp proc near
ifdef   debug
        public  adddummypsp
endif
        assume  ds:cseg
        mov     ah,52H
        int     21H                     ;get DOS list of lists
        mov     ax,es:[bx-2]            ;get first MCB segment
        mov     firstmcb,ax             ;save it for use later too
        call    addchain

        call    findhimemstart          ;find first high memory mcb
        mov     firsthimcb,ax           ;save it for use later too
        or      ax,ax
        jz      @@1
        call    addchain                ;add blocks in high memory too
@@1:    ret
adddummypsp endp

endcode:
cseg    ends
        end     pentry
