home *** CD-ROM | disk | FTP | other *** search
/ Microsoft Programmer's Library 1.3 / Microsoft-Programers-Library-v1.3.iso / sampcode / masm / masm6 / tsr / handlers.asm < prev    next >
Encoding:
Assembly Source File  |  1990-11-16  |  50.7 KB  |  1,289 lines

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