PAGE    60,132
TITLE   Ram-Resident Program Shell, Preliminary Version (Lesson 2)
SUBTTL  Date 02-02-87 -- Tutorial for Microsoft Forum  Version 0.03
;-----------------------------------------------------------------------------
;       Ram-Resident Program Shell
;
;
;The software, documentation and source code are:
;
;       (C) Copyright 1986, 1987
;       Chip Rabinowitz
;       All Rights Reserved
;
;       51 East Rogues Path
;       Huntington Station NY 11746
;       Assistant SysOp, Microsoft Forum, Compuserve PPN 76703,350
;
;
;COPYRIGHT NOTICE AND WARRANTY INFORMATION
;-----------------------------------------
;
;This document ("the source code"), other accompanying written and disk-based
;notes and specifications ("the documentation"), and all referenced and related
;program files accompanying the source code and the documentation ("the
;software") are copyrighted by Chip Rabinowitz.
;
;This program has been uploaded to Compuserve as part of a Tutorial in how
;to write Terminate-But-Stay-Resident programs, being conducted on
;Compuserve's Microsoft Special Interest Group during early 1987/
;
;This code (and all other code uploaded by the Copyright Holder as a
;part of this TSR Tutorial) may be used with the following restrictions:
;
;(1) If all or part of this code is used as part of a software package of
;ANY kind, the following acknowledgement must be used in the Documentation
;accompanying said package:
;
;       'Parts of the Resident Program Management for this Product are'
;       'Copyright (C) 1986, 1987 by Chip Rabinowitz, and are incorporated'
;       'into this product courtesy of the Ringmaster Development Team'
;
;(2) If all or part of this code is used as part of a software package that
;is placed in the public domain, no further restrictions apply.
;
;(3) If all or part of this code is used as part of a software package that
;is being distributed as 'shareware', a one-time-only donation of $10 will be
;accepted for the express purpose of continuing research into TSR standards.
;Note that this is a voluntary contribution, and should be sent to the
;Ringmaster Development Team at the address printed above.
;
;(4) If all or part of this code is used as part of a software package that
;will be distributed as a commercial product, a one-time-only payment of
;$25 will be accepted the express purpose of continuing research into TSR
;standards.  Note that in THIS CASE ONLY, this is not a voluntary
;contribution.  Commercial Developers are REQUIRED to make payment prior to
;incorporating these routines into their product.
;
;No copy of the source code may be distributed or given away without the
;accompanying documentation; and this notice must not be removed.
;
;There is no warranty of any kind associated with this software, and the
;copyright owner is not liable for damages of any kind.  By using this
;software, you agree to this.
;
;

XON     equ     11h
XOFF    equ     13h
TRUE    equ     1
FALSE   equ     0
CR      equ     0dh
LF      equ     0ah
TAB     equ     9


HOTKEYON        equ     01h     ;hot key pressed
SHIFTSON        equ     02h     ;shift states match
TSRACTIVE       equ     04h     ;tsr is running in foreground
INT28ACTIVE     equ     08h     ;INT28 routine is running in background

KB_DATA equ     60h
KB_CTL  equ     61h
KB_FLG1 equ     17h
KB_FLG2 equ     18h
BIOS_DATA       equ     40h

code segment para public 'CODE'
        org 100h
        assume  cs:code, ds:code, es:code
start:  jmp     begin

        db      1bh,'[2J'
cpyrt   db  'Ram-Resident Program Shell, Preliminary Version (Lesson 2)'
        db      CR,LF,LF
    db  'Copyright (C) 1986, 1987 Chip Rabinowitz'
        db      CR,LF,'All Rights Reserved',CR,LF,LF,'$'
hello   db      'Hello World!',CR,LF,'$'
bad_dos_msg     db      'Incompatible DOS version .... Aborting!',07h,CR,LF,'$'

tsr_parms:
        PSP     dw      ?       ;PSP of TSR
        OLDPSP  dw      ?       ;location to save current PSP
        DTA     dd      ?       ;DTA of TSR
        OLDDTA  dd      ?       ;location to save current DTA
        DSEG    dw      ?       ;TSR's Data Segment
        SSEG    dw      ?       ;TSR's Stack Segment ....
        SPTR    dw      0fffeh  ;.... and Stack Pointer
        ISSEG   dw      0       ;interrupted SS ...
        ISPTR   dw      0       ;..... and SP
        SHIFTST db      0       ;Shift State to Activate
        HOTKEY  db      0ffh    ;scan code to activate
        STATUS  dw      0       ;current status words
        POPUP   dd      0       ;far pointer to popup routine

intflags        db      0
ININT13         equ     04h     ;Bios disk interrupt
ININT21         equ     08h     ;DOS interrupt

dosversion      db      0       ;current version of DOS
waitcount       db      0       ;wait to activate count

indosflag       dd      0       ;Pointer to INDOS flag
doscriterr      dd      0       ;Pointer to DOS Critical Error Flag

dossseg         dw      0       ;pointer to dos stack segment
dossptr         dw      0       ;pointer to dos stack pointer
dossize         dw      0       ;dos stack size

dos21funcs      label   byte
        db      0,      0,      0,      0,      0,      0,      0,      0       ;0-7
        db      0,      0,      0,      0,      0,      0ffh,   0ffh,   0ffh    ;8-f
        db      0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0ffh    ;10-17
        db      0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0ffh    ;18-1f
        db      0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0,      0ffh    ;20-27
        db      0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0       ;28-2f
        db      0ffh,   0,      0,      0,      0,      0,      0ffh,   0ffh    ;30-37
        db      0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0ffh    ;38-3f
        db      0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0ffh    ;40-47
        db      0,      0,      0,      0,      0ffh,   0,      0ffh,   0ffh    ;48-4f **
        db      0,      0,      0,      0,      0ffh,   0,      0ffh,   0ffh    ;50-57
        db      0,      0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0,      0       ;58-5f
        db      0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0ffh,   0ffh    ;60-67

;
; ***************************************************************************
;       These are the bios interrupt vectors
;

BIOSI8  equ     08h             ;Bios Timer interrupt
BIOSI9  equ     09h             ;BIOS Hardware Keyboard interrupt
BIOSIB  equ     0Bh
BIOSIC  equ     0Ch
BIOSI10 equ     10h
BIOSI13 equ     13h             ;Bios Disk interrupt
BIOSI14 equ     14h
BIOSI15 equ     15h
BIOSI16 equ     16h             ;Bios Keyboard interrupt
BIOSI17 equ     17h
BIOSI1C equ     1Ch
DOSI21  equ     21h             ;DOS service router interrupt
DOSI28  equ     28h             ;DOS Idle interrupt
DOSI33  equ     33h             ;mouse interrupt
BIOSI1B equ     1Bh
DOSI23  equ     23h
DOSI24  equ     24h
;
oldint8         dd      0       ;BIOS Hardware Timer Interrupt
                db      BIOSI8
                dw      offset newint8  ;replacement vector
oldint9         dd      0       ;BIOS Hardware Keyboard Interrupt
                db      BIOSI9
                dw      offset newint9  ;replacement vector
oldint13        dd      0       ;BIOS Disk Interrupt
                db      BIOSI13
                dw      offset newint13 ;replacement vector
oldint16        dd      0       ;BIOS Software Keyboard Interrupt
                db      BIOSI16
                dw      offset newint16 ;replacement vector
oldint21        dd      0       ;DOS Services Interrupt
                db      DOSI21
                dw      offset newint21 ;replacement vector
oldint28        dd      0       ;DOS Idle Interrupt
                db      DOSI28
                dw      offset newint28 ;replacement vector
oldint1B        dd      0       ;control-C vector
                db      BIOSI1B
                dw      offset intret   ;replacement vector
oldint23        dd      0       ;control-C vector
                db      DOSI23
                dw      offset intret   ;replacement vector
oldint24        dd      0       ;critical error vector
                db      DOSI24
                dw      offset newint24 ;replacement vector

;
; ***************************************************************************
;       Function to restore interrupts.
;
;       If AX=0, this function does popup-interrupts (1B, 23 and 24)
;       If AX=anything else, this function does initial interrupts
;
restore_int proc near
        push    ds
        push    ax
        push    dx
        push    si
        push    di
        push    cx
        mov     di,7                    ;7 bytes per structure

        or      ax,ax
        jnz     restore1

        mov     cx,3                    ;three times through
        mov     si,offset oldint1B      ;start at table beginning
        jmp     restore_loop
restore1:
        mov     cx,06h                  ;six interrupts!
        mov     si,offset oldint8       ;top of table

restore_loop:
        push    cx
        push    di                      ;save structure size
        push    ds
        mov     al,4[si]                ;interrupt number
        mov     dx,[si]                 ;IP of interrupt vector
        mov     ds,2[si]                ;CS of interrupt vector
        mov     ah,25h                  ;set interrupt vector function
        int     21h
        pop     ds
        xor     ax,ax                   ;clear this entry from table
        mov     [si],ax                 ;so it shows not used
        mov     2[si],ax
        pop     di                      ;get size back
        add     si,di                   ;do it again
        pop     cx
        loop    restore_loop

        pop     cx
        pop     di
        pop     si
        pop     dx
        pop     ax
        pop     ds
        ret
restore_int endp
;
; ***************************************************************************
;       Function to set interrupts.
;
;       If AX=0, this function does popup-interrupts (1B, 23 and 24)
;       If AX=anything else, this function does initial interrupts
;
setup_int proc near
        push    es
        push    ax
        push    bx
        push    dx
        push    di
        push    si
        push    cx
        mov     di,7                    ;7 bytes per structure

        or      ax,ax
        jnz     setup1

        mov     cx,3                    ;three times through
        mov     si,offset oldint1B      ;start at table beginning
        jmp     setup_loop
setup1:
        mov     cx,06h                  ;six interrupts!
        mov     si,offset oldint8       ;top of table

setup_loop:
        push    cx
        push    di                      ;save structure size
;       push    ds

        mov     ax,[si]                 ;check for value
        or      ax,ax                   ;if not zero, next one
        jnz     setup2
        mov     al,4[si]                ;interrupt number
        mov     ah,35h                  ;get interrupt vector function

        int     21h
;       pop     ds
        mov     [si],bx                 ;save IP
        mov     2[si],es                ;save CS

        mov     al,4[si]                ;interrupt number
                                        ;DS already contains correct segment
        mov     dx,5[si]                ;IP of interrupt vector
        mov     ah,25h                  ;set interrupt vector function
        int     21h

setup2:
        pop     di                      ;get size back
        add     si,di                   ;do it again
        pop     cx
        loop    setup_loop
ex_set:
        pop     cx
        pop     si
        pop     di
        pop     dx
        pop     bx
        pop     ax
        pop     es
        ret
setup_int endp

setdta  macro   ddptr
        push    ds
        mov     ax,1a00h        ;Function used to get current DTA address }
        mov     dx,word ptr ddptr       ;Offset of DTA returned }
        mov     ds,word ptr ddptr+2     ;Segment of DTA returned by DOS }
        int     21h             ;Execute MSDos function request }
        pop     ds
        endm

