Metropoli BBS
VIEWER: spy.asm MODE: TEXT (ASCII)
name spy

rt              equ     0dh     ; return
lf              equ     0ah     ; linefeed
eof             equ     1ah     ; end of file
ok_seg          equ     1       ; flag for segment decode check
ok_off          equ     2       ; flag for offset decode check
max_size        equ     4095    ; maximum size for SPY search region
beep_delay      equ     4       ; number of clock ticks between beeps
timer_count     equ     597     ; frequency of beep = 1,193,180 / timer_count

code            segment
                assume cs:code, ds:code

                org     0
key1            dw      ?       ; offset of int 20h instruction
                                ;   used to verify a PSP location

                org     100h

begin:          jmp     start   ; jump to program start location

;-------------------------------------------------------------------------------
; This copyright message appears when SPY.COM is "typed" to the screen.
;-------------------------------------------------------------------------------

                db      8,8,8,'   '
copyright       db      rt,lf,'SPY Copyright (C) 1987 by Charles Lazo III, v1.0'
                db      eof,8,' ',rt,lf,'$'

key2            dw      1234h   ; keys used to determine prior load of SPY
key3            dw      5678h

oldint8         dd      ?       ; double word storage for old int 8 vector
beeps           dw      0       ; number of beeps (notices) to send to user
delay           dw      1       ; number of clock ticks 'til next beep
beep_on         db      0       ; beep on/off status
spy_size        dw      ?       ; number of bytes to spy on
resident        db      0       ; indicates prior load of SPY (0 = none)

spy_location    label   dword
spy_off         dw      ?       ; storage for value of offset for SPYing
spy_seg         dw      ?       ; storage for value of segment for SPYing

;-------------------------------------------------------------------------------
; Comparisons of the spied region and the spy buffer (set up initially as a copy
; of the spied region) are made in this addition to the timer interrupt.
; Changes to the spied region are counted and the spy buffer is updated.  The
; speaker is turned on and off here, but frequency is set during program
; initialization.
;-------------------------------------------------------------------------------

newint8         proc    near
                push    ax              ; save interrupted program's registers
                push    bx
                push    cx
                push    si
                push    di
                push    ds
                push    es

                mov     ax,cs           ; our data lies here
                mov     ds,ax

                lea     si,spy_buffer   ; point to our buffer
                les     di,spy_location ; point to spied region
                mov     cx,spy_size     ; compare whole region
                cld                     ; forward!
cmp_more:       repz    cmpsb           ; compare until no match or cx = 0
                jz      cmp_done        ; if zero, then cx = 0 and we're done
                inc     beeps           ; account for a change
                mov     al,es:[di-1]    ; change accounted; update spy_buffer
                mov     [si-1],al
                or      cx,cx           ; set zero flag by cx (avoid inf loop)
                jmp     short cmp_more  ; continue 'til done

cmp_done:       cmp     beep_on,0       ; is the beep on?
                jz      do_beep?        ; no, shall we do a beep?
                dec     beep_on         ; yes, turn it off
                in      al,97           ; get speaker control bits
                and     al,0fch         ; set them off
                out     97,al           ; turn off speaker
                jmp     short exit      ; job done; get out
do_beep?:       cmp     beeps,0         ; are there beeps to be done?
                jz      delay1          ; no, get out
                dec     delay           ; reduce delay 'til next beep
                jnz     exit            ; not zero, then exit
                in      al,97           ; get speaker control bits
                or      al,3            ; set them on
                out     97,al           ; turn on speaker
                inc     beep_on         ; signal beep is on
                mov     delay,beep_delay; reinitialize delay counter
                dec     beeps           ; one less beep to do
                jmp     short exit      ; leave now
delay1:         mov     delay,1         ; don't wait for first beep of a series
exit:           pop     es              ; restore registers
                pop     ds
                pop     di
                pop     si
                pop     cx
                pop     bx
                pop     ax
                jmp     dword ptr cs:oldint8    ; continue with interrupt
newint8         endp

