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
Text File  |  1992-11-12  |  52KB  |  1,284 lines

  1.         .MODEL  small, pascal
  2.         INCLUDE tsr.inc
  3.  
  4.         .CODE
  5.  
  6. ; Stack buffer used by TSR. Size is determined by constant STACK_SIZ,
  7. ; declared in TSR.INC file. NewStack points to top of stack.
  8.  
  9.          EVEN
  10.          BYTE   STACK_SIZ DUP(?)        ; Stack buffer
  11. NewStack LABEL BYTE                     ; Pointer to top of stack
  12.  
  13. ; Structures for interrupt handlers or "interrupt service routines."
  14. ; The following handlers are replaced during installation. Such routines
  15. ; usually set a flag to indicate they are active, call the original
  16. ; interrupt handler, optionally do some processing (such as detecting a
  17. ; hot key), and when finished clear the active flag.
  18.  
  19. HandArray       LABEL   BYTE                    ; Array of handler structures
  20. ;                Num   Flag     OldHand  NewHand
  21. intClock  INTR  < 8h,  FALSE,   NULL,    Clock>
  22. intKeybrd INTR  < 9h,  FALSE,   NULL,    Keybrd>
  23. intVideo  INTR  <10h,  FALSE,   NULL,    Video>
  24. intDiskIO INTR  <13h,  FALSE,   NULL,    DiskIO>
  25. intMisc   INTR  <15h,  FALSE,   NULL,    SkipMiscServ>
  26. intIdle   INTR  <28h,  FALSE,   NULL,    Idle>
  27. intMultex INTR  <2Fh,  FALSE,   NULL,    Multiplex>
  28.  
  29. CHAND   EQU     ($ - HandArray) / (SIZEOF INTR) ; Number of handlers in array
  30.  
  31. ; Interrupt trap routines. These interrupt routines are set up
  32. ; temporarily to trap keyboard break errors and critical errors
  33. ; while the TSR is active. When the TSR finishes its tasks, it
  34. ; restores the old interrupts before returning.
  35.  
  36. TrapArray       LABEL   BYTE                    ; Array of trap structures
  37. ;                Num   Flag     OldHand  NewHand
  38. intCtrlBk INTR  <1Bh,  FALSE,   NULL,    CtrlBreak>
  39. intCtrlC  INTR  <23h,  FALSE,   NULL,    CtrlC>
  40. intCritEr INTR  <24h,  FALSE,   NULL,    CritError>
  41.  
  42. CTRAP   EQU     ($ - TrapArray) / (SIZEOF INTR) ; Number of traps in array
  43.  
  44. ; Address of application's stack. Before calling the main body of the TSR,
  45. ; the Activate procedure stores the application's stack address, then resets
  46. ; SS:SP to point to LABEL NewStack (see above). This gives the TSR its own
  47. ; stack space without making demands on the current stack. Activate restores
  48. ; the application's stack before returning.
  49.  
  50. OldStackAddr    FPVOID  ?               ; SS:SP pointer to application stack
  51.  
  52. ; The TSR must set up its own disk transfer area if it calls DOS functions
  53. ; that use the DTA (see section "Preserving Existing Data" in Chapter 11 of
  54. ; the Programmer's Guide). DTA_SIZ is defined in the TSR.INC include file.
  55.  
  56.                 IFDEF   DTA_SIZ
  57. OldDtaAddr      FPVOID  ?               ; Address of application's DTA
  58. DtaBuff         BYTE    DTA_SIZ DUP(?)  ; DTA buffer
  59.                 ENDIF
  60.  
  61. ; Multiplex data. STR_LEN is defined in the TSR.INC include file
  62.  
  63. IDnumber        BYTE    0               ; TSR's identity number
  64. IDstring        BYTE    STR_LEN DUP (0) ; Copy of identifier string
  65. IDstrlen        WORD    ?               ; Length of identifier string
  66. ShareAddr       FPVOID  ?               ; Address of shared memory
  67.  
  68. ; Miscellaneous data
  69.  
  70. TsrRequestFlag  BYTE    FALSE           ; Flag set when hot key is pressed
  71. TsrActiveFlag   BYTE    FALSE           ; Flag set when TSR executes
  72. BreakCheckFlag  BYTE    ?               ; Break-checking status of application
  73. TsrPspSeg       WORD    ?               ; Segment address of PSP
  74. TsrAddr         FPVOID  ?               ; Pointer to main part of TSR
  75. CritErrAddr     FPVOID  ?               ; Pointer to MS-DOS critical error flag
  76. InDosAddr       FPVOID  ?               ; Pointer to MS-DOS InDos flag
  77.  
  78. ; Scan and shift codes for hot key. Install procedure initializes
  79. ; HotScan, HotShift, and HotMask during installation.
  80.  
  81. HotScan         BYTE    ?               ; Scan code hot key
  82. HotShift        BYTE    ?               ; Shift value of hot key
  83. HotMask         BYTE    ?               ; Mask unwanted shift values
  84.  
  85. Version         LABEL   WORD            ; DOS version number
  86. minor           BYTE    ?
  87. major           BYTE    ?
  88.  
  89. ; Timer data, used when the TSR is activated at a preset time instead
  90. ; of activated from the keyboard. The following variables serve the
  91. ; same purposes as the counter variables used in the ALARM.ASM program
  92. ; presented in Chapter 11 of the Programmer's Guide. Refer to the
  93. ; header comments in the Install procedure for an explanation of how
  94. ; to set up a time-activated TSR.
  95.  
  96. Tick91          BYTE    91              ; Measures 91 timer ticks (5 seconds)
  97. CountDown       WORD    0               ; Counts 5-second intervals
  98.  
  99.  
  100.  
  101. ;* Clock - Interrupt handler for Interrupt 08 (timer). Executes at each
  102. ;* timer interrupt; these occur an average of 18.2 times per second. Clock
  103. ;* first allows the original timer service routine to execute. It then
  104. ;* checks the flag TsrRequestFlag maintained either by the keyboard handler
  105. ;* (if keyboard activated) or by this procedure (if time activated). If
  106. ;* TsrRequestFlag = TRUE and system is okay, Clock invokes the TSR by
  107. ;* calling the Activate procedure. Uses an active flag to prevent the
  108. ;* Clock procedure from being reentered while executing.
  109. ;*
  110. ;* Uses:   intClock, TsrActiveFlag, CountDown
  111. ;*
  112. ;* Params: None
  113. ;*
  114. ;* Return: None
  115.  
  116. Clock   PROC    FAR
  117.  
  118.         pushf                           ; Simulate interrupt by pushing flags,
  119.         call    cs:intClock.OldHand     ;   far calling orig Int 08 routine
  120.  
  121.         .IF     cs:intClock.Flag == FALSE ; If not already in this handler:
  122.         mov     cs:intClock.Flag, TRUE  ; Set active flag
  123.  
  124.         sti                             ; Interrupts are okay
  125.         push    ds                      ; Save application's DS
  126.         push    cs
  127.         pop     ds                      ; Set DS to resident code segment
  128.         ASSUME  ds:@code
  129.  
  130.         call    CheckRequest            ; Check conditions
  131.         .IF     !carry?                 ; If TSR requested and safe,
  132.         mov     TsrActiveFlag, TRUE     ;   activate TSR
  133.         call    Activate
  134.         mov     TsrActiveFlag, FALSE
  135.         .ENDIF                          ; End carry flag check
  136.  
  137.         cmp     CountDown, 0            ; If CountDown = 0, TSR is not time
  138.         je      ticked                  ;   activated or has already executed
  139.         dec     Tick91                  ; Else count down 91 timer ticks
  140.         jnz     ticked                  ; If 91 ticks have not elapsed, exit
  141.         mov     Tick91, 91              ; Else reset secondary counter and
  142.         dec     CountDown               ;   subract one 5-second interval
  143.         ja      ticked                  ; If counter not yet drained, exit
  144.         mov     TsrRequestFlag, TRUE    ; Else raise request flag
  145. ticked:
  146.         mov     intClock.Flag, FALSE    ; Clear active flag
  147.         pop     ds                      ; Recover application's DS
  148.         ASSUME  ds:NOTHING
  149.  
  150.         .ENDIF                          ; End in-handler check
  151.         iret
  152.  
  153. Clock   ENDP
  154.  
  155.  
  156. ;* Keybrd - Interrupt handler for Interrupt 09 (keyboard).
  157. ;*
  158. ;* IBM PC/AT and compatibles:
  159. ;*      Gets the scan code of the current keystroke from port 60h. Then
  160. ;*      compares the scan code and shift state to the hot key. If they
  161. ;*      match, sets TsrRequestFlag to signal the handlers Clock and Idle
  162. ;*      that the TSR is requested.
  163. ;* 
  164. ;* IBM PS/2 series:
  165. ;*      Only the instructions at KeybrdMonitor (see below) are installed
  166. ;*      as Interrupt 09 handler, since above method should not be used to
  167. ;*      determine current keystroke in IBM PS/2 series. In this case, the
  168. ;*      Interrupt 15h handler MiscServ takes care of checking the scan codes
  169. ;*      and setting the request flag when the hot key is pressed.
  170. ;*
  171. ;* Time-activated TSRs:
  172. ;*      If the TSR is activated by time instead of by a hotkey, KeybrdMonitor
  173. ;*      serves as the Interrupt 09 handler for both PC/AT and PS/2 systems.
  174. ;*
  175. ;* Uses:   intKeybrd, TsrRequestFlag
  176. ;* 
  177. ;* Params: None
  178. ;*
  179. ;* Return: None
  180.  
  181. Keybrd  PROC    FAR
  182.  
  183.         sti                             ; Interrupts are okay
  184.         push    ax                      ; Save AX register
  185.         in      al, 60h                 ; AL = scan code of current key
  186.         call    CheckHotKey             ; Check for hot key
  187.         .IF     !carry?                 ; If not hot key:
  188.  
  189. ; Hot key pressed. Reset the keyboard to throw away keystroke.
  190.  
  191.         cli                             ; Disable interrupts while resetting
  192.         in      al, 61h                 ; Get current port 61h state
  193.         or      al, 10000000y           ; Turn on bit 7 to signal clear keybrd
  194.         out     61h, al                 ; Send to port
  195.         and     al, 01111111y           ; Turn off bit 7 to signal break
  196.         out     61h, al                 ; Send to port
  197.         mov     al, 20h                 ; Reset interrupt controller
  198.         out     20h, al
  199.         sti                             ; Reenable interrupts
  200.  
  201.         pop     ax                      ; Recover AX
  202.         mov     cs:TsrRequestFlag, TRUE ; Raise request flag
  203.         iret                            ; Exit interrupt handler
  204.         .ENDIF                          ; End hot-key check
  205.  
  206. ; No hot key was pressed, so let normal Int 09 service routine take over
  207.  
  208.         pop     ax                      ; Recover AX and fall through
  209.         cli                             ; Interrupts cleared for service
  210.  
  211. KeybrdMonitor LABEL FAR                 ; Installed as Int 09 handler for
  212.                                         ;   PS/2 or for time-activated TSR
  213.         mov     cs:intKeybrd.Flag, TRUE ; Signal that interrupt is busy
  214.         pushf                           ; Simulate interrupt by pushing flags,
  215.         call    cs:intKeybrd.OldHand    ;   far calling old Int 09 routine
  216.         mov     cs:intKeybrd.Flag, FALSE
  217.         iret
  218.  
  219. Keybrd  ENDP
  220.  
  221.  
  222. ;* Video - Interrupt handler for Interrupt 10h (video). Allows the original
  223. ;* video service routine to execute. Maintains an active flag to prevent
  224. ;* the TSR from being called while Interrupt 10h is executing.
  225. ;*
  226. ;* Uses:   intVideo
  227. ;*
  228. ;* Params: Registers passed to Interrupt 10h
  229. ;*
  230. ;* Return: Registers returned by Interrupt 10h
  231.  
  232. Video   PROC    FAR
  233.  
  234.         mov     cs:intVideo.Flag, TRUE  ; Set active flag
  235.         pushf                           ; Simulate interrupt by pushing flags,
  236.         call    cs:intVideo.OldHand     ;   far calling old Int 10h routine
  237.         mov     cs:intVideo.Flag, FALSE ; Clear active flag
  238.         iret
  239.  
  240. Video   ENDP
  241.  
  242.  
  243. ;* DiskIO - Interrupt handler for Interrupt 13h (disk I/O). Allows the
  244. ;* original disk I/O service routine to execute. Maintains an active flag
  245. ;* to prevent the TSR from being called while Interrupt 13h is executing.
  246. ;*
  247. ;* Uses:   intDiskIO
  248. ;*
  249. ;* Params: Registers passed to Interrupt 13h
  250. ;*
  251. ;* Return: Registers and the carry flag returned by Interrupt 13h
  252.  
  253. DiskIO  PROC    FAR
  254.  
  255.         mov     cs:intDiskIO.Flag, TRUE ; Set active flag
  256.         pushf                           ; Simulate interrupt by pushing flags,
  257.         call    cs:intDiskIO.OldHand    ;   far calling old Int 13h routine
  258.         mov     cs:intDiskIO.Flag, FALSE; Clear active flag without
  259.                                         ;   disturbing flags register
  260.         sti                             ; Enable interrupts
  261.         ret     2                       ; Simulate IRET without popping flags
  262.                                         ;   (since services use carry flag)
  263. DiskIO  ENDP
  264.  
  265.  
  266. ;* MiscServ - Interrupt handler for Interrupt 15h (Miscellaneous System
  267. ;* Services).
  268. ;*
  269. ;* IBM PC/AT and compatibles:
  270. ;*     Stub at SkipMiscServ is used as handler, bypassing all calls to
  271. ;*     Interrupt 15h. Keypresses are checked by Keybrd (Int 09 handler).
  272. ;* 
  273. ;* IBM PS/2 series:
  274. ;*     This procedure handles calls to Interrupt 15h, searching for
  275. ;*     Function 4Fh (Keyboard Intercept Service). When AH = 4Fh, gets
  276. ;*     scan code of current keystroke in AL register. Then compares the
  277. ;*     scan code and shift state to the hot key. If they match, sets
  278. ;*     TsrRequestFlag to signal the handlers Clock and Idle that the
  279. ;*     TSR is requested.
  280. ;*
  281. ;* Uses:   intMisc, TsrRequestFlag
  282. ;*
  283. ;* Params: Registers passed to Interrupt 15h
  284. ;*
  285. ;* Return: Registers returned by Interrupt 15h
  286.  
  287. MiscServ PROC   FAR
  288.  
  289.         sti                             ; Interrupts okay
  290.         .IF     ah == 4Fh               ; If Keyboard Intercept Service,
  291.         push    ax                      ;   preserve AX,
  292.         call    CheckHotKey             ;   check for hot key
  293.         pop     ax
  294.         .IF     !carry?                 ; If hot key,
  295.         mov     cs:TsrRequestFlag, TRUE ;   raise request flag,
  296.         clc                             ;   signal BIOS not to process the key,
  297.         ret     2                       ;   simulate IRET without popping flags
  298.         .ENDIF                          ; End carry flag check
  299.         .ENDIF                          ; End Keyboard Intercept check
  300.  
  301.         cli                             ; Disable interrupts and fall through
  302.  
  303. SkipMiscServ LABEL FAR                  ; Interrupt 15h handler if PC/AT
  304.  
  305.         jmp     cs:intMisc.OldHand
  306.  
  307. MiscServ ENDP
  308.  
  309.  
  310. ;* CtrlBreak - Interrupt trap for Interrupt 1Bh (CTRL+BREAK Handler).
  311. ;* Disables CTRL+BREAK processing.
  312. ;*
  313. ;* Params: None
  314. ;*
  315. ;* Return: None
  316.  
  317. CtrlBreak PROC  FAR
  318.  
  319.         iret
  320.  
  321. CtrlBreak ENDP
  322.  
  323.  
  324. ;* CtrlC - Interrupt trap for Interrupt 23h (CTRL+C Handler).
  325. ;* Disables CTRL+C processing.
  326. ;*
  327. ;* Params: None
  328. ;*
  329. ;* Return: None
  330.  
  331. CtrlC   PROC    FAR
  332.  
  333.         iret
  334.  
  335. CtrlC   ENDP
  336.  
  337.  
  338. ;* CritError - Interrupt trap for Interrupt 24h (Critical Error Handler).
  339. ;* Disables critical error processing.
  340. ;*
  341. ;* Params: None
  342. ;*
  343. ;* Return: AL = Stop code 0 or 3
  344.  
  345. CritError PROC  FAR
  346.  
  347.         sti
  348.         sub     al, al                  ; Assume DOS 2.x
  349.                                         ; Set AL = 0 for ignore error
  350.         .IF     cs:major != 2           ; If DOS 3.x, set AL = 3
  351.         mov     al, 3                   ; DOS call fails
  352.         .ENDIF
  353.  
  354.         iret
  355.  
  356. CritError ENDP
  357.  
  358.  
  359. ;* Idle - Interrupt handler for Interrupt 28h (DOS Idle). Allows the
  360. ;* original Interrupt 28h service routine to execute. Then checks the
  361. ;* request flag TsrRequestFlag maintained either by the keyboard handler
  362. ;* (keyboard-activated TSR) or by the timer handler (time-activated TSR).
  363. ;* See header comments above for Clock, Keybrd, and MiscServ procedures.
  364. ;*
  365. ;* If TsrRequestFlag = TRUE and system is in interruptable state, Idle
  366. ;* invokes the TSR by calling the Activate procedure. Uses an active flag
  367. ;* to prevent the Idle procedure from being reentered while executing.
  368. ;*
  369. ;* Uses:   intIdle and TsrActiveFlag
  370. ;*
  371. ;* Params: None
  372. ;*
  373. ;* Return: None
  374.  
  375. Idle    PROC    FAR
  376.  
  377.         pushf                           ; Simulate interrupt by pushing flags,
  378.         call    cs:intIdle.OldHand      ;   far-calling old Int 28h routine
  379.  
  380.         .IF     cs:intIdle.Flag == FALSE; If not already in this handler,
  381.         mov     cs:intIdle.Flag, TRUE   ;   set active flag
  382.  
  383.         sti                             ; Interrupts are okay
  384.         push    ds                      ; Save application's DS
  385.         push    cs
  386.         pop     ds                      ; Set DS to resident code segment
  387.         ASSUME  ds:@code
  388.  
  389.         call    CheckRequest            ; Check conditions
  390.         .IF     !carry?                 ; If TSR requested and safe,
  391.         mov     TsrActiveFlag, TRUE     ;   activate TSR
  392.         call    Activate
  393.         mov     TsrActiveFlag, FALSE
  394.         .ENDIF                          ; End carry flag check
  395.  
  396.         mov     intIdle.Flag, FALSE     ; Clear active flag
  397.         pop     ds                      ; Recover application's DS
  398.         .ENDIF                          ; End in-handler check
  399.  
  400.         iret
  401.  
  402. Idle    ENDP
  403.  
  404.  
  405. ;* Multiplex - Handler for Interrupt 2Fh (Multiplex Interrupt). Checks
  406. ;* AH for this TSR's identity number. If no match (indicating call is
  407. ;* not intended for this TSR), Multiplex passes control to the previous
  408. ;* Interrupt 2Fh handler.
  409. ;*
  410. ;* Params: AH = Handler identity number
  411. ;*         AL = Function number 0-2
  412. ;*
  413. ;* Return: AL    = 0FFh (function 0)
  414. ;*         ES:DI = Pointer to identifier string (function 0)
  415. ;*         ES:DI = Pointer to resident PSP segment (function 1)
  416. ;*         ES:DI = Pointer to shared memory (function 2)
  417.  
  418. Multiplex PROC  FAR
  419.  
  420.         .IF     ah != cs:IDnumber       ; If this handler not requested,
  421.         jmp     cs:intMultex.OldHand    ;   pass control to old Int 2Fh
  422.         .ENDIF                          ;   handler
  423.  
  424.         .IF     al == 0                 ; If function 0 (verify presence),
  425.         mov     al, 0FFh                ;   AL = 0FFh,
  426.         push    cs                      ;   ES = resident code segment
  427.         pop     es
  428.         mov     di, OFFSET IDstring     ;   DI = offset of identifier string
  429.  
  430.         .ELSEIF al == 1                 ; If function 1 (get PSP address),
  431.         mov     es, cs:TsrPspSeg        ;   ES:DI = far address of resident PSP
  432.         sub     di, di
  433.  
  434.         .ELSE
  435.         les     di, cs:ShareAddr        ; If function 2 (get shared memory),
  436.         .ENDIF                          ;   set ES:DI = far address
  437.  
  438. NoMultiplex LABEL  FAR                  ; Secondary entry for null Multiplex
  439.  
  440.         iret
  441.  
  442. Multiplex ENDP
  443.  
  444.  
  445. ;* CheckHotKey - Checks current keystroke for hot key. Called from Keybrd
  446. ;* handler if IBM PC/AT or compatible, or from MiscServ handler if PS/2.
  447. ;*
  448. ;* Uses:   HotScan, HotShift, HotMask, and SHFT_STAT
  449. ;*
  450. ;* Params: AL = Scan code
  451. ;*
  452. ;* Return: Carry flag set = FALSE; carry flag clear = TRUE
  453.  
  454. CheckHotKey PROC NEAR
  455.  
  456.         cmp     al, cs:HotScan          ; If current scan code isn't code
  457.         jne     e_exit                  ;   for hot key, exit with carry set
  458.  
  459.         push    es                      ; Else look into BIOS data area
  460.         sub     ax, ax                  ;   (segment 0) to check shift state
  461.         mov     es, ax
  462.         mov     al, es:[SHFT_STAT]      ; Get SHIFT-key flags
  463.         and     al, cs:HotMask          ; AND with "don't care" mask
  464.         cmp     al, cs:HotShift         ; Compare result with hot SHIFT key
  465.         pop     es
  466.         je      exit                    ; If match, exit with carry clear
  467.  
  468. e_exit: stc                             ; Set carry if not hot key
  469. exit:   ret
  470.  
  471. CheckHotKey ENDP
  472.  
  473.  
  474. ;* CheckRequest - Checks request flag and system status using the 
  475. ;* following logic:
  476. ;*
  477. ;*         IF (TsrRequestFlag AND (NOT TsrActiveFlag)
  478. ;*             AND DosStatus AND HardwareStatus)
  479. ;*             return TRUE
  480. ;*         ELSE
  481. ;*             return FALSE
  482. ;*
  483. ;* Uses:   TsrRequestFlag and TsrActiveFlag
  484. ;*
  485. ;* Params: DS = Resident code segment
  486. ;*
  487. ;* Return: Carry flag set = TRUE; carry flag clear = FALSE
  488.  
  489. CheckRequest PROC NEAR
  490.  
  491.         rol     TsrRequestFlag, 1       ; Rotate high bit into carry - set
  492.                                         ;   if TRUE (-1), clear if FALSE (0)
  493.         cmc                             ; NOT carry
  494.  
  495.         .IF     !carry?                 ; If TsrRequestFlag = TRUE:
  496.         ror     TsrActiveFlag, 1        ; Rotate low bit into carry - set
  497.                                         ;   if TRUE (-1), clear if FALSE (0)
  498.         .IF     !carry?                 ; If TsrActiveFlag = FALSE:
  499.         call    CheckDos                ; Is DOS in interruptable state?
  500.  
  501.         .IF     !carry?                 ; If so,
  502.         call    CheckHardware           ;   if hardware or BIOS unstable,
  503.         .ENDIF                          ;   set carry and exit
  504.         .ENDIF
  505.         .ENDIF
  506.         ret
  507.  
  508. CheckRequest ENDP
  509.  
  510.  
  511. ;* CheckDos - Checks status of MS-DOS using the following logic:
  512. ;*
  513. ;*         IF (NOT CritErr) AND ((NOT InDos) OR (Idle AND InDos))
  514. ;*             return DosStatus = TRUE
  515. ;*         ELSE
  516. ;*             return DosStatus = FALSE
  517. ;*
  518. ;* Uses:   CritErrAddr, InDosAddr, and intIdle
  519. ;*
  520. ;* Params: DS = Resident code segment
  521. ;*
  522. ;* Return: Carry flag set if MS-DOS is busy
  523.  
  524. CheckDos PROC   NEAR USES es bx ax
  525.  
  526.         les     bx, CritErrAddr
  527.         mov     ah, es:[bx]             ; AH = value of CritErr flag
  528.  
  529.         les     bx, InDosAddr
  530.         mov     al, es:[bx]             ; AL = value of InDos flag
  531.  
  532.         sub     bx, bx                  ; BH = 0, BL = 0
  533.         cmp     bl, intIdle.Flag        ; Carry flag set if call is from
  534.                                         ;   Interrupt 28h handler
  535.         rcl     bl, 1                   ; Rotate carry into BL: TRUE if Idle
  536.         cmp     bx, ax                  ; Carry flag clear if CritErr = 0
  537.                                         ;   and InDos <= BL
  538.         ret
  539.  
  540. CheckDos ENDP
  541.  
  542.  
  543. ;* CheckHardware - Checks status of BIOS and hardware using the
  544. ;* following logic:
  545. ;*
  546. ;*         IF HardwareActive OR KeybrdActive OR VideoActive OR DiskIOActive
  547. ;*             return HardwareStatus = FALSE
  548. ;*         ELSE
  549. ;*             return HardwareStatus = TRUE
  550. ;*
  551. ;* Uses:   intKeybrd, intVideo, and intDiskIO
  552. ;*
  553. ;* Params: DS = Resident code segment
  554. ;*
  555. ;* Return: Carry flag set if hardware or BIOS is busy
  556.  
  557. CheckHardware PROC NEAR USES ax
  558.  
  559. ; Verify hardware interrupt status by interrogating Intel 8259A
  560. ; Programmable Interrupt Controller
  561.  
  562.         mov     ax, 00001011y           ; AL = 0CW3 for Intel 8259A
  563.                                         ;   (RR = 1, RIS = 1)
  564.         out     20h, al                 ; Request 8259A in-service register
  565.         jmp     delay                   ; Wait a few cycles
  566. delay:
  567.         in      al, 20h                 ; AL = hardware interrupts being
  568.         cmp     ah, al                  ;   serviced (bit = 1 if in service)
  569.  
  570.         .IF     !carry?                 ; If no hard interrupts in service,
  571.         sub     al, al                  ;   verify BIOS interrupts not active,
  572.         cmp     al, intKeybrd.Flag      ;   check Interrupt 09 handler
  573.  
  574.         .IF     !carry?                 ; If Int 09 not active,
  575.         cmp     al, intVideo.Flag       ;   check Interrupt 10h handler
  576.  
  577.         .IF     !carry?                 ; If Int 10h not active,
  578.         cmp     al, intDiskIO.Flag      ;   check Interrupt 13h handler,
  579.         .ENDIF                          ;   return with carry set if
  580.         .ENDIF                          ;   Interrupt 09, 10h, or 13h
  581.         .ENDIF                          ;   is active
  582.  
  583.         ret
  584.  
  585. CheckHardware ENDP
  586.  
  587.  
  588. ;* Activate - Sets up for far call to TSR with the following steps:
  589. ;*
  590. ;*   1.  Stores stack pointer SS:SP and switches to new stack
  591. ;*   2.  Pushes registers onto new stack
  592. ;*   3.  Stores vectors for Interrupts 1Bh, 23h, and 23h, and
  593. ;*       replaces them with addresses of error-trapping handlers
  594. ;*   4.  Stores DOS CTRL+C checking flag, then turns off checking
  595. ;*   5.  If required, stores DTA address and switches to new DTA
  596. ;*
  597. ;* When TSR returns, restores all the above.
  598. ;*
  599. ;* Uses:   Reads or writes the following globals:
  600. ;*         OldStackAddr, TrapArray, BreakCheckFlag, TsrRequestFlag
  601. ;*
  602. ;* Params: DS = Resident code segment
  603. ;*
  604. ;* Return: None
  605.  
  606. Activate PROC   NEAR
  607.  
  608. ; Step 1.  Set up a new stack
  609.  
  610.         mov     WORD PTR OldStackAddr[0], sp    ; Save current 
  611.         mov     WORD PTR OldStackAddr[2], ss    ;   stack pointer
  612.  
  613.         cli                                     ; Turn off interrupts while
  614.         push    cs                              ;   changing stack
  615.         pop     ss                              ; New stack begins
  616.         mov     sp, OFFSET NewStack             ;   at LABEL NewStack
  617.         sti
  618.  
  619. ; Step 2.  Preserve registers (DS already saved in Clock or Idle)
  620.  
  621.         push    ax
  622.         push    bx
  623.         push    cx
  624.         push    dx
  625.         push    si
  626.         push    di
  627.         push    bp
  628.         push    es
  629.  
  630.         cld                                     ; Clear direction flag
  631.  
  632. ; Step 3.  Set up trapping handlers for keyboard breaks and DOS
  633. ; critical errors (Interrupts 1Bh, 23h, and 24h)
  634.  
  635.         mov     cx, CTRAP                       ; CX = number of handlers
  636.         mov     si, OFFSET TrapArray            ; DS:SI points to trap array
  637.  
  638.         .REPEAT
  639.         mov     al, [si]                        ; AL = interrupt number
  640.         mov     ah, 35h                         ; Request DOS Function 35h
  641.         int     21h                             ; Get interrupt vector (ES:BX)
  642.         mov     WORD PTR [si].INTR.OldHand[0], bx ; Save far address of
  643.         mov     WORD PTR [si].INTR.OldHand[2], es ;   application's handler
  644.         mov     dx, WORD PTR [si].INTR.NewHand[0] ; DS:DX points to TSR's hand
  645.         mov     ah, 25h                         ; Request DOS Function 25h
  646.         int     21h                             ; Set interrupt vector
  647.         add     si, SIZEOF INTR                 ; DS:SI points to next in list
  648.         .UNTILCXZ
  649.  
  650. ; Step 4.  Disable MS-DOS break checking during disk I/O
  651.  
  652.         mov     ax, 3300h               ; Request DOS Function 33h
  653.         int     21h                     ; Get CTRL+BREAK flag in DL
  654.         mov     BreakCheckFlag, dl      ; Preserve it
  655.  
  656.         sub     dl, dl                  ; DL = 0 to disable I/O break checking
  657.         mov     ax, 3301h               ; Request DOS Function 33h
  658.         int     21h                     ; Set CTRL+BREAK flag from DL
  659.  
  660. ; Step 5.  If TSR requires a disk transfer area, store address of current
  661. ; DTA and switch buffer address to this segment. See Help and section 
  662. ; "Preserving Existing Data" in Chapter 11 of the Programmer's Guide for
  663. ; more information about the DTA. 
  664.  
  665.         IFDEF   DTA_SIZ
  666.         mov     ah, 2Fh                         ; Request DOS Function 2Fh
  667.         int     21h                             ; Get DTA Address into ES:BX
  668.         mov     WORD PTR OldDtaAddr[0], bx      ; Store address
  669.         mov     WORD PTR OldDtaAddr[2], es
  670.  
  671.         mov     dx, OFFSET DtaBuff              ; DS:DX points to new DTA
  672.         mov     ah, 1Ah                         ; Request DOS Function 1Ah
  673.         int     21h                             ; Set DTA Address
  674.         ENDIF
  675.  
  676. ; Call main body of TSR.
  677.  
  678.         mov     ax, @data
  679.         mov     ds, ax                          ; Initialize DS and ES
  680.         mov     es, ax                          ;   to data segment
  681.  
  682.         call    cs:TsrAddr                      ; Call main part of TSR
  683.  
  684.         push    cs
  685.         pop     ds                              ; Reset DS to this segment
  686.  
  687. ; Undo step 5.  Restore previous DTA (if required)
  688.  
  689.         IFDEF   DTA_SIZ
  690.         push    ds                      ; Preserve DS
  691.         lds     dx, OldDtaAddr          ; DS:DX points to application's DTA
  692.         mov     ah, 1Ah                 ; Request DOS Function 1Ah
  693.         int     21h                     ; Set DTA Address
  694.         pop     ds
  695.         ENDIF
  696.  
  697. ; Undo step 4.  Restore previous MS-DOS break checking
  698.  
  699.         mov     dl, BreakCheckFlag      ; DL = previous break state
  700.         mov     ax, 3301h               ; Request DOS Function 33h
  701.         int     21h                     ; Set CTRL+BREAK flag from DL
  702.  
  703. ; Undo step 3.  Restore previous vectors for error-trapping handlers
  704.  
  705.         mov     cx, CTRAP
  706.         mov     di, OFFSET TrapArray
  707.         push    ds                      ; Preserve DS
  708.         push    ds                      ; ES = resident code segment
  709.         pop     es
  710.  
  711.         .REPEAT
  712.         mov     al, es:[di]             ; AL = interrupt number
  713.         lds     dx, es:[di].INTR.OldHand; DS:DX points to application's handler
  714.         mov     ah, 25h                 ; Request DOS Function 25h
  715.         int     21h                     ; Set interrupt vector from DS:DX
  716.         add     di, SIZEOF INTR         ; ES:DI points to next in list
  717.         .UNTILCXZ
  718.         pop     ds
  719.  
  720. ; Undo step 2.  Restore registers from stack
  721.  
  722.         pop     es
  723.         pop     bp
  724.         pop     di
  725.         pop     si
  726.         pop     dx
  727.         pop     cx
  728.         pop     bx
  729.         pop     ax
  730.  
  731. ; Undo step 1.  Restore address of original stack to SS:SP
  732.  
  733.         cli
  734.         mov     sp, WORD PTR OldStackAddr[0]
  735.         mov     ss, WORD PTR OldStackAddr[2]
  736.         sti
  737.  
  738. ; Clear request flag and return to caller (Clock or Idle procedure)
  739.  
  740.         mov     TsrRequestFlag, FALSE
  741.         ret
  742.  
  743. Activate ENDP
  744.  
  745.  
  746.  
  747. ;* INSTALLATION SECTION - The following code is executed only during
  748. ;* the TSR's installation phase. When the program terminates through
  749. ;* Function 31h, the above code and data remain resident; memory
  750. ;* occupied by the following code segment is returned to the operating
  751. ;* system.
  752.  
  753. DGROUP  GROUP INSTALLCODE
  754.  
  755. INSTALLCODE SEGMENT PARA PUBLIC 'CODE2'
  756.         ASSUME  ds:@code
  757.  
  758. ;* Install - Prepares for installation of a TSR by chaining interrupt
  759. ;* handlers and initializing pointers to DOS flags. Install does not
  760. ;* call the Terminate-and-Stay-Resident function.
  761. ;*
  762. ;* This library of routines accommodates both keyboard-activated and
  763. ;* time-activated TSRs. The latter are TSRs that activate at a preset
  764. ;* time. If the first parameter (Param1) is a valid scan code, Install
  765. ;* assumes the TSR is activated from the keyboard and sets up a keyboard
  766. ;* handler to search for the hot key. If Param1 is null, Install assumes
  767. ;* the next two parameters (Param2 and Param3) are respectively the hour
  768. ;* and minute at which the TSR is to activate. In this case, Install 
  769. ;* calls GetTimeToElapse to initialize the variable CountDown and sets
  770. ;* up KeybrdMonitor as the keyboard handler. CountDown and the secondary
  771. ;* counter Tick91 serve here the same functions as they do for the
  772. ;* ALARM.ASM program presented in Chapter 11 of the Programmer's Guide.
  773. ;* Install is callable from a high-level language.
  774. ;*
  775. ;* Uses:   InDosAddr, CritErrAddr, CHAND,
  776. ;*         HandArray, CTRAP, TrapArray
  777. ;*
  778. ;*                  Keyboard-activated                 Time-activated
  779. ;*                  ------------------                 --------------
  780. ;* Params: Param1 - Scan code for hot key              0
  781. ;*         Param2 - Bit value for shift hot key        Hour to activate
  782. ;*         Param3 - Bit mask for shift hot key         Minute to activate
  783. ;*         Param4 - Far address of main TSR procedure  (same)
  784. ;*
  785. ;* Return: AX = 0 if successful, or one of the following codes:
  786. ;*         IS_INSTALLED           FLAGS_NOT_FOUND        NO_IDNUM
  787. ;*         ALREADY_INSTALLED      WRONG_DOS
  788.  
  789. Install PROC    FAR USES ds si di,
  790.         Param1:WORD, Param2:WORD, Param3:WORD, Param4:FAR PTR FAR
  791.  
  792.         mov     ax, @code
  793.         mov     ds, ax                          ; Point DS to code segment
  794.  
  795. ; Get and store parameters passed from main program module
  796.  
  797.         mov     al, BYTE PTR Param1
  798.         mov     HotScan, al                     ; Store hot-key scan code
  799.         mov     al, BYTE PTR Param2             ;   or flag for time activate
  800.         mov     HotShift, al                    ; Store hot-key shift value
  801.         mov     al, BYTE PTR Param3             ;   or hour value
  802.         mov     HotMask, al                     ; Store hot-key shift mask
  803.                                                 ;   or minute value
  804.         mov     ax, WORD PTR Param4[0]
  805.         mov     bx, WORD PTR Param4[2]
  806.         mov     WORD PTR TsrAddr[0], ax         ; Store segment:offset of
  807.         mov     WORD PTR TsrAddr[2], bx         ;   TSR's main code
  808.  
  809. ; Get addresses of DOS flags, then check for prior installation
  810.  
  811.         call    GetDosFlags             ; Find DOS service flags
  812.         or      ax, ax
  813.         jnz     exit                    ; If flags not found, quit
  814.  
  815.         sub     al, al                  ; Request multiplex function 0
  816.         call    CallMultiplex           ; Invoke Interrupt 2Fh
  817.         cmp     ax, NOT_INSTALLED       ; Check for presence of resident TSR
  818.  
  819.         .IF     !zero?                  ; If TSR is installed,
  820.         cmp     ax, IS_INSTALLED        ;   return with appropriate
  821.         jne     exit                    ;   error code
  822.         mov     ax, ALREADY_INSTALLED
  823.         jmp     exit
  824.         .ENDIF
  825.  
  826. ; Check if TSR is to activate at the hour:minute specified by Param2:Param3.
  827. ; If so, determine the number of 5-second intervals that must elapse before
  828. ; activation, then set up the code at the far LABEL KeybrdMonitor to serve
  829. ; as the keyboard handler.
  830.  
  831.         .IF     HotScan == 0            ; If valid scan code given,
  832.         mov     ah, HotShift            ;   AH = hour to activate
  833.         mov     al, HotMask             ;   AL = minute to activate
  834.         call    GetTimeToElapse         ; Get number of 5-second intervals
  835.         mov     CountDown, ax           ;   to elapse before activation
  836.  
  837.         .ELSE                           ; Force use of KeybrdMonitor as
  838.                                         ;   keyboard handler
  839.         cmp     Version, 031Eh          ; DOS Version 3.3 or higher?
  840.         jb      setup                   ; No?  Skip next step
  841.  
  842. ; Test for IBM PS/2 series. If not PS/2, use Keybrd and SkipMiscServ as
  843. ; handlers for Interrupts 09 and 15h respectively. If PS/2 system, set up
  844. ; KeybrdMonitor as the Interrupt 09 handler. Audit keystrokes with MiscServ
  845. ; handler, which searches for the hot key by handling calls to Interrupt 15h
  846. ; (Miscellaneous System Services). For more information about keyboard
  847. ; handlers, refer to the section "Auditing Hardware Events for TSR Requests"
  848. ; in Chapter 11 of the Programmer's Guide
  849.  
  850.         mov     ax, 0C00h               ; Function 0Ch (Get System
  851.         int     15h                     ;   Configuration Parameters)
  852.         sti                             ; Compaq ROM may leave interrupts
  853.                                         ;   disabled
  854.  
  855.         jc      setup                   ; If carry set,
  856.         or      ah, ah                  ;   or if AH not 0,
  857.         jnz     setup                   ;   services are not supported
  858.  
  859.         test    BYTE PTR es:[bx+5], 00010000y   ; Test bit 4 to see if
  860.         jz      setup                           ;   intercept is implemented
  861.  
  862.         mov     ax, OFFSET MiscServ             ; If so, set up MiscServ as
  863.         mov     WORD PTR intMisc.NewHand, ax    ;   Interrupt 15h handler
  864.         .ENDIF
  865.  
  866.         mov     ax, OFFSET KeybrdMonitor        ; Set up KeybrdMonitor as
  867.         mov     WORD PTR intKeybrd.NewHand, ax  ;   Interrupt 09 handler
  868.  
  869. ; Interrupt structure is now initialized for either PC/AT or PS/2 system.
  870. ; Get existing handler addresses from interrupt vector table, store in
  871. ; OldHand member, and replace with addresses of new handlers.
  872.  
  873. setup:
  874.         mov     cx, CHAND               ; CX = count of handlers
  875.         mov     si, OFFSET HandArray    ; SI = offset of handler structures
  876.  
  877.         .REPEAT
  878.         mov     ah, 35h                 ; Request DOS Function 35h
  879.         mov     al, [si]                ; AL = interrupt number
  880.         int     21h                     ; Get interrupt vector in ES:BX
  881.         mov     WORD PTR [si].INTR.OldHand[0], bx ; Save far address
  882.         mov     WORD PTR [si].INTR.OldHand[2], es ;  of current handler
  883.         mov     dx, WORD PTR [si].INTR.NewHand[0] ; DS:DX points to TSR handler
  884.         mov     ah, 25h                 ; Request DOS Function 25h
  885.         int     21h                     ; Set interrupt vector from DS:DX
  886.         add     si, SIZEOF INTR         ; DS:SI points to next in list
  887.         .UNTILCXZ
  888.  
  889.         sub     ax, ax                  ; Clear return code
  890. exit:
  891.         ret                             ; Return to caller
  892.  
  893. Install ENDP
  894.  
  895.  
  896. ;* Deinstall - Prepares for deinstallation of a TSR. Deinstall is the
  897. ;* complement of the Install procedure. It restores to the vector table
  898. ;* the original addresses replaced during installation, thus unhooking
  899. ;* the TSR's handlers. Checks to see if another TSR has installed handlers
  900. ;* for the interrupts in array HandArray. If so, the procedure fails with
  901. ;* an appropriate error code. Callable from a high-level language.
  902. ;*
  903. ;* Params: None
  904. ;*
  905. ;* Return: AX = Segment address of resident portion's PSP or 
  906. ;*              one of the following error codes:
  907. ;*              CANT_DEINSTALL           WRONG_DOS
  908.                 
  909. Deinstall PROC  FAR USES ds si di
  910.  
  911.         mov     ax, @code
  912.         mov     ds, ax                  ; Point DS to code segment
  913.  
  914.         sub     al, al                  ; Request multiplex function 0
  915.         call    CallMultiplex           ; Get resident code segment in ES
  916.  
  917.         cmp     ax, IS_INSTALLED        ; If not resident,
  918.         jne     exit                    ;   exit with error
  919.         push    es                      ; Else point DS to
  920.         pop     ds                      ;   resident code segment
  921.         mov     cx, CHAND               ; Count of handlers
  922.         mov     si, OFFSET HandArray    ; SI points to handler structures
  923.  
  924. ; Read current vectors for TSR's interrupt handlers and compare with far
  925. ; addresses. If mismatch, another TSR has installed new handlers and ours
  926. ; cannot be safely deinstalled.
  927.  
  928.         .REPEAT
  929.         mov     al, [si]                ; AL = interrupt number
  930.         mov     ah, 35h                 ; Request DOS Function 35h
  931.         int     21h                     ; Get interrupt vector in ES:BX
  932.         cmp     bx, WORD PTR [si].INTR.NewHand[0] ; If offset different,
  933.         jne     e_exit                            ;   error
  934.         mov     ax, es
  935.         cmp     ax, WORD PTR [si].INTR.NewHand[2] ; If segment different,
  936.         jne     e_exit                            ;   error
  937.         add     si, SIZEOF INTR         ; DS:SI points to next in list
  938.         .UNTILCXZ
  939.  
  940. ; If no interrupts replaced, call TSR's multiplex handler to locate
  941. ; address of resident portion's PSP. Although the PSP is not required
  942. ; until memory is returned to DOS, the call must be done now before
  943. ; unhooking the multiplex handler.
  944.  
  945.         mov     al, 1                   ; Request multiplex function 1
  946.         call    CallMultiplex           ; Get resident code's PSP in ES
  947.         push    es                      ; Save it
  948.  
  949. ; Unhook all handlers by restoring the original vectors to vector table.
  950.  
  951.         mov     cx, CHAND               ; Count of installed handlers
  952.         mov     si, OFFSET HandArray    ; SI points to handler structures
  953.  
  954.         .REPEAT
  955.         mov     al, [si]                ; AL = interrupt number
  956.         push    ds                      ; Preserve DS segment
  957.         lds     dx, [si].INTR.OldHand   ; Put vector in DS:DX
  958.         mov     ah, 25h                 ; Request DOS Function 25h
  959.         int     21h                     ; Set interrupt vector from DS:DX
  960.         pop     ds
  961.         add     si, SIZEOF INTR         ; DS:SI points to next in list
  962.         .UNTILCXZ
  963.  
  964.         pop     ax                      ; Return address of resident PSP
  965.         jmp     exit                    ;  to signal success
  966. e_exit:
  967.         mov     ax, CANT_DEINSTALL
  968. exit:
  969.         ret
  970.  
  971. Deinstall ENDP
  972.  
  973.  
  974. ;* GetVersion - Gets the DOS version and stores it in a global variable as
  975. ;* well as returning it in AX.
  976. ;*
  977. ;* Uses:   Version
  978. ;*
  979. ;* Params: DS = Resident code segment
  980. ;*
  981. ;* Return: AH = Major version
  982. ;*         AL = Minor version
  983.  
  984. GetVersion PROC NEAR
  985.  
  986.         mov     ax, 3000h               ; Request DOS Function 30h
  987.         int     21h                     ; Get MS-DOS version number
  988.         .IF     al < 2                  ; If Version 1.x:
  989.         mov     ax, WRONG_DOS           ; Abort with WRONG_DOS as error code
  990.         .ELSE
  991.         xchg    ah, al                  ; AH = major, AL = minor version
  992.         mov     Version, ax             ; Save in global
  993.         .ENDIF
  994.         ret
  995.  
  996. GetVersion ENDP
  997.  
  998.  
  999. ;* GetDosFlags - Gets pointers to the DOS InDos and Critical Error flags.
  1000. ;*
  1001. ;* Params: DS = Resident code segment
  1002. ;*
  1003. ;* Return: 0 if successful, or the following error code:
  1004. ;*         FLAGS_NOT_FOUND
  1005.  
  1006. GetDosFlags PROC NEAR
  1007.  
  1008. ; Get InDOS address from MS-DOS
  1009.  
  1010.         mov     ah, 34h                         ; Request DOS Function 34h
  1011.         int     21h                             ; Get Address of InDos flag
  1012.         mov     WORD PTR InDosAddr[0], bx       ; Store address (ES:BX)
  1013.         mov     WORD PTR InDosAddr[2], es       ;   for later access
  1014.  
  1015. ; Determine address of Critical Error Flag
  1016.  
  1017.         mov     ax, Version             ; AX = DOS version number
  1018.  
  1019. ; If DOS 3.1 or greater and not OS/2 compatibility mode, Critical Error
  1020. ; flag is in byte preceding InDOS flag 
  1021.         .IF     (ah < 10) && (ah >= 3) && (al >= 10)
  1022.         dec     bx                      ; BX points to byte before InDos flag
  1023.  
  1024.         .ELSE
  1025. ; For earlier versions, the only reliable method is to scan through
  1026. ; DOS to find an INT 28h instruction in a specific context.
  1027.  
  1028.         mov     cx, 0FFFFh              ; Maximum bytes to scan
  1029.         sub     di, di                  ; ES:DI = start of DOS segment
  1030.  
  1031. INT_28  EQU     028CDh
  1032.  
  1033.         .REPEAT
  1034.         mov     ax, INT_28              ; Load opcode for INT 28h
  1035.  
  1036.         .REPEAT
  1037.         repne   scasb                   ; Scan for first byte of opcode
  1038.  
  1039.         .IF     !zero?
  1040.         mov     ax, FLAGS_NOT_FOUND     ; Return error if not found
  1041.         jmp     exit
  1042.         .ENDIF
  1043.         .UNTIL  ah == es:[di]           ; For each matching first byte,
  1044.                                         ;   check the second byte until match
  1045.  
  1046. ; See if INT 28h is in this context:
  1047. ;                                       ;     (-7)    (-5)
  1048. ;       CMP     ss:[CritErrFlag], 0     ;  36, 80, 3E,  ?,  ?,  0
  1049. ;       JNE     NearLabel               ;  75,  ?
  1050.         int     28h                     ;  CD, 28
  1051. ;                                       ;  (0) (1)
  1052. CMP_SS    EQU   3E80h
  1053. P_CMP_SS  EQU   8
  1054. P_CMP_OP  EQU   6
  1055.  
  1056.         mov     ax, CMP_SS              ; Load and compare opcode to CMP
  1057.         .IF     ax == es:[di-P_CMP_SS]  ; If match:
  1058.         mov     bx, es:[di-P_CMP_OP]    ; BX = offset of
  1059.         jmp     exit                    ;   Critical Error flag
  1060.         .ENDIF
  1061.  
  1062. ; See if INT 28h is in this context:
  1063. ;                                       ;     (-12)   (-10)
  1064. ;       TEST    ?s:[CritErr], 0FFh      ;  ?6  F6, 06,  ?,  ?, FF
  1065. ;       JNE     NearLabel               ;  75, ?
  1066. ;       PUSH    ss:[CritErrFlag]        ;  36, FF, 36,  ?,  ?
  1067.         int     28h                     ;  CD, 28
  1068. ;                                       ;  (0) (1)
  1069. TEST_SS   EQU   06F6h
  1070. P_TEST_SS EQU   13
  1071. P_TEST_OP EQU   11
  1072.  
  1073.         mov     ax, TEST_SS             ; Load AX = opcode for TEST
  1074.         .UNTIL  ax == es:[di-P_TEST_SS] ; If not TEST, continue scan
  1075.  
  1076.         mov     bx, es:[di-P_TEST_OP]   ; Else load BX with offset of
  1077.         .ENDIF                          ;   Critical Error flag
  1078. exit:
  1079.         mov     WORD PTR CritErrAddr[0], bx     ; Store address of
  1080.         mov     WORD PTR CritErrAddr[2], es     ;   Critical Error flag
  1081.         sub     ax, ax                          ; Clear error code
  1082.         ret
  1083.  
  1084. GetDosFlags ENDP
  1085.  
  1086.  
  1087. ;* GetTimeToElapse - Determines number of 5-second intervals that
  1088. ;* must elapse between specified hour:minute and current time.
  1089. ;*
  1090. ;* Params: AH = Hour
  1091. ;*         AL = Minute
  1092. ;*
  1093. ;* Return: AX = Number of 5-second intervals
  1094.  
  1095. GetTimeToElapse PROC NEAR 
  1096.  
  1097.         push    ax                      ; Save hour:minute
  1098.         mov     ah, 2Ch                 ; Request DOS Function 2Ch
  1099.         int     21h                     ; Get Time (CH:CL = hour:minute)
  1100.         pop     bx                      ; Recover hour:minute
  1101.         mov     dl, dh
  1102.         sub     dh, dh
  1103.         push    dx                      ; Save DX = current seconds
  1104.  
  1105.         mov     al, 60                  ; 60 minutes/hour
  1106.         mul     bh                      ; Multiply by specified hour
  1107.         sub     bh, bh
  1108.         add     bx, ax                  ; BX = minutes from midnight
  1109.                                         ;   to activation time
  1110.         mov     al, 60                  ; 60 minutes/hour
  1111.         mul     ch                      ; Multiply by current hour
  1112.         sub     ch, ch
  1113.         add     ax, cx                  ; AX = minutes from midnight
  1114.                                         ;   to current time
  1115.         sub     bx, ax                  ; BX = minutes to elapse before
  1116.         .IF     carry?                  ; If activation is tomorrow,
  1117.         add     bx, 24 * 60             ;   add number of minutes per day
  1118.         .ENDIF
  1119.  
  1120.         mov     ax, 60
  1121.         mul     bx                      ; DX:AX = minutes-to-elapse-times-60
  1122.         pop     bx                      ; Recover current seconds
  1123.         sub     ax, bx                  ; DX:AX = seconds to elapse before
  1124.         sbb     dx, 0                   ;   activation
  1125.         .IF     carry?                  ; If negative,
  1126.         mov     ax, 5                   ;   assume 5 seconds
  1127.         cwd
  1128.         .ENDIF
  1129.  
  1130.         mov     bx, 5                   ; Divide result by 5 seconds
  1131.         div     bx                      ; AX = number of 5-second intervals
  1132.         ret
  1133.  
  1134. GetTimeToElapse ENDP
  1135.  
  1136.  
  1137. ;* CallMultiplex - Calls the Multiplex Interrupt (Interrupt 2Fh).
  1138. ;*
  1139. ;* Uses:   IDstring
  1140. ;*
  1141. ;* Params: AL = Function number for multiplex handler
  1142. ;*
  1143. ;* Return: AX    = One of the following return codes:
  1144. ;*                 NOT_INSTALLED      IS_INSTALLED       NO_IDNUM
  1145. ;*         ES:DI = Resident code segment:identifier string (function 0)
  1146. ;*         ES:DI = Resident PSP segment address (function 1)
  1147. ;*         ES:DI = Far address of shared memory (function 2)
  1148.  
  1149. CallMultiplex PROC FAR USES ds
  1150.  
  1151.         push    ax                      ; Save function number
  1152.         mov     ax, @code
  1153.         mov     ds, ax                  ; Point DS to code segment
  1154.  
  1155. ; First, check 2Fh vector. DOS Version 2.x may leave the vector null
  1156. ; if PRINT.COM is not installed. If vector is null, point it to IRET
  1157. ; instruction at LABEL NoMultiplex. This allows the new multiplex
  1158. ; handler to pass control, if necessary, to a proper existing routine.
  1159.  
  1160.         mov     ax, 352Fh               ; Request DOS Function 35h
  1161.         int     21h                     ; Get interrupt vector in ES:BX
  1162.         mov     ax, es
  1163.         or      ax, bx
  1164.         .IF     zero?                   ; If Null vector,
  1165.         mov     dx, OFFSET NoMultiplex  ;   set vector to IRET instruction
  1166.         mov     ax, 252Fh               ;   at LABEL NoMultiplex
  1167.         int     21h                     ; Set interrupt vector
  1168.         .ENDIF
  1169.  
  1170. ; Second, call Interrupt 2Fh with function 0 (presence request). Cycle
  1171. ; through allowable identity numbers (192 to 255) until TSR's multiplex
  1172. ; handler returns ES:DI = IDstring to verify its presence or until call
  1173. ; returns AL = 0, indicating the TSR is not installed.
  1174.  
  1175.         mov     dh, 192                 ; Start with identity number = 192
  1176.  
  1177.         .REPEAT
  1178.         mov     ah, dh                  ; Call Multiplex with AH = trial ID
  1179.         sub     al, al                  ;   and AL = function 0
  1180.         push    dx                      ; Save DH and DS in case call
  1181.         push    ds                      ;   destroys them
  1182.         int     2Fh                     ; Multiplex
  1183.         pop     ds                      ; Recover DS and
  1184.         pop     dx                      ;   current ID number in DH
  1185.         or      al, al                  ; Does a handler claim this ID number?
  1186.         jz      no                      ; If not, stop search
  1187.  
  1188.         .IF     al == 0FFh              ; If handler ready to process calls,
  1189.         mov     si, OFFSET IDstring     ;   point DS:SI to ID string, compare
  1190.         mov     cx, IDstrlen            ;   with string at ES:DI returned
  1191.         repe    cmpsb                   ;   by multiplex handler
  1192.         je      yes                     ; If equal, TSR's handler is found
  1193.         .ENDIF
  1194.  
  1195.         inc     dh                      ; This handler is not the one
  1196.         .UNTIL  zero?                   ; Try next identity number up to 255
  1197.  
  1198.         mov     ax, NO_IDNUM            ; In the unlikely event that numbers
  1199.         jmp     e_exit                  ;   192-255 are all taken, quit
  1200.  
  1201. ; Third, assuming handler is found and verified, process the multiplex
  1202. ; call with the requested function number.
  1203.  
  1204. yes:
  1205.         pop     ax                      ; AL = original function number
  1206.         mov     ah, dh                  ; AH = identity number
  1207.         int     2Fh                     ; Multiplex
  1208.         mov     ax, IS_INSTALLED        ; Signal that handler has been found
  1209.         jmp     exit                    ;   and quit
  1210.  
  1211. ; Reaching this section means multiplex handler (and TSR) not installed.
  1212. ; Since the value in DH is not claimed by any handler, it will be used as
  1213. ; the resident TSR's identity number.  Save the number in resident code
  1214. ; segment so multiplex handler can find it.
  1215.  
  1216. no:
  1217.         mov     IDnumber, dh            ; Save multiplex identity number
  1218.         mov     ax, NOT_INSTALLED       ; Signal handler is not installed
  1219. e_exit:
  1220.         pop     bx                      ; Remove function number from stack
  1221. exit:
  1222.         ret
  1223.  
  1224. CallMultiplex ENDP
  1225.  
  1226.  
  1227. ;* InitTsr - Initializes DOS version variables and multiplex data with
  1228. ;* following parameters. This procedure must execute before calling
  1229. ;* either the Install, Deinstall, or CallMultiplex procedures. Callable
  1230. ;* from a high-level language.
  1231. ;*
  1232. ;* Uses:   IDstring
  1233. ;*
  1234. ;* Params: PspParam - Segment address of PSP
  1235. ;*         StrParam - Far address of TSR's identifier string
  1236. ;*         ShrParam - Far address of shared memory
  1237. ;*
  1238. ;* Return: AX = WRONG_DOS if not DOS Version 2.0 or higher
  1239.  
  1240. InitTsr PROC    FAR USES ds es si di,
  1241.         PspParam:WORD, StrParam:FPVOID, ShrParam:FPVOID
  1242.  
  1243.         mov     ax, @code
  1244.         mov     ds, ax                          ; Point DS and ES
  1245.         mov     es, ax                          ;   to code segment
  1246.  
  1247. ; Get and store parameters passed from main program module
  1248.  
  1249.         mov     ax, PspParam
  1250.         mov     TsrPspSeg, ax                   ; Store PSP segment address
  1251.  
  1252.         mov     ax, WORD PTR ShrParam[0]
  1253.         mov     bx, WORD PTR ShrParam[2]
  1254.         mov     WORD PTR ShareAddr[0], ax       ; Store far address of
  1255.         mov     WORD PTR ShareAddr[2], bx       ;   shared memory
  1256.  
  1257.         push    ds
  1258.         mov     si, WORD PTR StrParam[0]        ; DS:SI points to multiplex
  1259.         mov     ax, WORD PTR StrParam[2]        ;   identifier string
  1260.         mov     ds, ax
  1261.         mov     di, OFFSET IDstring             ; Copy string to IDstring
  1262.         mov     cx, STR_LEN                     ;   at ES:DI so multiplex
  1263.                                                 ;   handler has a copy
  1264.         .REPEAT
  1265.         lodsb                                   ; Copy STR_LEN characters
  1266.         .BREAK .IF al == 0                      ;   or until null terminator
  1267.         stosb                                   ;   found
  1268.         .UNTILCXZ
  1269.  
  1270.         pop     ds                              ; Recover DS = code segment
  1271.         mov     ax, STR_LEN
  1272.         sub     ax, cx
  1273.         mov     IDstrlen, ax                    ; Store string length
  1274.  
  1275.         INVOKE  GetVersion                      ; Return AX = version number
  1276.         ret                                     ;   or WRONG_DOS
  1277.  
  1278. InitTsr ENDP
  1279.  
  1280.  
  1281. INSTALLCODE ENDS
  1282.  
  1283.         END
  1284.