getdta  macro   ddptr
        push    es
        mov     ax,2f00h        ;Function used to get current DTA address }
        int     21h             ;Execute MSDos function request }
        mov     word ptr ddptr+2,es             ;Segment of DTA returned by DOS }
        mov     word ptr ddptr,bx               ;Offset of DTA returned }
        pop     es
        endm

;
; ***************************************************************************
;       S E T   P S P
;
;       A bug in DOS 2.0, 2.1, causes DOS to clobber its standard stack   }
;       Then the PSP get/set functions are issued at the DOS prompt. The  }
;       following checks are made, forcing DOS to use the "critical"      }
;       stack when the TSR enters at the INDOS level.                     }
;
;               BX contains the PSP segment to set
;
setpsp  proc    near
        call    checkindos              ;If Version < 3, and INDOS,...
        jz      do_set_psp
        mov     byte ptr es:[di],0ffh   ;set critical flag


do_set_psp:
        mov     ax,5000h                ;set PSP* function
                                        ;BX already holds segment
        int 21h                         ;DOS function request

        call    checkindos              ;If Version < 3, and INDOS,...
        jz      set_out
        mov     byte ptr es:[di],0h     ;set critical flag

set_out:
        ret

setpsp  endp

;
; ***************************************************************************
;       G E T   P S P
;
;       A bug in DOS 2.0, 2.1, causes DOS to clobber its standard stack   }
;       Then the PSP get/set functions are issued at the DOS prompt. The  }
;       following checks are made, forcing DOS to use the "critical"      }
;       stack when the TSR enters at the INDOS level.                     }
;
;               The PSP Segment is returned in BX
;
getpsp  proc    near
        call    checkindos              ;If Version < 3, and INDOS,...
        jz      do_get_psp
        mov     byte ptr es:[di],0ffh   ;set critical flag