;-------------------------------------------------------------------------------
; Here the region spied upon is copied to an area of the TSR that remains in
; memory after termination, the spy_buffer.  The es register is equal either to
; cs if SPY has not yet been loaded or to the value of cs when SPY was loaded
; previously (accomplished by a call to the set_es routine).
;-------------------------------------------------------------------------------

copy_spied:     lea     di,spy_buffer   ; destination of copy
                mov     si,spy_off      ; the offset is source of copy
                mov     cx,spy_size     ; number of bytes to copy
                mov     ax,spy_seg      ; load segment of SPY region to ds
                push    ds              ; SOD (save our data)
                mov     ds,ax
                cld                     ; forward copy
                rep     movsb           ; copy the SPY region to spy_buffer
                pop     ds              ; RestoreOD

                cmp     resident,0      ; is SPY currently resident in memory?
                je      tsr             ; no, make it so
                mov     ax,4c00h        ; yes, end with error code 0
                int     21h

;-------------------------------------------------------------------------------
; SPY has not yet been loaded into memory, so we do it now.  First the 8253-5
; chip that generates the frequency of the beeps is initialized with the value
; given by the constant timer_count.  Then the new interrupt 8 routine is
; spliced in and finally the constant max_size is used to reserve enough memory
; for the largest permissible spy buffer.
;-------------------------------------------------------------------------------

tsr:            mov     delay,beep_delay; initialize delay counter

                ; set 8253-5 programmable timer to chosen frequency
                mov     al,182          ; byte to initialize 8253 timer
                out     67,al           ; tell timer next two bytes are count
                mov     ax,timer_count  ; get timer count
                out     66,al           ; output low byte
                mov     al,ah
                out     66,al           ; output high byte

                mov     ax,3508h        ; get the interrupt 8 (clock) vector
                int     21h             ;   with DOS function 35h call

; Retain offset and segment of interrupt 8 vector:

                mov     word ptr cs:oldint8,bx
                mov     word ptr cs:oldint8+2,es

                lea     dx,newint8      ; place offset in dx
                mov     ax,2508h        ; set its pointer into interrupt table
                int     21h             ;   with DOS function 25h call

                lea     dx,spy_buffer   ; where SPY buffer begins
                mov     cx,max_size     ; add the maximum size of the buffer:
                add     dx,cx
                mov     cl,4            ; compute number of paragraphs to save:
                shr     dx,cl           ;   bytes to paragraphs in dx
                inc     dx              ;   insure sufficient size for buffer
                mov     ax,3100h        ; terminate but stay resident code = 0
                int     21h

;-------------------------------------------------------------------------------
; The following is initialization code and data that is overwritten by the data
; copied to the spy buffer when this buffer is initialized with a copy of the
; spied region.
;-------------------------------------------------------------------------------

spy_buffer:     ; this is where SPY will store a copy of the SPY region
                ;   to later check for any changes to the region

syntax          db      rt,lf,'SPY syntax:  SPY xxxx:yyyy L zzz',rt,lf,rt,lf
                db      'Where xxxx, and yyyy are hexadecimal numbers up to '
                db      'four digits in length and',rt,lf
                db      'zzz is a one to three digit hexadecimal number.  SPY '
                db      'will monitor the segment-',rt,lf
                db      'offset region xxxx:yyyy for a length of zzz bytes and '
                db      'report to the user with a',rt,lf
                db      'number of beeps equal to the number of bytes changed '
                db      'in that region if and when',rt,lf
                db      'any are changed.',rt,lf,'$'

progress        db      0       ; flags for progress of command line conversion

;-------------------------------------------------------------------------------
; This routine will convert an ASCII hex digit in al (either upper or lower case
; alpha hex) into a 1-of-16 binary value in al.
;-------------------------------------------------------------------------------

make_binary     proc    near            ; hex ASCII digit in al to binary in al
                cmp     al,'0'          ; less than "0", then not hex digit
                jb      not_digit
                cmp     al,'f'          ; greater than "f", then not hex digit
                ja      not_digit
                cmp     al,'9'          ; test for numeric digit
                ja      not_numeric     ; not a numeric digit
                sub     al,'0'          ; convert to binary
                ret
