home *** CD-ROM | disk | FTP | other *** search
- .MODEL small, pascal, os_dos
-
- ; Prototypes for internal procedures
- Activate PROTO NEAR
- CheckRequest PROTO NEAR
- CheckDos PROTO NEAR
- CheckHardware PROTO NEAR
- GetDosFlags PROTO NEAR
-
- 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, optionally do some
- ; processing (such as detecting a hot key), call the old system interrupt
- ; routine, 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 17.5.3 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 Section 17.3 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, which 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
-
- INVOKE CheckRequest ; Check conditions
- .IF !carry? ; If TSR requested and safe:
- mov TsrActiveFlag, TRUE ; Activate TSR
- INVOKE 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
-
- INVOKE CheckRequest ; Check conditions
- .IF !carry? ; If TSR requested and safe:
- mov TsrActiveFlag, TRUE ; Activate TSR
- INVOKE 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 reqested:
- jmp cs:intMultex.OldHand ; Pass control to old Int 2Fh handler
- .ENDIF
-
- .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:
- INVOKE CheckDos ; Is DOS in interruptable state?
-
- .IF !carry? ; If so:
- INVOKE 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 Section 17.5.3 of
- ; 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 accomodates 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 hotkey. 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 Section 17.3 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 hotkey 0
- ;* Param2 - Bit value for shift hotkey Hour to activate
- ;* Param3 - Bit mask for shift hotkey 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
-
- INVOKE 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). Refer to Section 17.2.1 of the Programmer's
- ; Guide for more information about keyboard handlers.
-
- mov ax, 0C00h ; Function 0Ch (Get System
- int 15h ; Configuration Parameters)
- sti ; Compaq ROM may leave 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 byte 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 DOS's 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 ; Mutiply 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
-