do_get_psp:
        mov     ax,5100h                ;get PSP function
        int 21h                         ;DOS function request
                                        ;BX now holds PSP segment
        call    checkindos              ;If Version < 3, and INDOS,...
        jz      get_out
        mov     byte ptr es:[di],0h     ;clear critical flag

get_out:
        ret

getpsp  endp

;
; ***************************************************************************
;       This routine checks the version of DOS in use, and returns the
;       ZF=0 (Not zero) if version < 3.0, and INDOS is set.  Internal function
;       is called ONLY by getpsp and setpsp.
;
checkindos proc near
        mov     al,dosversion
        cmp     al,3                    ;If Version is less than 3.0 ...
        jge     ok_ret
        mov     es,word ptr indosflag+2 ; ... and ...
        mov     di,word ptr indosflag
        mov     al,byte ptr es:[di]
        or      al,al                   ;INDOS is set ...
        jz      ok_ret
        mov     es,word ptr doscriterr+2; ... then ...
        mov     di,word ptr doscriterr
        jmp     fixit                   ;exit with not zero
ok_ret:
        xor     al,al                   ;set zero flag
fixit:
        ret

checkindos endp

;
; ***************************************************************************
;       Internal routine to establish addressibility of the data segment
;
dds proc near
        push    cs
        pop     ds
        ret