not_numeric:    cmp     al,'F'          ; over "F"?
                ja      low_case        ; yes, test for lower case
                cmp     al,'A'          ; less than "A"?
                jb      not_digit       ; yes, not hex digit
                sub     al,37h          ; convert to binary
                ret
low_case:       cmp     al,'a'          ; less than "a"?
                jb      not_digit       ; yes, not hex digit
                sub     al,57h          ; convert to binary
                ret
not_digit:      stc                     ; set carry flag if not hex digit
                ret
make_binary     endp

;-------------------------------------------------------------------------------
; This routine is called to allow the command line parse routines to ignore any
; space and tab characters on the command line that do not lie in hex numbers.
; If the return character at the end of the command line is encountered, then
; the carry flag is set; otherwise it is reset.
;-------------------------------------------------------------------------------

skip            proc    near            ; skip over spaces and tabs; signal
                                        ;   a RETURN with the carry flag
more_skip:      cmp     al,' '          ; is it a space?
                je      another         ; if so, get another
                cmp     al,9            ; is it a tab?
                je      another         ; if so, get another
                cmp     al,rt           ; is it a RETURN?
                je      a_rt            ; yes, set carry and return
                clc                     ; not one of these three so return
                ret                     ;   with carry clear
another:        lodsb                   ; get another character
                jmp     short more_skip ; and try again
a_rt:           stc                     ; return with carry set
                ret
skip            endp

;-------------------------------------------------------------------------------
; Here we parse the command line, which by the conditions of proper syntax has
; the three hexadecimal values for segment, offset and byte size for the region
; which is to be spied.  The routine between  cnv_more:  and  cnv_error:  is run
; once each to decode segment, offset and size which are stored respectively in
; the variables spy_seg, spy_off, and spy_size unless a syntax error is found.
; The variable progress is used to keep track of which of these three numbers
; has just been decoded.  A semicolon can be used to preface comments at the end
; of the command line.
;-------------------------------------------------------------------------------

convert         proc    near            ; convert hex ASCII to binary in ax
                mov     cx,16           ; for hex to binary conversion
cnv_more:       xor     bx,bx           ; accumulate result here
                xor     ah,ah           ; need this zero too
                dec     si              ; move back to last character
cycle:          lodsb                   ; get possible ASCII hex digit
                call    make_binary     ; if ASCII hex, then make binary in al
                jc      test_seg        ; if carry set, then char not hex digit
                xchg    ax,bx           ; accumulation in ax, last digit in bx
                mul     cx              ; bump to next power
                or      dx,dx           ; test for overflow
                jnz     cnv_error       ; result can't be larger than fff0h
                add     ax,bx           ; add in most recent digit
                xchg    ax,bx           ; accumulation in bx, ah = 0
                jmp     short cycle     ; continue conversion
cnv_error:      pop     ax              ; remove return location (of call)
                jmp     stax            ; display syntax and end
test_seg:       test    progress,ok_seg ; have we decoded the segment value?
                jnz     test_off        ; yes, test offset
                call    skip            ; skip over spaces and tabs
                jc      cnv_error       ; carry flag set if RETURN was found
                cmp     al,':'          ; syntax says must have colon here
                jne     cnv_error
                lodsb                   ; get another
                call    skip            ; skip over spaces and tabs
                jc      cnv_error       ; carry flag set if RETURN was found
                mov     spy_seg,bx      ; store value of segment for SPYing
                or      progress,ok_seg ; segment value is now decoded
                jmp     short cnv_more  ; continue to convert offset and size
test_off:       test    progress,ok_off ; have we decoded the offset value?
                jnz     test_size       ; yes, test size
                call    skip            ; skip over spaces and tabs
                jc      cnv_error       ; carry flag set if RETURN was found
                and     al,0dfh         ; convert "L" or "l" to "L"
                cmp     al,'L'          ; syntax says must have "l" or "L" here
                jne     cnv_error
                lodsb                   ; get another
                call    skip            ; skip over spaces and tabs
                jc      cnv_error       ; carry flag set if return was found
                mov     spy_off,bx      ; store value of offset for SPYing
                or      progress,ok_off ; offset value is now decoded
                jmp     short cnv_more  ; continue to convert size
