Date: Sunday, 1 January 1989  11:43-MST
From: <MULTI%TRIUMFCL.BITNET@CORNELLC.CIT.CORNELL.EDU>
Re:   TSR program that "unhooks" itself, see Issues 49,57 of 1988 digest

Title   U
Page    80,132
BUFLEN  =64                             ; Length of command save
;DBG    =1                              ; Define for printout
GUARD   =0                              ; Length of guard band
INTDOS  =21h*4                          ; Address of DOS vector
PSPOFF  =5Ch                            ; PSP above here overlaid
RELOC   =100h-PSPOFF                    ; Distance code shuffled
;
 .SFCOND                                ; Suppress false conditionals
;
Code    Segment
        ORG     100h
Assume  cs:code,ds:code
start   label   byte
count:  jmp     u                       ; Command being recalled
punt:   db      0EAh                    ; Far jump to DOS
chain   dw      2 dup(?)                ;  ...int 21h entry
;
begin:  cmp     ah,0Ah                  ; Buffered keyboard input request?
        jne     punt                    ;  ...no, pass to DOS
        push    ax
        push    bx
        push    cx
        push    dx
        push    si
        push    di
        push    es
        push    ds
        pop     es
        mov     di,dx                   ; DS:DI --> user's buffer
        inc     di                      ; Skip maximum  byte count
        inc     di                      ; Skip returned byte count
        mov     bx,dx                   ; Load into index register
        cld                             ; Strings advance forwards
retry:  mov     byte ptr [bx]+1,0       ; Zero returned byte count
;
loop:   call    in                      ; Read char into AL reg
loop1:  cmp     al,0Dh                  ; Entry process char in AL reg
        je      done
        or      al,al                   ; Function key?
        je      f_key                   ;  ...begins with null
        cmp     al,"H"-64               ; Backspace
        je      erase
        cmp     al,"U"-64               ; ^U
        je      erall
        cmp     al,27                   ; Escape
        je      erall
        mov     dx,[bx]                 ; Get both byte counts
        cmp     dh,dl                   ;  ...full if equal
        je      bell                    ;  ...ignore,bell if so
        stosb                           ; Save character
        call    echo                    ;  ...echo it
        cmp     byte ptr [di]-1,"C"-64  ; ^C character
        je      break                   ;  ...causes interrupt
        inc     byte ptr [bx]+1         ; End of checking, accept char.
        jmp     loop                    ;  ...back for more
;
f_key:  call    in                      ; AL contains 2nd char
        or      al,al                   ; Zero is break
        je      break
        cmp     al,61                   ; F3 = Function Recall
        je      recal
        cmp     al,72                   ; Cursor up,, same thing
        je      recal
        cmp     al,65                   ; F7 = Function Recall Backwards
        je      rbc
        cmp     al,80                   ; Cursor Down, same thing
        je      rbc
        cmp     al,64                   ; F6 = Control-Z
        je      ctrl_z
        jmp     loop                    ; Ignore the remainder
;
ctrl_z: mov     al,"Z"-64               ; F6 is Control-Z
        jmp     loop1                   ;  ...pretend from keyboard
;
recal:  jmp     recall
rbc:    jmp     rbck
;
break:  int     23h                     ; Control_Break
        call    crlf                    ;  ...as per documentation
        jnc     retry                   ;  ...carry clear, retry
        mov     ax,4C00h                ; Else do a kosher terminate
        int     21h                     ;  ...do not know his PSP
;
done:   stosb                           ; Success, save 0Dh terminator
        call    store                   ; Store the command
        pop     es                      ; Done, restore reg. and exit
        pop     di
        pop     si
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        iret
;
erase:  call    delete
        jmp     loop
;
erall:  call    delete
        cmp     Byte ptr [bx]+1,0
        jne     erall
        jmp     loop
;
bell:   mov     al,7
        call    out
        jmp     loop
;
recall: call    delete                  ; Attempt to delete char.
        cmp     Byte ptr [bx+1],0       ;  ...any left to delete
        jne     recall                  ;  ...yes, loop around
;
        push    ds                      ; Save user's DS
        push    bx                      ;  ...and BX reg
        push    cs                      ; Make DS the same
        pop     ds                      ;  ...as code seg
;
        xor     bx,bx                   ; Go to start of buffer
        mov     dx,word ptr count-RELOC ; DX is count of comands
        inc     word ptr count-RELOC    ; One more in stack
;
rec0:   dec     dx                      ; This command
        je      extrct                  ;  ...yes
;
        mov     cl,[save-RELOC+bx]      ; Get saved byte count
        mov     ch,0                    ;  ...make word
        jcxz    rec90                   ; End of commands
        inc     cx                      ;  ...skip past char count
        add     bx,cx                   ;  ...and string
        cmp     bx,BUFLEN               ; Buffer overflowed
        jae     rec90
        jmp     rec0                    ;  ...and continue

rec90:  xor     bx,bx                   ; Back to start of buffer
        mov     word ptr count-RELOC,2  ; Say one command there