dds endp

newint9 proc far

;NOTE:  None of this pusing/popping is really necessary for THIS particular
;TSR, so I;ve commented it out.
;
;       pushf
;       cli
;       push    ds
;       push    es
;       push    ax
;       push    bx
;       push    cx
;       push    dx
;       push    di
;       push    si

;       push    cs
;       pop     ds

;internal INT9 processing here

;getkey_out:
;       pop     si
;       pop     di
;       pop     dx
;       pop     cx
;       pop     bx
;       pop     ax
;       pop     es
;       pop     ds
;out_9:
;       popf

;       cli
        jmp     dword ptr cs:oldint9


;
; ***************************************************************************
;       This is the int return (IRET) dummy pointer
;
intret:
        iret

;This routine will be used at some future point ... commented out for now.
;
;ignore_9:
;       in      al,60h
;       push    ax
;       pop     ax
;       in      al,61h
;       mov     ah,al
;       or      al,80h
;       out     61h,al
;       xchg    ah,al
;       out     61h,al
;       mov     al,20h
;       out     20h,al
;       pop     ax
;       jmp     short out_9

newint9 endp

new_bios_flag   db      0

newint16 proc far
start_again:
        cmp     ah,00           ;if char request,
        je      func00          ;loop for character
        cmp     ah,01           ;if character availability test
        je      func01          ;go check for char
        push    ax
        and     ah,10h          ;test for extra bios
        pop     ax
        jz      gobios16
        mov     cs:new_bios_flag,10h
        and     ah,3
        jmp     short start_again
gobios16:
        or      ah,cs:new_bios_flag
        jmp     dword ptr cs:[oldint16]
                                ;go to bios interrupt 16
func01:
        push    ds
        push    si
        push    di
        call    dds
func01A:
        call    keystat         ;look at key buffer
        pushf                   ;save return flags
        jz      fret01          ;return if no key
        call    hotcheck        ;test for hot key
        jnz     fret01          ;not there
        popf                    ;flags back for loop
        jmp     func01A         ;it was a hot key -- skip it!