test_size:      cmp     bx,max_size     ; size must not be over maximum
                ja      cnv_error
                call    skip            ; skip over spaces and tabs
        jnc comnt     ; shall allow comments at cmd line end
cnv_finished:   mov     spy_size,bx     ; store away size
                ret
comnt:    cmp al,';'      ; test for comment symbol
                jne     cnv_error       ; not comment symbol, then error
                jmp     short cnv_finished      ; all done
convert         endp

;-------------------------------------------------------------------------------
; This is the routine that contains the code that this program was designed to
; demonstrate.  It serves to detect if SPY has been loaded previously as a TSR.
; If it has, then the code segment at the time of the previous load will be put
; into es; if not, then es will return with the current value of cs.  The method
; used attempts to find the set of key values key1, key2, and key3 in the memory
; above the current PSP.  key1 is word cd20h which is the int 20h instruction
; found at offset 0 of every PSP.  If key1 matches, then key2 is tried and if it
; matches key3 is tried.  A triple match gives fair confidence that SPY has been
; run as a TSR once before.  It is even possible to gain further confidence or
; detect the presence of a different version of the same program by comparing
; a string in the returned es to a string in cs, say the copyright notice.
;-------------------------------------------------------------------------------

set_es          proc    near            ; es becomes cs of original SPY load
                mov     cx,cs           ; initialize count register
                dec     cx              ; start at the paragraph one above cs
not_yet:        mov     es,cx           ; trial value for es
                mov     ax,key1         ; have we a match for the first key?
                cmp     ax,es:key1
                je      test_2nd        ; yes, test the 2nd key
                loop    not_yet         ; no, not yet
                jmp     short none_found; no match for keys; no prior SPY load
test_2nd:       mov     ax,key2         ; have we a match for the second key?
                cmp     ax,es:key2
                je      test_3rd        ; yes, test the 3rd key
                loop    not_yet         ; no, not yet
                jmp     short none_found; no match for keys; no prior SPY load
test_3rd:       mov     ax,key3         ; have we a match for the third key?
                cmp     ax,es:key3
                je      match_found     ; yes, es now has cs of original load
                loop    not_yet         ; no, not yet
                jmp     short none_found; no match for keys; no prior SPY load
none_found:     mov     ax,cs           ; no prior load of SPY; use this cs
                mov     es,ax
                ret
match_found:    mov     resident,1      ; prior load; set resident flag
                ret
set_es          endp

;-------------------------------------------------------------------------------
; Execution starts here.  The command line is decoded, new parameters are passed
; to the prior load if it exists, and the beeps variable is set to zero for a
; fresh start.  Execution is then passed to  copy_spied:  and other termination
; code that can not be overwritten by the copy process.
;-------------------------------------------------------------------------------

start:          lea     dx,copyright    ; display copyright
                mov     ah,9
                int     21h

                mov     bx,80h          ; get command line byte count
                mov     al,[bx]
                cmp     al,1
                ja      parse           ; if command given, parse it
stax:           lea     dx,syntax       ; display command syntax
                mov     ah,9
                int     21h
                mov     ax,4c01h        ; and exit with error code 1
                int     21h

parse:          mov     si,82h          ; address first usable character
                lodsb
                call    skip            ; skip over spaces and tabs
                jc      stax            ; carry is set if RETURN was found
                call    convert         ; convert and store hex values
                call    set_es          ; set es to spy_buffer segment

                mov     ax,spy_seg      ; set SPY region to newly given values
                mov     es:spy_seg,ax   ;   (this isn't necessary for first
                mov     ax,spy_off      ;    time through, but it doesn't
                mov     es:spy_off,ax   ;    hurt either.)
                mov     ax,spy_size
                mov     es:spy_size,ax

                xor     ax,ax           ; initialize beeps count
                mov     es:beeps,ax

                jmp     copy_spied      ; copy that which is to be spied

code            ends
                end     begin

[ RETURN TO DIRECTORY ]