extrct: mov     cl,[save-RELOC+bx]      ;  ...get char count
        mov     ch,0                    ;  ...be safe
        lea     si,[save+1-RELOC+bx]    ; ds:SI --> input buffer
        inc     bx                      ; Add in character counter
        add     bx,cx                   ; Length of line
        cmp     bx,BUFLEN               ;  ...will it fit?
        jae     rec90                   ;  ...no, so restart
        pop     bx
        cmp     cl,es:[bx]              ; Compare with new buffer
        jbe     fit                     ;  ...will fit
        mov     cl,es:[bx]              ;  ...copy what will fit
fit:    mov     es:[bx]+1,cl            ; Get saved byte count
        jcxz    nul                     ;  ...all done
        lea     di,[bx]+2               ; ES:DI --> user  buffer
;
l:      lodsb                           ; Load AL from input  buffer
        stosb                           ; Dump AL into output buffer
        call    echo                    ; Echo AL on   print  screen
        loop    l                       ;  ...loop until CX=0
;
nil:    pop     ds                      ; Restore user's DS
        jmp     loop                    ;  ...back for more
;
nul:    cmp     save-RELOC,0            ; Buffer really empty?
        je      nil                     ;  ...yes, nothing to recall
        push    bx                      ; Else save BX
        jmp     rec90                   ;  ...EOB, go back to start
;
rbck:   sub     word ptr cs:count-RELOC,2; Back two commands
        jle     toofar
bckr:   jmp     recall                  ;  ...and recall
toofar: push    ds                      ; Must scan for the
        push    bx                      ;  ...end of buffer
        push    cs                      ; Make DS the same
        pop     ds                      ;  ...as code seg.
        xor     bx,bx                   ; Beginning of buffer
        mov     word ptr count-RELOC,0  ; Start at command 0
bck1:   mov     cl,[save-RELOC+bx]      ; Get saved byte count
        mov     ch,0                    ;  ...be safe
        jcxz    bck90                   ; Not very nice, null string
        inc     cx                      ; Include char. count
        add     bx,cx                   ; Now point at next string
        cmp     bx,BUFLEN               ; Buffer overflowed
        jae     bck90                   ; Not very nice
        inc     word ptr count-RELOC    ; Another kosher stored command
        jmp     bck1
;
bck90:  cmp     word ptr count-RELOC,0  ; Too small?
        ja      bck91                   ;  ...no
        mov     word ptr count-RELOC,1  ; Must have one command
bck91:  pop     bx                      ; Got our answer
        pop     ds                      ;  ...restore
        jmp     bckr                    ; Go to find command routine
;
delete: cmp     Byte ptr [bx]+1,0       ; Anything to delete
        je      delret                  ;  ...no, then nop
        dec     di                      ; Back up pointer
        mov     al,[di]                 ;  ...AL has char
        dec     Byte ptr [bx]+1         ; One less in buffer
        mov     cx,1                    ; Assume takes one print pos.
        cmp     al," "                  ;  ...space and above OK
        jae     norm                    ;  ...blank out one position
        inc     cx                      ; Control char blank out two
;
norm:   mov     al,"H"-64               ; Backspace over char.
        call    out                     ;  ...print backspace
        mov     al," "                  ; Destructive overprint
        call    out                     ;  ...print space
        mov     al,"H"-64               ; Backspace to one before
        call    out                     ;  ...print backspace
        loop    norm                    ; Destroy one or two char.
delret: ret                             ;  ...all done
;
in:     mov     ah,7                    ; Read/no echo
        int     21h                     ; AL contains char
        ret                             ;  ...back to user
;
out:    push    dx                      ; Save register
        mov     dl,al                   ; Get character to print
        mov     ah,2                    ;  ...on print screen
        int     21h                     ;  ...do DOS call supposed
        pop     dx                      ;  ...restore reg.
        ret                             ;  ...to check for break
;
echo:   cmp     al,32                   ; Char in AL destroyed
        jae     nocntl                  ;  ...is space or above
        push    ax                      ; Else save a copy
        mov     al,"^"                  ;  ...print ^ arrow
        call    out                     ;  ...this way
        pop     ax                      ; Restore char in AL
        add     al,"A"-1                ;  ...map control to letter
nocntl: call    out                     ; Print the character
        ret                             ;  ...back to DOS
;

store:  mov     cl,[bx]+1               ; Char. count for new command
        mov     ch,0                    ;  ...null hi order
        jcxz    null                    ;  ...no command
        inc     cx                      ;  ...include Count in string
        cmp     cx,BUFLEN               ; String too long
        ja      null                    ;  ...yes, don't save
        push    cx                      ;  ...save a copy
        neg     cx                      ;  ...negate length
        lea     di,save+BUFLEN-RELOC-1  ; Source for shuffle
        lea     si,save+BUFLEN-RELOC-1  ; Target for shuffle
        add     si,cx                   ;  ...back up source
        add     cx,BUFLEN               ;  ...size of buffer to shuffle
        js      null                    ;  ...too big
        push    ds                      ; Save old DS segment
        push    cs
        pop     ds                      ; DS = CS
        push    cs
        pop     es                      ; ES = CS
        std                             ;  ...shuffle backwards