fret01:
        mov     new_bios_flag,0
        popf                    ;flags back
        pop     di
        pop     si
        pop     ds
        ret     2               ;return to user
func00:
        push    ds
        push    si
        push    di
        call    dds
func00A:
        call    keystat         ;wait until character available
        jz      func00A         ;loop it
        call    hotcheck        ;test for a hot key
        jz      func00A         ;it sure was -- start loop again

b_func0:
        mov     ah,0            ;get the next user key
        or      ah,new_bios_flag
        pushf                   ;simulate it ...
        call    dword ptr cs:oldint16   ;do bios

func_out:
        pushf                   ;save return flags
        jmp     fret01          ;restore and return

newint16 endp

;
;       call the background task if no key is available
;
keystat proc near
                                ;preserves all registers except
                                ;AX,SI,DI,DS & flags
        push    cx
        push    bx

        push    ax
        push    ds
        cld

        mov     ax,BIOS_DATA
        mov     ds,ax           ;set data segment
        mov     si,KB_FLG1      ;set source

        lodsw                   ;get the high and low bytes
        pop     ds
        mov     bx,ax           ;save it away
        pop     ax


        mov     al,SHIFTST      ;shift state check

        or      al,al           ;is it zero?
        jz      sim_16          ;yes -- set flag for simulation

        push    bx              ;save current flags
        and     bl,al           ;mask bits
        cmp     al,bl           ;are we the same?
        pop     bx
        jne     skip_16A        ;nope -- next one
sim_16:
        or      word ptr STATUS,SHIFTSON        ;set the flag

        mov     al,HOTKEY       ;is this control block the last one?

        or      al,al
        jnz     short skip_16
        or      word ptr STATUS,HOTKEYON        ;turn on hotkey flag
        jmp     short skip_16
skip_16A:
        and     word ptr STATUS,NOT SHIFTSON    ;set the flag

skip_16:
        mov     ah,1                    ;see if character is available
        or      ah,new_bios_flag
        pushf                           ;simulate interrupt
        call    dword ptr cs:oldint16

        jnz     exit_161                ;got one

chkdos_161:
        cld
        push    ds                      ;check if dos critical error in effect
        push    si
        lds     si,cs:doscriterr        ;zero says dos is interruptable
        lodsb                           ;$ff  says dos is in a critical state
        lds     si,cs:indosflag         ;if indos then int $28 issued by dos
        or      al,[si]                 ;so we dont have to.
                                        ;account for active interrupts
        or      al,cs:intflags          ;any flags says we dont issue call
        pop     si                      ;to the background.
        pop     ds
        cmp     al,01                   ;must be indos flag only
        jg      skip28                  ;dos cannot take an interrupt yet
        int     28h                     ;call dos idle function (background dispatch).
skip28:
        xor     ax,ax                   ;show  no keycode available
exit_161:
        pop     bx
        pop     cx
        ret

keystat endp

hotcheck proc near      ;DI points to TSR Block character is from
                        ;SI holds ID of TSR routine

        push    cx
        push    ax              ;save character
        push    di

        mov     al,HOTKEY

        or      al,al
        jz      only_shift      ;special case for shift-state only

        cmp     al,ah           ;do scan codes match?
        jne     again_16H

only_shift:
        push    ax
        mov     ax,STATUS       ;get TSR status flags
        test    ax,SHIFTSON     ;are shift codes set?
        jz      again_16H1      ;nope -- try next one

        or      ax,HOTKEYON     ;turn on hotkey flag
        mov     STATUS,ax       ;put new flags back

        pop     ax
        or      al,al           ;test for special case
        pop     di              ;get back Block pointer
        pop     ax              ;throwing this away

        jz      hot_out         ;special case for flags only

b_func1:
        mov     ah,0            ;get rid of the character
        or      ah,new_bios_flag
        pushf                   ;simulate interrupt
        call    dword ptr cs:oldint16   ;do bios

hot_out:
        xor     ax,ax           ;set zero flag
        jmp     got_hot

again_16H1:
        pop     ax              ;get rid of flag
