home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 10 Tools
/
10-Tools.zip
/
mass61.zip
/
mass.zip
/
masm61
/
DISK3
/
SAMPLES
/
TSR
/
HANDLERS.AS$
/
HANDLERS
Wrap
Text File
|
1992-11-12
|
52KB
|
1,284 lines
.MODEL small, pascal
INCLUDE tsr.inc
.CODE
; Stack buffer used by TSR. Size is determined by constant STACK_SIZ,
; declared in TSR.INC file. NewStack points to top of stack.
EVEN
BYTE STACK_SIZ DUP(?) ; Stack buffer
NewStack LABEL BYTE ; Pointer to top of stack
; Structures for interrupt handlers or "interrupt service routines."
; The following handlers are replaced during installation. Such routines
; usually set a flag to indicate they are active, call the original
; interrupt handler, optionally do some processing (such as detecting a
; hot key), and when finished clear the active flag.
HandArray LABEL BYTE ; Array of handler structures
; Num Flag OldHand NewHand
intClock INTR < 8h, FALSE, NULL, Clock>
intKeybrd INTR < 9h, FALSE, NULL, Keybrd>
intVideo INTR <10h, FALSE, NULL, Video>
intDiskIO INTR <13h, FALSE, NULL, DiskIO>
intMisc INTR <15h, FALSE, NULL, SkipMiscServ>
intIdle INTR <28h, FALSE, NULL, Idle>
intMultex INTR <2Fh, FALSE, NULL, Multiplex>
CHAND EQU ($ - HandArray) / (SIZEOF INTR) ; Number of handlers in array
; Interrupt trap routines. These interrupt routines are set up
; temporarily to trap keyboard break errors and critical errors
; while the TSR is active. When the TSR finishes its tasks, it
; restores the old interrupts before returning.
TrapArray LABEL BYTE ; Array of trap structures
; Num Flag OldHand NewHand
intCtrlBk INTR <1Bh, FALSE, NULL, CtrlBreak>
intCtrlC INTR <23h, FALSE, NULL, CtrlC>
intCritEr INTR <24h, FALSE, NULL, CritError>
CTRAP EQU ($ - TrapArray) / (SIZEOF INTR) ; Number of traps in array
; Address of application's stack. Before calling the main body of the TSR,
; the Activate procedure stores the application's stack address, then resets
; SS:SP to point to LABEL NewStack (see above). This gives the TSR its own
; stack space without making demands on the current stack. Activate restores
; the application's stack before returning.
OldStackAddr FPVOID ? ; SS:SP pointer to application stack
; The TSR must set up its own disk transfer area if it calls DOS functions
; that use the DTA (see section "Preserving Existing Data" in Chapter 11 of
; the Programmer's Guide). DTA_SIZ is defined in the TSR.INC include file.
IFDEF DTA_SIZ
OldDtaAddr FPVOID ? ; Address of application's DTA
DtaBuff BYTE DTA_SIZ DUP(?) ; DTA buffer
ENDIF
; Multiplex data. STR_LEN is defined in the TSR.INC include file
IDnumber BYTE 0 ; TSR's identity number
IDstring BYTE STR_LEN DUP (0) ; Copy of identifier string
IDstrlen WORD ? ; Length of identifier string
ShareAddr FPVOID ? ; Address of shared memory
; Miscellaneous data
TsrRequestFlag BYTE FALSE ; Flag set when hot key is pressed
TsrActiveFlag BYTE FALSE ; Flag set when TSR executes
BreakCheckFlag BYTE ? ; Break-checking status of application
TsrPspSeg WORD ? ; Segment address of PSP
TsrAddr FPVOID ? ; Pointer to main part of TSR
CritErrAddr FPVOID ? ; Pointer to MS-DOS critical error flag
InDosAddr FPVOID ? ; Pointer to MS-DOS InDos flag
; Scan and shift codes for hot key. Install procedure initializes
; HotScan, HotShift, and HotMask during installation.
HotScan BYTE ? ; Scan code hot key
HotShift BYTE ? ; Shift value of hot key
HotMask BYTE ? ; Mask unwanted shift values
Version LABEL WORD ; DOS version number
minor BYTE ?
major BYTE ?
; Timer data, used when the TSR is activated at a preset time instead
; of activated from the keyboard. The following variables serve the
; same purposes as the counter variables used in the ALARM.ASM program
; presented in Chapter 11 of the Programmer's Guide. Refer to the
; header comments in the Install procedure for an explanation of how
; to set up a time-activated TSR.
Tick91 BYTE 91 ; Measures 91 timer ticks (5 seconds)
CountDown WORD 0 ; Counts 5-second intervals
;* Clock - Interrupt handler for Interrupt 08 (timer). Executes at each
;* timer interrupt; these occur an average of 18.2 times per second. Clock
;* first allows the original timer service routine to execute. It then
;* checks the flag TsrRequestFlag maintained either by the keyboard handler
;* (if keyboard activated) or by this procedure (if time activated). If
;* TsrRequestFlag = TRUE and system is okay, Clock invokes the TSR by
;* calling the Activate procedure. Uses an active flag to prevent the
;* Clock procedure from being reentered while executing.
;*
;* Uses: intClock, TsrActiveFlag, CountDown
;*
;* Params: None
;*
;* Return: None
Clock PROC FAR
pushf ; Simulate interrupt by pushing flags,
call cs:intClock.OldHand ; far calling orig Int 08 routine
.IF cs:intClock.Flag == FALSE ; If not already in this handler:
mov cs:intClock.Flag, TRUE ; Set active flag
sti ; Interrupts are okay
push ds ; Save application's DS
push cs
pop ds ; Set DS to resident code segment
ASSUME ds:@code
call CheckRequest ; Check conditions
.IF !carry? ; If TSR requested and safe,
mov TsrActiveFlag, TRUE ; activate TSR
call Activate
mov TsrActiveFlag, FALSE
.ENDIF ; End carry flag check
cmp CountDown, 0 ; If CountDown = 0, TSR is not time
je ticked ; activated or has already executed
dec Tick91 ; Else count down 91 timer ticks
jnz ticked ; If 91 ticks have not elapsed, exit
mov Tick91, 91 ; Else reset secondary counter and
dec CountDown ; subract one 5-second interval
ja ticked ; If counter not yet drained, exit
mov TsrRequestFlag, TRUE ; Else raise request flag
ticked:
mov intClock.Flag, FALSE ; Clear active flag
pop ds ; Recover application's DS
ASSUME ds:NOTHING
.ENDIF ; End in-handler check
iret
Clock ENDP
;* Keybrd - Interrupt handler for Interrupt 09 (keyboard).
;*
;* IBM PC/AT and compatibles:
;* Gets the scan code of the current keystroke from port 60h. Then
;* compares the scan code and shift state to the hot key. If they
;* match, sets TsrRequestFlag to signal the handlers Clock and Idle
;* that the TSR is requested.
;*
;* IBM PS/2 series:
;* Only the instructions at KeybrdMonitor (see below) are installed
;* as Interrupt 09 handler, since above method should not be used to
;* determine current keystroke in IBM PS/2 series. In this case, the
;* Interrupt 15h handler MiscServ takes care of checking the scan codes
;* and setting the request flag when the hot key is pressed.
;*
;* Time-activated TSRs:
;* If the TSR is activated by time instead of by a hotkey, KeybrdMonitor
;* serves as the Interrupt 09 handler for both PC/AT and PS/2 systems.
;*
;* Uses: intKeybrd, TsrRequestFlag
;*
;* Params: None
;*
;* Return: None
Keybrd PROC FAR
sti ; Interrupts are okay
push ax ; Save AX register
in al, 60h ; AL = scan code of current key
call CheckHotKey ; Check for hot key
.IF !carry? ; If not hot key:
; Hot key pressed. Reset the keyboard to throw away keystroke.
cli ; Disable interrupts while resetting
in al, 61h ; Get current port 61h state
or al, 10000000y ; Turn on bit 7 to signal clear keybrd
out 61h, al ; Send to port
and al, 01111111y ; Turn off bit 7 to signal break
out 61h, al ; Send to port
mov al, 20h ; Reset interrupt controller
out 20h, al
sti ; Reenable interrupts
pop ax ; Recover AX
mov cs:TsrRequestFlag, TRUE ; Raise request flag
iret ; Exit interrupt handler
.ENDIF ; End hot-key check
; No hot key was pressed, so let normal Int 09 service routine take over
pop ax ; Recover AX and fall through
cli ; Interrupts cleared for service
KeybrdMonitor LABEL FAR ; Installed as Int 09 handler for
; PS/2 or for time-activated TSR
mov cs:intKeybrd.Flag, TRUE ; Signal that interrupt is busy
pushf ; Simulate interrupt by pushing flags,
call cs:intKeybrd.OldHand ; far calling old Int 09 routine
mov cs:intKeybrd.Flag, FALSE
iret
Keybrd ENDP
;* Video - Interrupt handler for Interrupt 10h (video). Allows the original
;* video service routine to execute. Maintains an active flag to prevent
;* the TSR from being called while Interrupt 10h is executing.
;*
;* Uses: intVideo
;*
;* Params: Registers passed to Interrupt 10h
;*
;* Return: Registers returned by Interrupt 10h
Video PROC FAR
mov cs:intVideo.Flag, TRUE ; Set active flag
pushf ; Simulate interrupt by pushing flags,
call cs:intVideo.OldHand ; far calling old Int 10h routine
mov cs:intVideo.Flag, FALSE ; Clear active flag
iret
Video ENDP
;* DiskIO - Interrupt handler for Interrupt 13h (disk I/O). Allows the
;* original disk I/O service routine to execute. Maintains an active flag
;* to prevent the TSR from being called while Interrupt 13h is executing.
;*
;* Uses: intDiskIO
;*
;* Params: Registers passed to Interrupt 13h
;*
;* Return: Registers and the carry flag returned by Interrupt 13h
DiskIO PROC FAR
mov cs:intDiskIO.Flag, TRUE ; Set active flag
pushf ; Simulate interrupt by pushing flags,
call cs:intDiskIO.OldHand ; far calling old Int 13h routine
mov cs:intDiskIO.Flag, FALSE; Clear active flag without
; disturbing flags register
sti ; Enable interrupts
ret 2 ; Simulate IRET without popping flags
; (since services use carry flag)
DiskIO ENDP
;* MiscServ - Interrupt handler for Interrupt 15h (Miscellaneous System
;* Services).
;*
;* IBM PC/AT and compatibles:
;* Stub at SkipMiscServ is used as handler, bypassing all calls to
;* Interrupt 15h. Keypresses are checked by Keybrd (Int 09 handler).
;*
;* IBM PS/2 series:
;* This procedure handles calls to Interrupt 15h, searching for
;* Function 4Fh (Keyboard Intercept Service). When AH = 4Fh, gets
;* scan code of current keystroke in AL register. Then compares the
;* scan code and shift state to the hot key. If they match, sets
;* TsrRequestFlag to signal the handlers Clock and Idle that the
;* TSR is requested.
;*
;* Uses: intMisc, TsrRequestFlag
;*
;* Params: Registers passed to Interrupt 15h
;*
;* Return: Registers returned by Interrupt 15h
MiscServ PROC FAR
sti ; Interrupts okay
.IF ah == 4Fh ; If Keyboard Intercept Service,
push ax ; preserve AX,
call CheckHotKey ; check for hot key
pop ax
.IF !carry? ; If hot key,
mov cs:TsrRequestFlag, TRUE ; raise request flag,
clc ; signal BIOS not to process the key,
ret 2 ; simulate IRET without popping flags
.ENDIF ; End carry flag check
.ENDIF ; End Keyboard Intercept check
cli ; Disable interrupts and fall through
SkipMiscServ LABEL FAR ; Interrupt 15h handler if PC/AT
jmp cs:intMisc.OldHand
MiscServ ENDP
;* CtrlBreak - Interrupt trap for Interrupt 1Bh (CTRL+BREAK Handler).
;* Disables CTRL+BREAK processing.
;*
;* Params: None
;*
;* Return: None
CtrlBreak PROC FAR
iret
CtrlBreak ENDP
;* CtrlC - Interrupt trap for Interrupt 23h (CTRL+C Handler).
;* Disables CTRL+C processing.
;*
;* Params: None
;*
;* Return: None
CtrlC PROC FAR
iret
CtrlC ENDP
;* CritError - Interrupt trap for Interrupt 24h (Critical Error Handler).
;* Disables critical error processing.
;*
;* Params: None
;*
;* Return: AL = Stop code 0 or 3
CritError PROC FAR
sti
sub al, al ; Assume DOS 2.x
; Set AL = 0 for ignore error
.IF cs:major != 2 ; If DOS 3.x, set AL = 3
mov al, 3 ; DOS call fails
.ENDIF
iret
CritError ENDP
;* Idle - Interrupt handler for Interrupt 28h (DOS Idle). Allows the
;* original Interrupt 28h service routine to execute. Then checks the
;* request flag TsrRequestFlag maintained either by the keyboard handler
;* (keyboard-activated TSR) or by the timer handler (time-activated TSR).
;* See header comments above for Clock, Keybrd, and MiscServ procedures.
;*
;* If TsrRequestFlag = TRUE and system is in interruptable state, Idle
;* invokes the TSR by calling the Activate procedure. Uses an active flag
;* to prevent the Idle procedure from being reentered while executing.
;*
;* Uses: intIdle and TsrActiveFlag
;*
;* Params: None
;*
;* Return: None
Idle PROC FAR
pushf ; Simulate interrupt by pushing flags,
call cs:intIdle.OldHand ; far-calling old Int 28h routine
.IF cs:intIdle.Flag == FALSE; If not already in this handler,
mov cs:intIdle.Flag, TRUE ; set active flag
sti ; Interrupts are okay
push ds ; Save application's DS
push cs
pop ds ; Set DS to resident code segment
ASSUME ds:@code
call CheckRequest ; Check conditions
.IF !carry? ; If TSR requested and safe,
mov TsrActiveFlag, TRUE ; activate TSR
call Activate
mov TsrActiveFlag, FALSE
.ENDIF ; End carry flag check
mov intIdle.Flag, FALSE ; Clear active flag
pop ds ; Recover application's DS
.ENDIF ; End in-handler check
iret
Idle ENDP
;* Multiplex - Handler for Interrupt 2Fh (Multiplex Interrupt). Checks
;* AH for this TSR's identity number. If no match (indicating call is
;* not intended for this TSR), Multiplex passes control to the previous
;* Interrupt 2Fh handler.
;*
;* Params: AH = Handler identity number
;* AL = Function number 0-2
;*
;* Return: AL = 0FFh (function 0)
;* ES:DI = Pointer to identifier string (function 0)
;* ES:DI = Pointer to resident PSP segment (function 1)
;* ES:DI = Pointer to shared memory (function 2)
Multiplex PROC FAR
.IF ah != cs:IDnumber ; If this handler not requested,
jmp cs:intMultex.OldHand ; pass control to old Int 2Fh
.ENDIF ; handler
.IF al == 0 ; If function 0 (verify presence),
mov al, 0FFh ; AL = 0FFh,
push cs ; ES = resident code segment
pop es
mov di, OFFSET IDstring ; DI = offset of identifier string
.ELSEIF al == 1 ; If function 1 (get PSP address),
mov es, cs:TsrPspSeg ; ES:DI = far address of resident PSP
sub di, di
.ELSE
les di, cs:ShareAddr ; If function 2 (get shared memory),
.ENDIF ; set ES:DI = far address
NoMultiplex LABEL FAR ; Secondary entry for null Multiplex
iret
Multiplex ENDP
;* CheckHotKey - Checks current keystroke for hot key. Called from Keybrd
;* handler if IBM PC/AT or compatible, or from MiscServ handler if PS/2.
;*
;* Uses: HotScan, HotShift, HotMask, and SHFT_STAT
;*
;* Params: AL = Scan code
;*
;* Return: Carry flag set = FALSE; carry flag clear = TRUE
CheckHotKey PROC NEAR
cmp al, cs:HotScan ; If current scan code isn't code
jne e_exit ; for hot key, exit with carry set
push es ; Else look into BIOS data area
sub ax, ax ; (segment 0) to check shift state
mov es, ax
mov al, es:[SHFT_STAT] ; Get SHIFT-key flags
and al, cs:HotMask ; AND with "don't care" mask
cmp al, cs:HotShift ; Compare result with hot SHIFT key
pop es
je exit ; If match, exit with carry clear
e_exit: stc ; Set carry if not hot key
exit: ret
CheckHotKey ENDP
;* CheckRequest - Checks request flag and system status using the
;* following logic:
;*
;* IF (TsrRequestFlag AND (NOT TsrActiveFlag)
;* AND DosStatus AND HardwareStatus)
;* return TRUE
;* ELSE
;* return FALSE
;*
;* Uses: TsrRequestFlag and TsrActiveFlag
;*
;* Params: DS = Resident code segment
;*
;* Return: Carry flag set = TRUE; carry flag clear = FALSE
CheckRequest PROC NEAR
rol TsrRequestFlag, 1 ; Rotate high bit into carry - set
; if TRUE (-1), clear if FALSE (0)
cmc ; NOT carry
.IF !carry? ; If TsrRequestFlag = TRUE:
ror TsrActiveFlag, 1 ; Rotate low bit into carry - set
; if TRUE (-1), clear if FALSE (0)
.IF !carry? ; If TsrActiveFlag = FALSE:
call CheckDos ; Is DOS in interruptable state?
.IF !carry? ; If so,
call CheckHardware ; if hardware or BIOS unstable,
.ENDIF ; set carry and exit
.ENDIF
.ENDIF
ret
CheckRequest ENDP
;* CheckDos - Checks status of MS-DOS using the following logic:
;*
;* IF (NOT CritErr) AND ((NOT InDos) OR (Idle AND InDos))
;* return DosStatus = TRUE
;* ELSE
;* return DosStatus = FALSE
;*
;* Uses: CritErrAddr, InDosAddr, and intIdle
;*
;* Params: DS = Resident code segment
;*
;* Return: Carry flag set if MS-DOS is busy
CheckDos PROC NEAR USES es bx ax
les bx, CritErrAddr
mov ah, es:[bx] ; AH = value of CritErr flag
les bx, InDosAddr
mov al, es:[bx] ; AL = value of InDos flag
sub bx, bx ; BH = 0, BL = 0
cmp bl, intIdle.Flag ; Carry flag set if call is from
; Interrupt 28h handler
rcl bl, 1 ; Rotate carry into BL: TRUE if Idle
cmp bx, ax ; Carry flag clear if CritErr = 0
; and InDos <= BL
ret
CheckDos ENDP
;* CheckHardware - Checks status of BIOS and hardware using the
;* following logic:
;*
;* IF HardwareActive OR KeybrdActive OR VideoActive OR DiskIOActive
;* return HardwareStatus = FALSE
;* ELSE
;* return HardwareStatus = TRUE
;*
;* Uses: intKeybrd, intVideo, and intDiskIO
;*
;* Params: DS = Resident code segment
;*
;* Return: Carry flag set if hardware or BIOS is busy
CheckHardware PROC NEAR USES ax
; Verify hardware interrupt status by interrogating Intel 8259A
; Programmable Interrupt Controller
mov ax, 00001011y ; AL = 0CW3 for Intel 8259A
; (RR = 1, RIS = 1)
out 20h, al ; Request 8259A in-service register
jmp delay ; Wait a few cycles
delay:
in al, 20h ; AL = hardware interrupts being
cmp ah, al ; serviced (bit = 1 if in service)
.IF !carry? ; If no hard interrupts in service,
sub al, al ; verify BIOS interrupts not active,
cmp al, intKeybrd.Flag ; check Interrupt 09 handler
.IF !carry? ; If Int 09 not active,
cmp al, intVideo.Flag ; check Interrupt 10h handler
.IF !carry? ; If Int 10h not active,
cmp al, intDiskIO.Flag ; check Interrupt 13h handler,
.ENDIF ; return with carry set if
.ENDIF ; Interrupt 09, 10h, or 13h
.ENDIF ; is active
ret
CheckHardware ENDP
;* Activate - Sets up for far call to TSR with the following steps:
;*
;* 1. Stores stack pointer SS:SP and switches to new stack
;* 2. Pushes registers onto new stack
;* 3. Stores vectors for Interrupts 1Bh, 23h, and 23h, and
;* replaces them with addresses of error-trapping handlers
;* 4. Stores DOS CTRL+C checking flag, then turns off checking
;* 5. If required, stores DTA address and switches to new DTA
;*
;* When TSR returns, restores all the above.
;*
;* Uses: Reads or writes the following globals:
;* OldStackAddr, TrapArray, BreakCheckFlag, TsrRequestFlag
;*
;* Params: DS = Resident code segment
;*
;* Return: None
Activate PROC NEAR
; Step 1. Set up a new stack
mov WORD PTR OldStackAddr[0], sp ; Save current
mov WORD PTR OldStackAddr[2], ss ; stack pointer
cli ; Turn off interrupts while
push cs ; changing stack
pop ss ; New stack begins
mov sp, OFFSET NewStack ; at LABEL NewStack
sti
; Step 2. Preserve registers (DS already saved in Clock or Idle)
push ax
push bx
push cx
push dx
push si
push di
push bp
push es
cld ; Clear direction flag
; Step 3. Set up trapping handlers for keyboard breaks and DOS
; critical errors (Interrupts 1Bh, 23h, and 24h)
mov cx, CTRAP ; CX = number of handlers
mov si, OFFSET TrapArray ; DS:SI points to trap array
.REPEAT
mov al, [si] ; AL = interrupt number
mov ah, 35h ; Request DOS Function 35h
int 21h ; Get interrupt vector (ES:BX)
mov WORD PTR [si].INTR.OldHand[0], bx ; Save far address of
mov WORD PTR [si].INTR.OldHand[2], es ; application's handler
mov dx, WORD PTR [si].INTR.NewHand[0] ; DS:DX points to TSR's hand
mov ah, 25h ; Request DOS Function 25h
int 21h ; Set interrupt vector
add si, SIZEOF INTR ; DS:SI points to next in list
.UNTILCXZ
; Step 4. Disable MS-DOS break checking during disk I/O
mov ax, 3300h ; Request DOS Function 33h
int 21h ; Get CTRL+BREAK flag in DL
mov BreakCheckFlag, dl ; Preserve it
sub dl, dl ; DL = 0 to disable I/O break checking
mov ax, 3301h ; Request DOS Function 33h
int 21h ; Set CTRL+BREAK flag from DL
; Step 5. If TSR requires a disk transfer area, store address of current
; DTA and switch buffer address to this segment. See Help and section
; "Preserving Existing Data" in Chapter 11 of the Programmer's Guide for
; more information about the DTA.
IFDEF DTA_SIZ
mov ah, 2Fh ; Request DOS Function 2Fh
int 21h ; Get DTA Address into ES:BX
mov WORD PTR OldDtaAddr[0], bx ; Store address
mov WORD PTR OldDtaAddr[2], es
mov dx, OFFSET DtaBuff ; DS:DX points to new DTA
mov ah, 1Ah ; Request DOS Function 1Ah
int 21h ; Set DTA Address
ENDIF
; Call main body of TSR.
mov ax, @data
mov ds, ax ; Initialize DS and ES
mov es, ax ; to data segment
call cs:TsrAddr ; Call main part of TSR
push cs
pop ds ; Reset DS to this segment
; Undo step 5. Restore previous DTA (if required)
IFDEF DTA_SIZ
push ds ; Preserve DS
lds dx, OldDtaAddr ; DS:DX points to application's DTA
mov ah, 1Ah ; Request DOS Function 1Ah
int 21h ; Set DTA Address
pop ds
ENDIF
; Undo step 4. Restore previous MS-DOS break checking
mov dl, BreakCheckFlag ; DL = previous break state
mov ax, 3301h ; Request DOS Function 33h
int 21h ; Set CTRL+BREAK flag from DL
; Undo step 3. Restore previous vectors for error-trapping handlers
mov cx, CTRAP
mov di, OFFSET TrapArray
push ds ; Preserve DS
push ds ; ES = resident code segment
pop es
.REPEAT
mov al, es:[di] ; AL = interrupt number
lds dx, es:[di].INTR.OldHand; DS:DX points to application's handler
mov ah, 25h ; Request DOS Function 25h
int 21h ; Set interrupt vector from DS:DX
add di, SIZEOF INTR ; ES:DI points to next in list
.UNTILCXZ
pop ds
; Undo step 2. Restore registers from stack
pop es
pop bp
pop di
pop si
pop dx
pop cx
pop bx
pop ax
; Undo step 1. Restore address of original stack to SS:SP
cli
mov sp, WORD PTR OldStackAddr[0]
mov ss, WORD PTR OldStackAddr[2]
sti
; Clear request flag and return to caller (Clock or Idle procedure)
mov TsrRequestFlag, FALSE
ret
Activate ENDP
;* INSTALLATION SECTION - The following code is executed only during
;* the TSR's installation phase. When the program terminates through
;* Function 31h, the above code and data remain resident; memory
;* occupied by the following code segment is returned to the operating
;* system.
DGROUP GROUP INSTALLCODE
INSTALLCODE SEGMENT PARA PUBLIC 'CODE2'
ASSUME ds:@code
;* Install - Prepares for installation of a TSR by chaining interrupt
;* handlers and initializing pointers to DOS flags. Install does not
;* call the Terminate-and-Stay-Resident function.
;*
;* This library of routines accommodates both keyboard-activated and
;* time-activated TSRs. The latter are TSRs that activate at a preset
;* time. If the first parameter (Param1) is a valid scan code, Install
;* assumes the TSR is activated from the keyboard and sets up a keyboard
;* handler to search for the hot key. If Param1 is null, Install assumes
;* the next two parameters (Param2 and Param3) are respectively the hour
;* and minute at which the TSR is to activate. In this case, Install
;* calls GetTimeToElapse to initialize the variable CountDown and sets
;* up KeybrdMonitor as the keyboard handler. CountDown and the secondary
;* counter Tick91 serve here the same functions as they do for the
;* ALARM.ASM program presented in Chapter 11 of the Programmer's Guide.
;* Install is callable from a high-level language.
;*
;* Uses: InDosAddr, CritErrAddr, CHAND,
;* HandArray, CTRAP, TrapArray
;*
;* Keyboard-activated Time-activated
;* ------------------ --------------
;* Params: Param1 - Scan code for hot key 0
;* Param2 - Bit value for shift hot key Hour to activate
;* Param3 - Bit mask for shift hot key Minute to activate
;* Param4 - Far address of main TSR procedure (same)
;*
;* Return: AX = 0 if successful, or one of the following codes:
;* IS_INSTALLED FLAGS_NOT_FOUND NO_IDNUM
;* ALREADY_INSTALLED WRONG_DOS
Install PROC FAR USES ds si di,
Param1:WORD, Param2:WORD, Param3:WORD, Param4:FAR PTR FAR
mov ax, @code
mov ds, ax ; Point DS to code segment
; Get and store parameters passed from main program module
mov al, BYTE PTR Param1
mov HotScan, al ; Store hot-key scan code
mov al, BYTE PTR Param2 ; or flag for time activate
mov HotShift, al ; Store hot-key shift value
mov al, BYTE PTR Param3 ; or hour value
mov HotMask, al ; Store hot-key shift mask
; or minute value
mov ax, WORD PTR Param4[0]
mov bx, WORD PTR Param4[2]
mov WORD PTR TsrAddr[0], ax ; Store segment:offset of
mov WORD PTR TsrAddr[2], bx ; TSR's main code
; Get addresses of DOS flags, then check for prior installation
call GetDosFlags ; Find DOS service flags
or ax, ax
jnz exit ; If flags not found, quit
sub al, al ; Request multiplex function 0
call CallMultiplex ; Invoke Interrupt 2Fh
cmp ax, NOT_INSTALLED ; Check for presence of resident TSR
.IF !zero? ; If TSR is installed,
cmp ax, IS_INSTALLED ; return with appropriate
jne exit ; error code
mov ax, ALREADY_INSTALLED
jmp exit
.ENDIF
; Check if TSR is to activate at the hour:minute specified by Param2:Param3.
; If so, determine the number of 5-second intervals that must elapse before
; activation, then set up the code at the far LABEL KeybrdMonitor to serve
; as the keyboard handler.
.IF HotScan == 0 ; If valid scan code given,
mov ah, HotShift ; AH = hour to activate
mov al, HotMask ; AL = minute to activate
call GetTimeToElapse ; Get number of 5-second intervals
mov CountDown, ax ; to elapse before activation
.ELSE ; Force use of KeybrdMonitor as
; keyboard handler
cmp Version, 031Eh ; DOS Version 3.3 or higher?
jb setup ; No? Skip next step
; Test for IBM PS/2 series. If not PS/2, use Keybrd and SkipMiscServ as
; handlers for Interrupts 09 and 15h respectively. If PS/2 system, set up
; KeybrdMonitor as the Interrupt 09 handler. Audit keystrokes with MiscServ
; handler, which searches for the hot key by handling calls to Interrupt 15h
; (Miscellaneous System Services). For more information about keyboard
; handlers, refer to the section "Auditing Hardware Events for TSR Requests"
; in Chapter 11 of the Programmer's Guide
mov ax, 0C00h ; Function 0Ch (Get System
int 15h ; Configuration Parameters)
sti ; Compaq ROM may leave interrupts
; disabled
jc setup ; If carry set,
or ah, ah ; or if AH not 0,
jnz setup ; services are not supported
test BYTE PTR es:[bx+5], 00010000y ; Test bit 4 to see if
jz setup ; intercept is implemented
mov ax, OFFSET MiscServ ; If so, set up MiscServ as
mov WORD PTR intMisc.NewHand, ax ; Interrupt 15h handler
.ENDIF
mov ax, OFFSET KeybrdMonitor ; Set up KeybrdMonitor as
mov WORD PTR intKeybrd.NewHand, ax ; Interrupt 09 handler
; Interrupt structure is now initialized for either PC/AT or PS/2 system.
; Get existing handler addresses from interrupt vector table, store in
; OldHand member, and replace with addresses of new handlers.
setup:
mov cx, CHAND ; CX = count of handlers
mov si, OFFSET HandArray ; SI = offset of handler structures
.REPEAT
mov ah, 35h ; Request DOS Function 35h
mov al, [si] ; AL = interrupt number
int 21h ; Get interrupt vector in ES:BX
mov WORD PTR [si].INTR.OldHand[0], bx ; Save far address
mov WORD PTR [si].INTR.OldHand[2], es ; of current handler
mov dx, WORD PTR [si].INTR.NewHand[0] ; DS:DX points to TSR handler
mov ah, 25h ; Request DOS Function 25h
int 21h ; Set interrupt vector from DS:DX
add si, SIZEOF INTR ; DS:SI points to next in list
.UNTILCXZ
sub ax, ax ; Clear return code
exit:
ret ; Return to caller
Install ENDP
;* Deinstall - Prepares for deinstallation of a TSR. Deinstall is the
;* complement of the Install procedure. It restores to the vector table
;* the original addresses replaced during installation, thus unhooking
;* the TSR's handlers. Checks to see if another TSR has installed handlers
;* for the interrupts in array HandArray. If so, the procedure fails with
;* an appropriate error code. Callable from a high-level language.
;*
;* Params: None
;*
;* Return: AX = Segment address of resident portion's PSP or
;* one of the following error codes:
;* CANT_DEINSTALL WRONG_DOS
Deinstall PROC FAR USES ds si di
mov ax, @code
mov ds, ax ; Point DS to code segment
sub al, al ; Request multiplex function 0
call CallMultiplex ; Get resident code segment in ES
cmp ax, IS_INSTALLED ; If not resident,
jne exit ; exit with error
push es ; Else point DS to
pop ds ; resident code segment
mov cx, CHAND ; Count of handlers
mov si, OFFSET HandArray ; SI points to handler structures
; Read current vectors for TSR's interrupt handlers and compare with far
; addresses. If mismatch, another TSR has installed new handlers and ours
; cannot be safely deinstalled.
.REPEAT
mov al, [si] ; AL = interrupt number
mov ah, 35h ; Request DOS Function 35h
int 21h ; Get interrupt vector in ES:BX
cmp bx, WORD PTR [si].INTR.NewHand[0] ; If offset different,
jne e_exit ; error
mov ax, es
cmp ax, WORD PTR [si].INTR.NewHand[2] ; If segment different,
jne e_exit ; error
add si, SIZEOF INTR ; DS:SI points to next in list
.UNTILCXZ
; If no interrupts replaced, call TSR's multiplex handler to locate
; address of resident portion's PSP. Although the PSP is not required
; until memory is returned to DOS, the call must be done now before
; unhooking the multiplex handler.
mov al, 1 ; Request multiplex function 1
call CallMultiplex ; Get resident code's PSP in ES
push es ; Save it
; Unhook all handlers by restoring the original vectors to vector table.
mov cx, CHAND ; Count of installed handlers
mov si, OFFSET HandArray ; SI points to handler structures
.REPEAT
mov al, [si] ; AL = interrupt number
push ds ; Preserve DS segment
lds dx, [si].INTR.OldHand ; Put vector in DS:DX
mov ah, 25h ; Request DOS Function 25h
int 21h ; Set interrupt vector from DS:DX
pop ds
add si, SIZEOF INTR ; DS:SI points to next in list
.UNTILCXZ
pop ax ; Return address of resident PSP
jmp exit ; to signal success
e_exit:
mov ax, CANT_DEINSTALL
exit:
ret
Deinstall ENDP
;* GetVersion - Gets the DOS version and stores it in a global variable as
;* well as returning it in AX.
;*
;* Uses: Version
;*
;* Params: DS = Resident code segment
;*
;* Return: AH = Major version
;* AL = Minor version
GetVersion PROC NEAR
mov ax, 3000h ; Request DOS Function 30h
int 21h ; Get MS-DOS version number
.IF al < 2 ; If Version 1.x:
mov ax, WRONG_DOS ; Abort with WRONG_DOS as error code
.ELSE
xchg ah, al ; AH = major, AL = minor version
mov Version, ax ; Save in global
.ENDIF
ret
GetVersion ENDP
;* GetDosFlags - Gets pointers to the DOS InDos and Critical Error flags.
;*
;* Params: DS = Resident code segment
;*
;* Return: 0 if successful, or the following error code:
;* FLAGS_NOT_FOUND
GetDosFlags PROC NEAR
; Get InDOS address from MS-DOS
mov ah, 34h ; Request DOS Function 34h
int 21h ; Get Address of InDos flag
mov WORD PTR InDosAddr[0], bx ; Store address (ES:BX)
mov WORD PTR InDosAddr[2], es ; for later access
; Determine address of Critical Error Flag
mov ax, Version ; AX = DOS version number
; If DOS 3.1 or greater and not OS/2 compatibility mode, Critical Error
; flag is in byte preceding InDOS flag
.IF (ah < 10) && (ah >= 3) && (al >= 10)
dec bx ; BX points to byte before InDos flag
.ELSE
; For earlier versions, the only reliable method is to scan through
; DOS to find an INT 28h instruction in a specific context.
mov cx, 0FFFFh ; Maximum bytes to scan
sub di, di ; ES:DI = start of DOS segment
INT_28 EQU 028CDh
.REPEAT
mov ax, INT_28 ; Load opcode for INT 28h
.REPEAT
repne scasb ; Scan for first byte of opcode
.IF !zero?
mov ax, FLAGS_NOT_FOUND ; Return error if not found
jmp exit
.ENDIF
.UNTIL ah == es:[di] ; For each matching first byte,
; check the second byte until match
; See if INT 28h is in this context:
; ; (-7) (-5)
; CMP ss:[CritErrFlag], 0 ; 36, 80, 3E, ?, ?, 0
; JNE NearLabel ; 75, ?
int 28h ; CD, 28
; ; (0) (1)
CMP_SS EQU 3E80h
P_CMP_SS EQU 8
P_CMP_OP EQU 6
mov ax, CMP_SS ; Load and compare opcode to CMP
.IF ax == es:[di-P_CMP_SS] ; If match:
mov bx, es:[di-P_CMP_OP] ; BX = offset of
jmp exit ; Critical Error flag
.ENDIF
; See if INT 28h is in this context:
; ; (-12) (-10)
; TEST ?s:[CritErr], 0FFh ; ?6 F6, 06, ?, ?, FF
; JNE NearLabel ; 75, ?
; PUSH ss:[CritErrFlag] ; 36, FF, 36, ?, ?
int 28h ; CD, 28
; ; (0) (1)
TEST_SS EQU 06F6h
P_TEST_SS EQU 13
P_TEST_OP EQU 11
mov ax, TEST_SS ; Load AX = opcode for TEST
.UNTIL ax == es:[di-P_TEST_SS] ; If not TEST, continue scan
mov bx, es:[di-P_TEST_OP] ; Else load BX with offset of
.ENDIF ; Critical Error flag
exit:
mov WORD PTR CritErrAddr[0], bx ; Store address of
mov WORD PTR CritErrAddr[2], es ; Critical Error flag
sub ax, ax ; Clear error code
ret
GetDosFlags ENDP
;* GetTimeToElapse - Determines number of 5-second intervals that
;* must elapse between specified hour:minute and current time.
;*
;* Params: AH = Hour
;* AL = Minute
;*
;* Return: AX = Number of 5-second intervals
GetTimeToElapse PROC NEAR
push ax ; Save hour:minute
mov ah, 2Ch ; Request DOS Function 2Ch
int 21h ; Get Time (CH:CL = hour:minute)
pop bx ; Recover hour:minute
mov dl, dh
sub dh, dh
push dx ; Save DX = current seconds
mov al, 60 ; 60 minutes/hour
mul bh ; Multiply by specified hour
sub bh, bh
add bx, ax ; BX = minutes from midnight
; to activation time
mov al, 60 ; 60 minutes/hour
mul ch ; Multiply by current hour
sub ch, ch
add ax, cx ; AX = minutes from midnight
; to current time
sub bx, ax ; BX = minutes to elapse before
.IF carry? ; If activation is tomorrow,
add bx, 24 * 60 ; add number of minutes per day
.ENDIF
mov ax, 60
mul bx ; DX:AX = minutes-to-elapse-times-60
pop bx ; Recover current seconds
sub ax, bx ; DX:AX = seconds to elapse before
sbb dx, 0 ; activation
.IF carry? ; If negative,
mov ax, 5 ; assume 5 seconds
cwd
.ENDIF
mov bx, 5 ; Divide result by 5 seconds
div bx ; AX = number of 5-second intervals
ret
GetTimeToElapse ENDP
;* CallMultiplex - Calls the Multiplex Interrupt (Interrupt 2Fh).
;*
;* Uses: IDstring
;*
;* Params: AL = Function number for multiplex handler
;*
;* Return: AX = One of the following return codes:
;* NOT_INSTALLED IS_INSTALLED NO_IDNUM
;* ES:DI = Resident code segment:identifier string (function 0)
;* ES:DI = Resident PSP segment address (function 1)
;* ES:DI = Far address of shared memory (function 2)
CallMultiplex PROC FAR USES ds
push ax ; Save function number
mov ax, @code
mov ds, ax ; Point DS to code segment
; First, check 2Fh vector. DOS Version 2.x may leave the vector null
; if PRINT.COM is not installed. If vector is null, point it to IRET
; instruction at LABEL NoMultiplex. This allows the new multiplex
; handler to pass control, if necessary, to a proper existing routine.
mov ax, 352Fh ; Request DOS Function 35h
int 21h ; Get interrupt vector in ES:BX
mov ax, es
or ax, bx
.IF zero? ; If Null vector,
mov dx, OFFSET NoMultiplex ; set vector to IRET instruction
mov ax, 252Fh ; at LABEL NoMultiplex
int 21h ; Set interrupt vector
.ENDIF
; Second, call Interrupt 2Fh with function 0 (presence request). Cycle
; through allowable identity numbers (192 to 255) until TSR's multiplex
; handler returns ES:DI = IDstring to verify its presence or until call
; returns AL = 0, indicating the TSR is not installed.
mov dh, 192 ; Start with identity number = 192
.REPEAT
mov ah, dh ; Call Multiplex with AH = trial ID
sub al, al ; and AL = function 0
push dx ; Save DH and DS in case call
push ds ; destroys them
int 2Fh ; Multiplex
pop ds ; Recover DS and
pop dx ; current ID number in DH
or al, al ; Does a handler claim this ID number?
jz no ; If not, stop search
.IF al == 0FFh ; If handler ready to process calls,
mov si, OFFSET IDstring ; point DS:SI to ID string, compare
mov cx, IDstrlen ; with string at ES:DI returned
repe cmpsb ; by multiplex handler
je yes ; If equal, TSR's handler is found
.ENDIF
inc dh ; This handler is not the one
.UNTIL zero? ; Try next identity number up to 255
mov ax, NO_IDNUM ; In the unlikely event that numbers
jmp e_exit ; 192-255 are all taken, quit
; Third, assuming handler is found and verified, process the multiplex
; call with the requested function number.
yes:
pop ax ; AL = original function number
mov ah, dh ; AH = identity number
int 2Fh ; Multiplex
mov ax, IS_INSTALLED ; Signal that handler has been found
jmp exit ; and quit
; Reaching this section means multiplex handler (and TSR) not installed.
; Since the value in DH is not claimed by any handler, it will be used as
; the resident TSR's identity number. Save the number in resident code
; segment so multiplex handler can find it.
no:
mov IDnumber, dh ; Save multiplex identity number
mov ax, NOT_INSTALLED ; Signal handler is not installed
e_exit:
pop bx ; Remove function number from stack
exit:
ret
CallMultiplex ENDP
;* InitTsr - Initializes DOS version variables and multiplex data with
;* following parameters. This procedure must execute before calling
;* either the Install, Deinstall, or CallMultiplex procedures. Callable
;* from a high-level language.
;*
;* Uses: IDstring
;*
;* Params: PspParam - Segment address of PSP
;* StrParam - Far address of TSR's identifier string
;* ShrParam - Far address of shared memory
;*
;* Return: AX = WRONG_DOS if not DOS Version 2.0 or higher
InitTsr PROC FAR USES ds es si di,
PspParam:WORD, StrParam:FPVOID, ShrParam:FPVOID
mov ax, @code
mov ds, ax ; Point DS and ES
mov es, ax ; to code segment
; Get and store parameters passed from main program module
mov ax, PspParam
mov TsrPspSeg, ax ; Store PSP segment address
mov ax, WORD PTR ShrParam[0]
mov bx, WORD PTR ShrParam[2]
mov WORD PTR ShareAddr[0], ax ; Store far address of
mov WORD PTR ShareAddr[2], bx ; shared memory
push ds
mov si, WORD PTR StrParam[0] ; DS:SI points to multiplex
mov ax, WORD PTR StrParam[2] ; identifier string
mov ds, ax
mov di, OFFSET IDstring ; Copy string to IDstring
mov cx, STR_LEN ; at ES:DI so multiplex
; handler has a copy
.REPEAT
lodsb ; Copy STR_LEN characters
.BREAK .IF al == 0 ; or until null terminator
stosb ; found
.UNTILCXZ
pop ds ; Recover DS = code segment
mov ax, STR_LEN
sub ax, cx
mov IDstrlen, ax ; Store string length
INVOKE GetVersion ; Return AX = version number
ret ; or WRONG_DOS
InitTsr ENDP
INSTALLCODE ENDS
END