;
        rep     movsb                   ;  ...push old commands
        mov     word ptr count-RELOC,1  ;  ...reset buffer pointer
;
        pop     ds                      ; Restore data segment
        pop     cx                      ; Restore line length
        lea     si,[bx]+1               ; Get address of counted line
        lea     di,save-RELOC           ; ES:DI --> Save buffer
        cld                             ;  ...save forwards
        rep     movsb                   ;  ...save into local buffer
Ifdef   DEB
        call    debug                   ; *** DEBUG ***
endif
null:   ret                             ;  ...back to caller
;
crlf:   push    ax
        mov     al,13
        call    out
        mov     al,10
        call    out
        pop     ax
        ret
;
Ifdef   DEB
number: push    ax
        push    cx
        mov     cx,4
num:    rol     bp,1
        rol     bp,1
        rol     bp,1
        rol     bp,1
        mov     ax,bp
        and     ax,0Fh
        add     ax,"0"
        cmp     ax,"9"
        jbe     num1
        add     ax,"A"-"9"-1
num1:   call    out
        loop    num
        pop     cx
        pop     ax
        ret
;
debug:  push    ax
        push    bx
        push    ds
        push    cs
        pop     ds
        xor     bx,bx
deb0:   test    bx,63
        jne     deb1
        call    crlf
        mov     bp,cs
        call    number
        mov     al,":"
        call    out
        lea     bp,[save-RELOC+bx]
        call    number
        mov     al," "
        call    out
deb1:   mov     al,[save-RELOC+bx]
        inc     bx
        cmp     al," "
        jae     deb2
        mov     al,"."
deb2:   call    out
        cmp     bx,BUFLEN+GUARD         ; *** DEBUG ***
        jb      deb0
        pop     ds
        pop     bx
        pop     ax
        ret
Endif
;
impure  label   byte                    ; End of area to check for install
;
save    db      ?                       ; Token null for buffer
;
quit    label   byte                    ; End of permanent region
;
u:      xor     dx,dx                   ; Get vector segment
        mov     es,dx                   ;  ...and load into ES
        lds     si,dword ptr es:INTDOS  ; DS:SI --> DOS interrupt routine
        mov     cx,offset impure-begin  ;  ...this is pure code to compare
        push    cs                      ; Save code      segment
        pop     es                      ; ES same as code seg.
        lea     di,begin                ; ES:DI --> our interrupt service
        rep     cmpsb                   ; Are they the same?
        push    cs                      ; Set data segment same as
        pop     ds                      ; the code segment
        jz      remove                  ;  ...code compares, remove
;
        lea     si,start                ; DS:SI --> Code segment
        mov     di,PSPOFF               ; ES:DI --> Where relocates to
        mov     cx,offset quit-start    ; CX = bytes to relocate
        rep     movsb                   ;  ...shuffle section down
;
        mov     ds,dx                   ; Reload vector segment
        mov     si,INTDOS               ; DS:SI --> DOS  vector
        lea     di,chain-RELOC          ; ES:DI --> JMPF address
        movsw                           ; Move the offset
        movsw                           ; Move the segment
        push    cs                      ; Equivalence code and
        pop     ds                      ;  ...data segments
        mov     es,dx                   ; ES:DI --> DOS  vector
        mov     di,INTDOS
        lea     ax,begin-RELOC          ; AX is entry offset
        stosw                           ;  ...store as DOS vector
        mov     ax,cs                   ; CS is entry segment
        stosw                           ;  ...store as DOS vector
;
        mov     word ptr count-RELOC,1  ;  ...reset count
;
        lea     dx,msg1                 ; Say editor installed
        mov     ah,9h                   ;  ...print string
        int     21h                     ;  ...like so
;
        push    ds:2Ch                  ; Get environ. segment
        pop     es                      ;  ...into ES register
        mov     ah,49h                  ;  ...then free
        int     21h                     ;  ...the memory
;
        lea     dx,save+BUFLEN-RELOC    ; Last address to save
        int     27h                     ;  ...terminate, stay resident
;
remove: lea     dx,msg2                 ; Code already was installed
        mov     ah,9h                   ;  ...print string
        int     21h                     ;  ...is function
        xor     dx,dx                   ; Point to vector area
        mov     es,dx                   ;  ...to restore DOS
        lds     si,dword ptr es:INTDOS  ; Get saved dos interrupt
        mov     di,INTDOS+4             ; Where to restore old DOS entry
        std                             ;  ...copy backwards
        cmpsw                           ; Backspace SI and DI
        movsw                           ;  ...restore segment
        movsw                           ;  ...restore offset
        mov     bx,ds                   ; Get PSP of stored program
        dec     bx                      ; BX --> storage block
        mov     ds,bx                   ; DS --> storage block
        mov     word ptr ds:1,0         ; This deletes the PSP
        ret                             ;  ...back to caller
;
msg1:   db      "[Editor Installed]$"   ; Hooked to DOS, resident
msg2:   db      "[Editor Removed]$"     ; Unhooked, gone from memory
;
code    ends
;
End     count