again_16H:
        mov     al,0ffh
        or      al,al           ;clear zero flag if set
        pop     di
        pop     ax              ;get back keystroke
got_hot:
        pop     cx              ;get back cx register
        ret

hotcheck endp

newint8 proc far

        pushf
        push    ds
        push    di
        push    cx                      ;save regs
        push    ax
        call    dds

        mov     ax,STATUS
        test    ax,HOTKEYON     ;is hot-key flag set?
        jz      again_8_2
        test    ax,TSRACTIVE    ;are we already running?
        jnz     again_8_2       ;yep -- next one

wait_8:
        cmp     waitcount,0     ;if waiting, check time
        jnz     wait2_8         ;decrement count

chkdos_8:
        cld
        push    ds                      ;check if dos critical error in effect
        push    si
        lds     si,cs:doscriterr        ;zero says dos is interruptable
        lodsb                           ;$ff  says dos is in a critical state
        lds     si,cs:indosflag         ;add in second status byte
        or      al,[si]
        or      al,cs:intflags          ;any flags says we're busy
        pop     si
        pop     ds
        jnz     wait1_8                 ;we're busy -- set count & EXIT
        call    dopopup                 ;call the popup stuff
        jmp     end_8                   ;and out we go
wait1_8:
        mov     waitcount,10h           ;set the count
wait2_8:
        dec     waitcount
        jz      chkdos_8

again_8_2:
end_8:
        pushf                           ;simulate interrupt ...
        call dword ptr oldint8          ;do the original

        pop     ax              ;get back registers
        pop     cx
        pop     di
        pop     ds
        popf

exit_8:
        iret

newint8 endp

newint28 proc far

        pushf                           ;simulate interrupt ...
        call dword ptr cs:oldint28      ;do the original

        push    ds
        push    di
        push    cx                      ;save regs
        push    ax

        push    cs
        pop     ds

again_28:
        mov     ax,STATUS       ;get TSR status flags
        test    ax,HOTKEYON     ;is hot-key flag set?
        jz      again_28_2
        test    ax,TSRACTIVE    ;are we already running?
        jnz     again_28_2      ;yep -- next one

chkdos_28:
        cld
        push    ds                      ;check if dos critical error in effect
        push    si
        lds     si,cs:doscriterr        ;zero says dos is interruptable
        lodsb                           ;$ff  says dos is in a critical state
        or      al,cs:intflags          ;any flags says we're busy
                                        ;NOTE:  Indos flag is always set here!
                                        ; ... so don't have to check it!
        pop     si
        pop     ds
        jnz     end_28                  ;we're busy-EXIT & try again next time

        mov     waitcount,0             ;clear waiting count and ...
        call    dopopup                 ;... call the popup stuff
        jmp     end_28                  ;and out we go

again_28_2:
end_28:
        pop     ax              ;get back registers
        pop     cx
        pop     di
        pop     ds

exit_28:
        iret

newint28 endp

di28    dw      0
ds28    dw      0

dopopup proc near

        cli                     ;no interrupts now!!!

        or      word ptr STATUS,TSRACTIVE       ;set active bit

        mov     ISSEG,ss                ;save user's stack segment
        mov     ISPTR,sp                ;... and stack pointer
                                        ;DS was saved in INT8 or INT28

        mov     ss,SSEG         ;load TSR stack segment ...
        mov     sp,SPTR         ;... and stack pointer

        push    bp                      ;save all registers except for
        push    bx                      ;...those saved by calling routine,
        push    dx                      ;...DS,DI,CX,AX are restored on exit
        push    si                      ;...from this procedure
        push    es

        mov     [di28],di               ;save TSR pointer until after saving
        mov     [ds28],ds               ;...Indos stack

        mov     di,sp                   ;destination is our stack
        dec     di
        dec     di                      ;back off current word
        mov     ax,ss                   ;move stack segment
        mov     es,ax                   ;...to extra segment

        mov     si,cs:dossptr           ;source is DOS Indos Primary Stack
        mov     ds,cs:dossseg           ;...and segment, of course
        dec     si                      ;point to last word on DOS stack
        dec     si

        mov     cx,40h                  ;stack size to save

        mov     ax,sp                   ;save current stack pointer ...
        sub     ax,cx                   ;...and make room to avoid
        sub     ax,cx                   ;...overwriting during REP
        mov     sp,ax                   ;...and put it back

        std                             ;set the direction flag ...
        rep     movsw                   ;and move the 40 word stack
        mov     sp,di
        cld                             ;direction flag back

        mov     di,cs:di28              ;get TSR pointer back
        mov     ds,cs:ds28

        sti                             ;interrupts back on!!

        getdta  OLDDTA                  ;save current DTA

        call    getpsp                  ;get current PSP segment
        mov     OLDPSP,bx               ;...returned in BX

        mov     bx,PSP                  ;set TSR's PSP in BX
        call    setpsp                  ;...and do it

        setdta  DTA

        xor     ax,ax
        call    setup_int               ;replace with our handler

        push    ds                      ;save DS:DI for after popup
        push    di
        mov     ds,DSEG         ;load user's data segment


        call    dword ptr cs:POPUP      ;call the user routine!!


        pop     di                      ;restore ds:di for addressibility
        pop     ds

        mov     bx,OLDPSP               ;replace interrupted PSP
        call    setpsp

        setdta  OLDDTA          ;restore interrupted DTA

        xor     ax,ax
        call    restore_int             ;restore interrupts as well

;terminate checking here!

        cli                             ;no interrupts while manipulating stack

        mov     cx,40h                  ;stack size here

        mov     di28,di                 ;save TSR pointer until after saving
        mov     ds28,ds                 ;...Indos stack

        mov     ax,ss                   ;source is current stack
        mov     ds,ax
        mov     si,sp                   ;point to last word on our stack
        inc     si
        inc     si

        mov     es,cs:dossseg           ;destination is dos stack
        mov     di,cs:dossptr
        sub     di,cx                   ;point backward to last used word
        sub     di,cx                   ;...on dos stack
        cld
        rep     movsw

        mov     sp,si                   ;skip over moved words

        mov     di,cs:[di28]            ;get TSR pointer back
        mov     ds,cs:[ds28]

        pop     es
        pop     si
        pop     dx
        pop     bx
        pop     bp

;
; ***************************************************************************
;
;       CAUTION!!!! THE USER ROUTINE MUST RETURN SS:SP WITH THE SAME
;               VALUES AS ON ENTRY.  OTHER REGISTERS WILL BE RESTORED
;               BY THIS ROUTINE.
;
; ***************************************************************************
;
back_popup:

        mov     ss,ISSEG                ;restore user's stack segment
        mov     sp,ISPTR                ;... and stack pointer
                                        ;DS will be restored in INT8 or INT28
        and     STATUS,NOT HOTKEYON+TSRACTIVE
                                        ;clear hot-key and inuse bits

        sti                             ;enable interrupts again
dopopup_end:
        ret

dopopup endp

newint13 proc far

        pushf
        or      cs:intflags,ININT13
        popf                    ;get back original flags

        pushf                           ;simulate interrupt ...
        call dword ptr cs:oldint13      ;do the original

        pushf
        and     cs:intflags,NOT ININT13
        popf

        ret     2

newint13 endp

newint21 proc far
        pushf
        sti                             ;allow interrupts

        cmp     ah,63h                  ;max number
        jg      notme
        push    ax                      ;some INT21 functions must be left
        push    bx                      ;...alone.  They either never return,
        mov     bx,offset dos21funcs    ;...grab parameters from the stack
        mov     al,ah                   ;...or can be interrupted.
        xlat    cs:dos21funcs           ;test function against table
        or      al,al                   ;wait for functions marked zero
        pop     bx                      ;these ,ust be left alone
        pop     ax
        jz      notme                   ;jump to original INT21
        jmp     short set_21
notme:
        popf
        jmp     dword ptr cs:oldint21

set_21:
        or      cs:intflags,ININT21     ;say int21 is active
        popf                            ;get original flags back
        pushf                           ;save flags and all regs that

        call    dword ptr cs:oldint21   ;do the interrupt
        sti

        pushf                           ;save return registers
        and     cs:intflags,NOT ININT21 ;not active any more
        popf                            ;they're back

        ret     2                       ;return with flags set

newint21 endp

error24 dw      0

newint24 proc far
        pushf
        mov     cs:error24,di           ;save the error
        mov     al,0
        iret
newint24 endp

pop_routine proc far    ;just prints a msg and gets out
        lea     dx,hello
        mov     ah,9
        int     21h             ; display copyright notice
        ret
pop_routine endp


TSR_stack       db      512 dup(0)
TSR_stack_end:

res_part:       ; this is the end of my resident code so I can tell DOS
                ; how big I am
;
;
; ***************************************************************************
;
;       Initialization Code -- load routine and point new int14
;
second_time     db      0       ;flag used to test for odd-byte check

begin:
        lea     dx,cpyrt
        mov     ah,9
        int     21h             ; display copyright notice

        call    getpsp          ;Current PSP returned in BX
        mov     PSP,bx
        getdta  DTA             ;macro to get Disk Transfer Address

        mov     ax,ds
        mov     DSEG,ax         ;save it
        mov     SSEG,ax         ;save it here too!
        mov     word ptr POPUP+2,ax     ;save it here three!
        mov     ax,offset TSR_stack_end
        sub     ax,2
        mov     SPTR,ax

        mov     SHIFTST,08h     ;ALT-Key + ...
        mov     HOTKEY,2dh      ;...'X' to pop up

        mov     word ptr POPUP,offset pop_routine

        mov     ax,3400h                ;find the DOS Indos Byte
        int     21h
;IF SOMEBODY HAS AT&T DOS 2.11, would you please try something?
;
;Move the label 'crit2' to the second location (where it is currently)
;commented out, and see how it works.  There is still some dissension
;about where the critical byte actually is in that version of DOS!

crit2:
        mov     word ptr indosflag,bx   ;offset returned in BX
        mov     word ptr indosflag+2,es ;segment returned in ES
        mov     dossseg,es              ;this is also the DOS stack segment
        mov     word ptr doscriterr+2,es;as well as the segment for the critical flag
;crit2:
        mov     cx,2000h                ;search for instruction: CMP [crit flag],00
        mov     ax,3e80h                ;3e80 is opcode to search for
        mov     di,bx                   ;start search here
crit3:
        repnz   scasw                   ;search until a match is found
        jnz     no_crit_found           ;couldn't find critical byte
                                        ;we're going to search for the
                                        ;address of the critical flag
                                        ;es:[di-2] now points to:
                                        ;       CMP [crit flag],00
                                        ;       JNZ ...
                                        ;       MOV SP,indos stack address
        mov     al,byte ptr es:[di+5]   ;MOV SP,xxxx
        cmp     al,0bch                 ;opcode for MOV SP
        jne     crit3                   ;bad place.  Sorry
        mov     ax,word ptr es:[di]     ;here's the critical byte address
        mov     word ptr doscriterr,ax
        mov     ax,word ptr es:[di+6]   ;...and here's the stack pointer
        mov     word ptr dossptr,ax
        mov     ax,1
        jmp     no_error
no_crit_found:
        mov     al,second_time          ;did we already try odd bytes?
        or      al,al
        jz      crit_again              ;nope ...
        jmp     bad_dos_version         ;we tried!
crit_again:
        mov     second_time,1           ;set the flag
        inc     bx                      ;increment pointer (needed for AT&T
        jmp     crit2                   ;DOS 2.11 and some others

no_error:
        mov     ax,1
        call    setup_int

        lea     dx,res_part     ; load offset of top of code
        add     dx,15           ;round up
        shr     dx,1
        shr     dx,1
        shr     dx,1
        shr     dx,1            ; divide by 16 to get paragraphs
        xor     al,al           ; no errors
        mov     ah,31h          ; load terminate & stay resident code
        int     21h

bad_dos_version:
        lea     dx,bad_dos_msg
        mov     ah,9
        int     21h             ; display error message
        mov     ax,4cffh                        ;exit with -1 return code
        int     21h

code    ends

        end     start
