;******************************************************
;		   TPTSR.ASM 4.03
;	       TSR Management routines
;	 Copyright (c) TurboPower Software 1987.
; Portions copyright (c) Sunny Hill Software 1985, 1986
;     and used under license to TurboPower Software
;		 All rights reserved.
;******************************************************

	INCLUDE TPCOMMON.INC

;****************************************************** Equates

MaxScanCode	=	83		;Do not change
IfcSignature	=	0F0F0h		;Do not change
NextIfc 	=	14		;Offset of NextIfc field in IFC record

;****************************************************** Data


DATA	SEGMENT BYTE PUBLIC

	;Pascal variables

	EXTRN	PopupAddrs : BYTE		;Addresses of popup routines
	EXTRN	PopupStacks : BYTE		;Stacks for popup routines
	EXTRN	PopupInUse : BYTE		;Flags for popups in use
	EXTRN	PopupKeys : BYTE		;Trigger keys for popups
	EXTRN	ShiftKeys : BYTE		;Shift keys for popups
	EXTRN	DosWaitFlags : BYTE		;Flags popups that need DOS
	EXTRN	PopTickerPtr : DWORD		;Points to PopTicker
	EXTRN	PopupsEnabledPtr : DWORD	;Points to PopupsEnabled
	EXTRN	PopupToCallPtr : DWORD		;Points to PopupToCall
	EXTRN	PrefixSeg : WORD		;Our PSP segment
	EXTRN	DosVersion : WORD		;Version of DOS
	EXTRN	ThisIfc : BYTE			;Standard interface record
	EXTRN	IfcInstalled : BYTE		;True if we're in charge of IFC

	;internal variables

	DosTrapsSet	DB	?		;True if special trapping of DOS
						;INT vectors, etc. has been done
	LastEntrySS	DW	?		;Data needed for EmergencyExit
	LastEntrySP	DW	?
	LastEntryIP	DW	?
	IsEnhanced	DB	0		;1 if it's an enhanced keyboard

DATA	ENDS

;****************************************************** Code

CODE	SEGMENT BYTE PUBLIC

	ASSUME	CS:CODE,DS:DATA

	PUBLIC	Int5, Int8, Int9, Int10, Int13, Int14, Int16, Int17, Int25,
	PUBLIC	Int26, Int28, InitTsrPtrs, Int24Result, EmergencyExit

	;Pascal routines

	EXTRN	IoResultPrim:NEAR	;Pascal routine

;Note that these variables are all in the code segment

;When both of these variables point to zero, DOS may be called
DosInUsePtr	Pointer <>		;True if DOS is in use
DosCriticalPtr	Pointer <>		;True if DOS is critical

;Old interrupt vectors
OldInt05	Pointer <>		;Old INT $05 handler (PrtSc)
OldInt08	Pointer <>		;Old INT $08 handler (clock)
OldInt09	Pointer <>		;Old INT $09 handler (keyboard)
OldInt10	Pointer <>		;Old INT $10 handler (video)
OldInt13	Pointer <>		;Old INT $13 handler (disk)
OldInt14	Pointer <>		;Old INT $14 handler (comm.)
OldInt16	Pointer <>		;Old INT $16 handler (keyboard)
OldInt17	Pointer <>		;Old INT $17 handler (printer)
OldInt25	Pointer <>		;Old INT $25 handler (abs. disk read)
OldInt26	Pointer <>		;Old INT $26 handler (abs. disk write)
OldInt28	Pointer <>		;Old INT $28 handler (DOS multitasking)

;Following needed for TSR's that use the 8087
NewInt02	Pointer <>		;NMI interrupt
NewInt75	Pointer <>		;8087 exceptions (???)

Int24Err	DB	0		;Boolean -- 1 means critical error
Int24ErrCode	DB	0		;Byte -- the DOS error code

PopupToCall	DB	0		;Index number of a pending popup
PopupsEnabled	DB	0		;Boolean, determines if we react to pop keys
PopTimeOut	DW	36		;Default timeout number of clock ticks
					;(2 seconds on PC)
PopTicker	DW	0		;Decremented as the clock ticker
OurDS		DW	DATA		;Value of DS -- init'd by EXE loader
OurDTA		Pointer <>		;Our DTA
OurPSP		DW	0		;Our PSP
TempTrapFlag	DB	0		;Flag to indicate if traps need setting

Dos3Plus	DB	True		;Boolean - True if running DOS 3.x or
					; higher

SystemState	DW	0		;see comment below
StateFlags	EQU	WP CS:SystemState ;for convenience

COMMENT @
 When SystemState is zero, popping up is OK.  Each interrupt handler has its
 own bit which is set on entry and reset on exit from the interrupt.  One
 bit used is set when we are setting/removing the DOS traps.

 The bit flags are as follows:

   F E D C B A 9 8 7 6 5 4 3 2 1 0  Flag indicates we're inside this INT:
   -------------------------------- -------------------------------------
   | | | | | | | | | | | | | | | +- $05  PrtSc
   | | | | | | | | | | | | | | +--- $08  Clock tick (hardware)
   | | | | | | | | | | | | | +----- $09  Keyboard (hardware)
   | | | | | | | | | | | | +------- $10  BIOS video interrupt
   | | | | | | | | | | | +--------- $13  BIOS disk read/write
   | | | | | | | | | | +----------- $14  BIOS communications
   | | | | | | | | | +------------- $16  BIOS keyboard
   | | | | | | | | +--------------- $17  BIOS printer
   | | | | | | | +----------------- $25  DOS absolute disk read
   | | | | | | +------------------- $26  DOS absolute disk write
   | | | | | +--------------------- $28  DOS multitasking
   | | | | +-----------------------  --  Setting DOS traps
   +-+-+-+------------------------- xxx  Bits $C-$F are reserved
@

;****************************************************** Bit masks

InInt05 	=	0000000000000001b	;Set when in INT $05
InInt08 	=	0000000000000010b	;Set when in INT $08
InInt09 	=	0000000000000100b	;Set when in INT $09
InInt10 	=	0000000000001000b	;Set when in INT $10
InInt13 	=	0000000000010000b	;Set when in INT $13
InInt14 	=	0000000000100000b	;Set when in INT $14
InInt16 	=	0000000001000000b	;Set when in INT $16
InInt17 	=	0000000010000000b	;Set when in INT $17
InInt25 	=	0000000100000000b	;Set when in INT $25
InInt26 	=	0000001000000000b	;Set when in INT $26
InInt28 	=	0000010000000000b	;Set when in INT $28
InSetTR 	=	0000100000000000b	;Set when we set traps

NotIn05 	=	1111111111111110b	;Clears INT $05 flag
NotIn08 	=	1111111111111101b	;Clears INT $08 flag
NotIn09 	=	1111111111111011b	;Clears INT $09 flag
NotIn10 	=	1111111111110111b	;Clears INT $10 flag
NotIn13 	=	1111111111101111b	;Clears INT $13 flag
NotIn14 	=	1111111111011111b	;Clears INT $14 flag
NotIn16 	=	1111111110111111b	;Clears INT $16 flag
NotIn17 	=	1111111101111111b	;Clears INT $17 flag
NotIn25 	=	1111111011111111b	;Clears INT $25 flag
NotIn26 	=	1111110111111111b	;Clears INT $26 flag
NotIn28 	=	1111101111111111b	;Clears INT $28 flag
NotSetTR	=	1111011111111111b	;Clears setting traps flag

;****************************************************** TryPop

COMMENT |
  This procedure checks to see if the system is OK and, if so, calls the
  current popup.  It preserves all registers except the flags.

  On entry, the stack should look exactly as it did after the interrupt
  occurred, with no other data PUSHed, and the interrupt flag should be
  disabled.

  TryPop must not be CALLed; it must be jumped to. When finished, TryPop
  executes an IRET instruction.

  TryPop also contains the label DosOkToPop, which the interrupt $28 filter
  jumps to when it needs to pop up.
|

TryPop	PROC NEAR

	CLI				;No interrupts between chk and zero
	CMP	CS:PopTicker,0		;check this so we can be called without
					;knowing
	JZ	SysNotOK		;if not waiting, just return
	CMP	StateFlags,0		;Check system state
	JE	SysOkTryPop		;if not OK, pass on

SysNotOK:
	IRET				;return with non-zero flags

SysOkTryPop:
	PUSH	DS			;Save DS
	PUSH	BX			;Save BX
	PUSH	AX			;Save AX

	MOV	DS,CS:OurDS		;get data seg
	SetZero AX			;zero AX
	MOV	AL,CS:PopupToCall	;Popup index in AL
	DEC	AX			;less 1 for base of 1
	MOV	BX,Offset DosWaitFlags	;offset of DosWaitFlags array
	XLAT				;Get value. If not 0, check DOS
	MOV	BX,AX			;Store state of the flag in BX
	SUB	BL,DosTrapsSet		;BL = (1-0), (1-1), (0-0), or (0-1)
	MOV	CS:TempTrapFlag,BL	;Save the result
	OR	AX,AX			;check for 0
	JNZ	CheckDosOkToPop 	;if not 0, check DOS

	POP	AX			;restore used regs
	POP	BX
	POP	DS
	JMP	SHORT DosOkToPop	;not using DOS, all's well

CheckDosOkToPop:
	LDS	BX,CS:DosInUsePtr	;DOS in use pointer in DS:BX
	MOV	AL,[BX] 		;DOS byte in AL
	LDS	BX,CS:DosCriticalPtr	;Check DOS critical flag
	OR	AL,[BX] 		;they must both be zero
	POP	AX			;restore used regs
	POP	BX
	POP	DS
	JNZ	SysNotOK		;if DOS isn't OK, return

;If we get here then the DISK and DOS are fine.
DosOkToPop:
	MOV	CS:PopTicker,0		;stop checking period
	SaveAllRegs			;Save all registers

	MOV	DS,CS:OurDS		;Get data segment
	SetZero BX			;Zero BX
	MOV	BL,CS:PopupToCall	;Get index of popup
	DEC	BX			;PopupAddrs is 1-based
	MOV	SI,BX			;save index in SI for now
	SHL	BX,1			;PopupAddrs is array of pointers
	SHL	BX,1

	PUSH	WP PopupAddrs[BX].Segm	;Push segment of popup
	PUSH	WP PopupAddrs[BX].Ofst	;Push offset of popup

	;switch stacks

	MOV	AX,SS			;save current SS in AX
	MOV	ES,AX			;ES = current SS
	MOV	DX,WP PopupStacks[BX].Segm ;DX = new SS
	MOV	BX,WP PopupStacks[BX].Ofst ;BX = new SP
	MOV	SS,DX			;new SS in SS
	XCHG	BX,SP			;swap new & old SP, old in ES:BX

	STI				;interrupts on
	ADD	BX,4			;ES:BX points to Regs,
					;ES:[BX-4] has pointer to popup
	PUSH	ES			;save top of regs segment
	PUSH	BX			;and offset
	PUSH	SI			;save index of popup

	CLI				;interrupts off
	PUSH	LastEntrySS		;Save any previous routine's SS
	PUSH	LastEntrySP		;SP
	PUSH	LastEntryIP		;and reentry offset

	PUSH	ES			;pass top of regs segment
	PUSH	BX			;and offset as VAR parameter

	CMP	CS:TempTrapFlag,True	;see if traps need to be set
	JE	SetTrapsToPop		;If so, use alternate routine

	;Else, save info for emergency exit and call popup
	MOV	LastEntrySS,SS		;Save SS
	MOV	LastEntrySP,SP		;Save SP
	MOV	LastEntryIP,Offset BackInTryPop ;Save reentry offset

	STI				;interrupts on
	CallFar ES:[BX-4]		;CALL popup, which will get rid
					;of the last two PUSH's
	JMP	SHORT BackInTryPop	;We're back, clean up

SetTrapsToPop:
	CALL	SetDosTraps		;Set the traps and call pop-up

BackInTryPop:
	CLI				;interrupts off
	POP	LastEntryIP		;restore previous reentry offset
	POP	LastEntrySP		;SP
	POP	LastEntrySS		;and previous routine's SS
	POP	BX			;index value in BX

	MOV	PopupInUse[BX],False	;Popup is not in use
	POP	BX			;old SP in BX
	POP	AX			;old SS in AX
	MOV	SS,AX			;restore SS
	MOV	SP,BX			;restore SP
	RestoreAllRegs			;restore all registers
	IRET				;return to caller

TryPop	ENDP

;****************************************************** Int8

COMMENT |
  Clock interrupt handler.
  ------------------------
  Traps the clock tick to see if it should activate a routine. If it can, it
  clears the clock tick, then calls the appropriate popup. If it can't, it
  chains to the previous INT $8 handler.
|

TempInt08	Pointer <>		;Temporary address

Int8	PROC NEAR

	CMP	CS:PopTicker,0		;Check if were waiting to pop up
	JA	CheckPopClk		;if so, see if it's safe
	JmpFar	CS:OldInt08		;if not, pass on interrupt

CheckPopClk:
	TEST	StateFlags,InInt08	;check if we're in use
	JNZ	CheckTimeOut		;pass on if so

	OR	StateFlags,InInt08	;now we are in use
	POP	CS:TempInt08.Ofst	;caller's offset
	POP	CS:TempInt08.Segm	;caller's segment
	CALL	CS:OldInt08		;Call original routine

	;Push flags and address on the stack so we can use IRET to return
	PUSHF				;caller's flags on stack
	PUSH	CS:TempInt08.Segm	;caller's segment
	PUSH	CS:TempInt08.Ofst	;caller's offset on stack

	CLI				;interrupts off
	CMP	CS:PopTicker,0		;check again in case it expired
	JZ	NoTimeOut		;return

	AND	StateFlags,NotIn08	;clear clock-in-use bit
	JNZ	CheckTimeOut		;not OK, pass on

	JMP	TryPop			;try to pop-up

CheckTimeOut:
	DEC	PopTicker		;Not OK, decrement ticker
	JNZ	NoTimeOut

	PUSH	DS			;Timeout, use these regs
	PUSH	BX
	MOV	DS,CS:OurDS		;get data segment
	SetZero BH			;zero BH
	MOV	BL,CS:PopupToCall	;get number of popup
	DEC	BX			;array has base of 1, convert to 0 base
	MOV	PopupInUse[BX],False	;Popup is not in use
	POP	BX			;restore registers
	POP	DS

NoTimeOut:
	IRET				;return to caller

Int8	ENDP

;****************************************************** Int9

COMMENT |
  Keyboard interrupt handler.
  ---------------------------
  Intercepts the keyboard hardware interrupt (INT 9) to determine when we should
  pop up. If the proper mask is active and one of the defined keys is struck,
  then this routine does the following:

  - sets PopupToCall to the array index of the key that was struck
  - sets PopTicker to the timeout value
  - resets the keyboard and returns

  Int8, Int16, and Int28 take over from there.
|

TempInt09	Pointer <>
RoutineNum	DB	0
BiosDataSeg	DW	40h

KbdData 	=	60h
BiosShiftFlags	=	17h

Int9	PROC NEAR

	PUSH	DS			;Save DS
	PUSH	AX			;Save AX
	PUSH	BX			;Save BX
	MOV	DS,CS:BiosDataSeg	;Check BIOS data area
	MOV	AH,DS:[BiosShiftFlags]	;BIOS status byte into AH
	AND	AH,00001111b		;If none of these flags is set, can't
					;be a hot key...
	JZ	PassKbdInt		;So exit

	IN	AL,KbdData		;Get key struck
	CMP	AL,MaxScanCode		;see if it's in our range
	JA	PassKbdInt		;no, pass on

	MOV	DS,CS:OurDS		;Reset DS
	SetZero BH			;BH = 0
	MOV	BL,AL			;BX has index into arrays of bytes
					;based on scan codes
	MOV	AL,PopupKeys[BX]	;get popup handle in AL
	OR	AL,AL			;check for 0
	JZ	PassKbdInt		;if 0, not a hot key
	CMP	AH,ShiftKeys[BX]	;Shift keys match?
	JE	AttemptPop		;if so, try to POP up

PassKbdInt:
	POP	BX			;Restore BX
	POP	AX			;Restore AX
	POP	DS			;Restore DS
	TEST	StateFlags,InInt09	;Check if we're in use
	JZ	TrackKbd		;if no, set variable
	JmpFar	CS:OldInt09		;else, just pass this on

TrackKbd:
	OR	StateFlags,InInt09	;Set our bit
	POP	CS:TempInt09.Ofst	;Offset of caller
	POP	CS:TempInt09.Segm	;Segment of caller
	CallFar CS:OldInt09		;Call original routine

	;Push flags and address on the stack so we can use IRET to return
	PUSHF				;Save flags
	CLI				;Ints off
	PUSH	CS:TempInt09.Segm	;Segment of caller
	PUSH	CS:TempInt09.Ofst	;Offset of caller

	AND	StateFlags,NotIn09	;Reset our bit
	IRET				;return to caller

AttemptPop:
	SetZero AH			;zero in AH
	MOV	BX,AX			;get routine handle IN BX
	DEC	BX			;array has base of 1

	;make sure it's all right to try to pop up

	CMP	CS:PopupsEnabled,True	;Are popups enabled?
	JNE	DontPop 		;No? Eat the hotkey
	CMP	PopupInUse[BX],AH	;Popup already in use?
	JNE	DontPop 		;Yes? Eat the hotkey
	CMP	CS:PopTicker,0		;Something else waiting to pop up?
	JNE	DontPop 		;Yes? Eat the hotkey

	;checks went OK, we can set the pop ticker

	MOV	PopupInUse[BX],True	;This popup is in use now
	INC	BX			;Popup to call in BX
	MOV	CS:PopupToCall,BL	;In PopupToCall
	MOV	AX,CS:PopTimeOut	;timeout value in AX
	MOV	CS:PopTicker,AX 	;set PopTicker
DontPop:
	ResetKbd			;reset keyboard (trashes AX)
	NullJump			;delay
	ResetPIC			;reset PIC port (trashes AX)
	POP	BX			;Restore BX
	POP	AX			;Restore AX
	POP	DS			;Restore DS
	IRET				;return from interrupt

Int9	ENDP

;****************************************************** Int16

COMMENT |
  BIOS keyboard interrupt handler.
  --------------------------------
  Intercepts the BIOS keyboard interrupt (INT 16) to allow popups to be
  activated while waiting for keyboard input.

  Also used to communicate data between TSR's written with Turbo Professional.
|

TempInt16	Pointer <>		;local

Int16	PROC NEAR

	CLI				;Just in case it wasn't called properly
	CMP	AX,IfcSignature 	;See if this is a request for info
	JNE	NotIfcCheck		;If not, continue
	PUSH	DS			;Save DS
	MOV	DS,CS:OurDs		;DS = OurDS
	CMP	IfcInstalled,True	;Are we in charge of the interface?
	POP	DS			;Restore DS
	JNE	JumpOld16		;If not, chain to old ISR
	NOT	AX			;Flip the bits in AX
	MOV	ES,CS:OurDs		;ES = DS
	MOV	DI,Offset ThisIfc	;ES:DI points to ThisIfc
	MOV	WP ES:[DI].NextIfc,0	;If we're answering this, we're the end
	MOV	WP ES:[DI].NextIfc+2,0	; of the line, so NextIfc is nil
	IRET				;Return

NotIfcCheck:
	CLI				;No ints here
	TEST	StateFlags,InInt16	;Check if we're in use
	JZ	TrackInt16		;if not, set variable

JumpOld16:
	JmpFar	CS:OldInt16		;else, pass this on

TrackInt16:
	CMP	AH,10h			;Is it function 10h?
	JE	ChkEnhKbd		;If so, use enhanced keyboard code
	OR	AH,AH			;check for function 0 (read next char)
	JNZ	TrackRaw16		;if AH <> 0, track INT 16 raw

ChkKbdLoop:
	;loop until a key is pressed, alternately checking for keyboard
	;input and trying to pop up

	MOV	AH,1			;execute check for keypress function
	CLI				;interrupts off to emulate interrupt
	OR	StateFlags,InInt16	;Set our bit

	POP	CS:TempInt16.Ofst	;Offset of caller
	POP	CS:TempInt16.Segm	;Segment of caller

	CallFar CS:OldInt16		;Call original routine

	PUSHF				;save flags
	CLI				;interrupts off
	PUSH	CS:TempInt16.Segm	;Segment of caller
	PUSH	CS:TempInt16.Ofst	;Offset of caller

	LAHF				;save return flags
	AND	StateFlags,NotIn16	;Reset our bit
	JNZ	ChkKbdLoopNext		;if state isn't clear, don't try

	PUSHF				;get set for IRET
	PUSH	CS			;push CS (next call is near)
	CALL	ChkKbdTryPop		;push return offset too

	;IRET in TryPop returns to here
	JMP	SHORT ChkKbdLoopNext	;check again

ChkKbdTryPop:
	JMP	TryPop			;try to pop up

ChkKbdLoopNext:
	SAHF				;restore flags
	JZ	ChkKbdLoop		;if no key waiting, loop
	SetZero AH			;switch back to function 0, get key

TrackRaw16:
	CLI				;No ints here either
	OR	StateFlags,InInt16	;Set our bit
	POP	CS:TempInt16.Ofst	;Offset of caller
	POP	CS:TempInt16.Segm	;Segment of caller
	CallFar CS:OldInt16		;Call original routine

	;Push flags and address on the stack so we can use IRET to return
	PUSHF				;save flags
	CLI				;interrupts off
	PUSH	CS:TempInt16.Segm	;Segment of caller
	PUSH	CS:TempInt16.Ofst	;Offset of caller

	;reset our in-interrupt flag and return
	AND	StateFlags,NotIn16	;Reset our bit
	JNZ	RetFromI16		;if that didn't make state 0, return

	JMP	TryPop			;try to pop up

RetFromI16:
	IRET				;return to caller

	;the remainder of the ISR allows us to pop up during function $10
	;calls (the enhanced keyboard version of function 0, read next char)

ChkEnhKbd:
	CMP	CS:IsEnhanced,1 	;Is it an enhanced keyboard?
	JNE	TrackRaw16		;If not, don't use the following code!

ChkEnhKbdLoop:
	;loop until a key is pressed, alternately checking for keyboard
	;input and trying to pop up

	MOV	AH,11h			;execute check for keypress function
	CLI				;interrupts off to emulate interrupt
	OR	StateFlags,InInt16	;Set our bit

	POP	CS:TempInt16.Ofst	;Offset of caller
	POP	CS:TempInt16.Segm	;Segment of caller

	CallFar CS:OldInt16		;Call original routine

	PUSHF				;save flags
	CLI				;interrupts off
	PUSH	CS:TempInt16.Segm	;Segment of caller
	PUSH	CS:TempInt16.Ofst	;Offset of caller

	LAHF				;save return flags
	AND	StateFlags,NotIn16	;Reset our bit
	JNZ	ChkEnhKbdLoopNext	;if state isn't clear, don't try

	PUSHF				;get set for IRET
	PUSH	CS			;push CS (next call is near)
	CALL	ChkEnhKbdTryPop 	;push return offset too

	;IRET in TryPop returns to here
	JMP	SHORT ChkEnhKbdLoopNext ;check again

ChkEnhKbdTryPop:
	JMP	TryPop			;try to pop up

ChkEnhKbdLoopNext:
	SAHF				;restore flags
	JZ	ChkEnhKbdLoop		;if no key waiting, loop
	MOV	AH,10h			;switch back to function 10h, get key
	JMP	SHORT TrackRaw16	;and track it

Int16	ENDP

;****************************************************** Int28

COMMENT |
  DOS multitasking interrupt handler.
  -----------------------------------
  Handles the DOS multitasking interrupt, which is called continuously at
  the DOS prompt. If PopTicker > 0, this interrupt will call the appropriate
  popup before chaining to the previous INT $28 handler.
|

TempInt28	Pointer <>		;Temporary address

Int28	PROC NEAR

	CLI				;ints off to make sure
	CMP	CS:PopTicker,0		;check pop ticker before doing anything
	JZ	I28Pass 		;pass if not set

	TEST	StateFlags,InInt28	;check if we're in use
	JNZ	I28Pass 		;if so, pass

	;following needed for Compaq DOS 3.25--calls itself to check for ^Break
	;during file operations

	PUSH	DS			;Save DS
	PUSH	BX			;Save BX
	LDS	BX,CS:DosInUsePtr	;DS:BX => DosInUse flag
	CMP	BYTE PTR [BX],2 	;BX <= 2?
	POP	BX			;Restore DS
	POP	DS			;Restore BX
	JNB	I28Pass 		;pass if DosInUse >= 2

	OR	StateFlags,InInt28	;now we're in use
	POP	CS:TempInt28.Ofst	;caller's offset
	POP	CS:TempInt28.Segm	;caller's segment

	CallFar CS:OldInt28		;call original routine

	PUSHF				;caller's flags on stack
	CLI				;ints off while we test flag
	AND	StateFlags,NotIn28	;now we're not in use
	PUSH	CS:TempInt28.Segm	;caller's segment
	PUSH	CS:TempInt28.Ofst	;caller's offset on stack

	CMP	CS:PopTicker,0		;check if we're waiting to pop up
	JNE	Go28			;if so, do it

I28Return:
	IRET				;return

I28Pass:
	JmpFar	CS:OldInt28		;pass to old int 28 handler

Go28:
	PUSH	DS			;Save DS
	PUSH	BX			;Save BX
	PUSH	AX			;Save AX

	MOV	DS,CS:OurDS		;get data seg
	SetZero AX			;zero AX
	MOV	AL,CS:PopupToCall	;Popup index in AL
	DEC	AX			;less 1 for base of 1
	MOV	BX,Offset DosWaitFlags	;offset of DosWaitFlags array
	XLAT				;Get value. If not 0, check DOS
	SUB	AL,DosTrapsSet		;AL = (1-0), (1-1), (0-0), or (0-1)
	MOV	CS:TempTrapFlag,AL	;Save the result

	POP	AX			;restore used regs
	POP	BX
	POP	DS
	JMP	DosOkToPop		;call routine which won't return
	IRET				;put this here anyhow

Int28	ENDP

COMMENT |
  The remainder of these interrupt handlers are simple filters that prevent
  us from popping up when any one of these interrupts is in progress by setting
  a bit in the system state flag. Their overhead is minimal.
|

;****************************************************** Int5

COMMENT |
  PrtSc interrupt handler.
  ------------------------
  Intercepts the PrtSc interrupt to contend with programs that generate the
  interrupt themselves to do screen dumps.
|

TempInt05      Pointer <>	       ;local

Int5	PROC NEAR

	CLI				;Just in case it wasn't called properly
	TEST	StateFlags,InInt05	;Check if we're in use
	JZ	TrackInt5		;if not, set variable
	JmpFar	CS:OldInt05		;yes, pass this on

TrackInt5:
	OR	StateFlags,InInt05	;Set our bit
	POP	CS:TempInt05.Ofst	;Offset of caller
	POP	CS:TempInt05.Segm	;Segment of caller
	CallFar CS:OldInt05		;Call original routine

	;Push flags and address on the stack so we can use IRET to return
	PUSHF				;save flags
	CLI				;interrupts off
	PUSH	CS:TempInt05.Segm	;Segment of caller
	PUSH	CS:TempInt05.Ofst	;Offset of caller

	;reset our in-interrupt flag and return
	AND	StateFlags,NotIn05	;Reset our bit

	IRET				;return to caller

Int5	ENDP

;****************************************************** Int10

COMMENT |
  BIOS video interrupt handler.
  -----------------------------
  Intercepts the BIOS video interrupt to prevent problems when running in
  the OS/2 compatibility box. Not captured if running under DOS 2.x or 3.x.
|

TempInt10	Pointer <>		;local

Int10	PROC NEAR

	CLI				;Just in case it wasn't called properly
	TEST	StateFlags,InInt10	;Check if we're in use
	JZ	TrackInt10		;if not, set variable
	JmpFar	CS:OldInt10		;yes, pass this on

TrackInt10:
	OR	StateFlags,InInt10	;Set our bit
	POP	CS:TempInt10.Ofst	;Offset of caller
	POP	CS:TempInt10.Segm	;Segment of caller
	CallFar CS:OldInt10		;Call original routine

	;Push flags and address on the stack so we can use IRET to return
	PUSHF				;save flags
	CLI				;interrupts off
	PUSH	CS:TempInt10.Segm	;Segment of caller
	PUSH	CS:TempInt10.Ofst	;Offset of caller

	;reset our in-interrupt flag and return
	AND	StateFlags,NotIn10	;Reset our bit

	IRET				;return to caller

Int10	ENDP

;****************************************************** Int13

COMMENT |
  BIOS disk interrupt handler.
  ----------------------------
  Intercepts the BIOS disk interrupt to contend with programs that bypass DOS
  and access the disk directly.
|

TempInt13	Pointer <>		;local

Int13	PROC NEAR

	CLI				;Just in case it wasn't called properly
	TEST	StateFlags,InInt13	;Check if we're in use
	JZ	TrackInt13		;if not, set variable
	JmpFar	CS:OldInt13		   ;yes, pass this on

TrackInt13:
	OR	StateFlags,InInt13	;Set our bit
	POP	CS:TempInt13.Ofst	;Offset of caller
	POP	CS:TempInt13.Segm	;Segment of caller
	CallFar CS:OldInt13		;Call original routine

	;Push flags and address on the stack so we can use IRET to return
	PUSHF				;save flags
	CLI				;interrupts off
	PUSH	CS:TempInt13.Segm	;Segment of caller
	PUSH	CS:TempInt13.Ofst	;Offset of caller

	;reset our in-interrupt flag and return
	AND	StateFlags,NotIn13	;Reset our bit

	IRET				;return to caller

Int13	ENDP

;****************************************************** Int14

COMMENT |
  BIOS communications interrupt handler.
  --------------------------------------
  Intercepts the BIOS communications interrupt to prevent problems when running
  in the OS/2 compatibility box. Not captured if running under DOS 2.x or 3.x.
|

TempInt14	Pointer <>		;local

Int14	PROC NEAR

	CLI				;Just in case it wasn't called properly
	TEST	StateFlags,InInt14	;Check if we're in use
	JZ	TrackInt14		;if not, set variable
	JmpFar	CS:OldInt14		;yes, pass this on

TrackInt14:
	OR	StateFlags,InInt14	;Set our bit
	POP	CS:TempInt14.Ofst	;Offset of caller
	POP	CS:TempInt14.Segm	;Segment of caller
	CallFar CS:OldInt14		;Call original routine

	;Push flags and address on the stack so we can use IRET to return
	PUSHF				;save flags
	CLI				;interrupts off
	PUSH	CS:TempInt14.Segm	;Segment of caller
	PUSH	CS:TempInt14.Ofst	;Offset of caller

	;reset our in-interrupt flag and return
	AND	StateFlags,NotIn14	;Reset our bit

	IRET				;return to caller

Int14	ENDP

;****************************************************** Int17

COMMENT |
  BIOS printer interrupt handler.
  ------------------------------
  Intercepts the BIOS printer interrupt to prevent problems when running in the
  OS/2 compatibility box. Not captured if running under DOS 2.x or 3.x.
|

TempInt17	Pointer <>		;local

Int17	PROC NEAR

	CLI				;Just in case it wasn't called properly
	TEST	StateFlags,InInt17	;Check if we're in use
	JZ	TrackInt17		;if not, set variable
	JmpFar	CS:OldInt17		   ;yes, pass this on

TrackInt17:
	OR	StateFlags,InInt17	;Set our bit
	POP	CS:TempInt17.Ofst	;Offset of caller
	POP	CS:TempInt17.Segm	;Segment of caller
	CallFar CS:OldInt17		;Call original routine

	;Push flags and address on the stack so we can use IRET to return
	PUSHF				;save flags
	CLI				;interrupts off
	PUSH	CS:TempInt17.Segm	;Segment of caller
	PUSH	CS:TempInt17.Ofst	;Offset of caller

	;reset our in-interrupt flag and return
	AND	StateFlags,NotIn17	;Reset our bit

	IRET				;return to caller

Int17	ENDP

;****************************************************** Int25

COMMENT |
  DOS absolute disk read interrupt handler.
  -----------------------------------------
  Intercepts the absolute disk read interrupt to contend with programs
  that use this interrupt.
|

TempInt25	Pointer <>		;local

Int25	PROC NEAR

	CLI				;Just in case it wasn't called properly
	TEST	StateFlags,InInt25	;Check if we're in use
	JZ	TrackInt25		;if not, set variable
	JmpFar	CS:OldInt25		;yes, pass this on

TrackInt25:
	OR	StateFlags,InInt25	;Set our bit
	POP	CS:TempInt25.Ofst	;Offset of caller
	POP	CS:TempInt25.Segm	;Segment of caller
	CallFar CS:OldInt25		;Call original routine

	;Push flags and address on the stack so we can use IRET to return
	PUSHF				;save flags
	CLI				;interrupts off
	PUSH	CS:TempInt25.Segm	;Segment of caller
	PUSH	CS:TempInt25.Ofst	;Offset of caller

	;reset our in-interrupt flag and return
	AND	StateFlags,NotIn25	;Reset our bit

	IRET				;return to caller

Int25	ENDP

;****************************************************** Int26

COMMENT |
  DOS absolute disk write interrupt handler.
  -----------------------------------------
  Intercepts the absolute disk write interrupt to contend with programs
  that use this interrupt.
|

TempInt26	Pointer <>		;local

Int26	PROC NEAR

	CLI				;Just in case it wasn't called properly
	TEST	StateFlags,InInt26	;Check if we're in use
	JZ	TrackInt26		;if not, set variable
	JmpFar	CS:OldInt26		;yes, pass this on

TrackInt26:
	OR	StateFlags,InInt26	;Set our bit
	POP	CS:TempInt26.Ofst	;Offset of caller
	POP	CS:TempInt26.Segm	;Segment of caller
	CallFar CS:OldInt26		;Call original routine

	;Push flags and address on the stack so we can use IRET to return
	PUSHF				;save flags
	CLI				;interrupts off
	PUSH	CS:TempInt26.Segm	;Segment of caller
	PUSH	CS:TempInt26.Ofst	;Offset of caller

	;reset our in-interrupt flag and return
	AND	StateFlags,NotIn26	;Reset our bit

	IRET				;return to caller

Int26	ENDP

;****************************************************** InitTsrPtrs

;function InitTsrPtrs : Boolean;

;Initializes pointers to hidden variables and pointers that indicate when
;DOS is active. Returns false if unsupported version of DOS is found.

InitTsrPtrs	PROC NEAR

	;initialization
	MOV	DosTrapsSet,False	;DOS traps not set
	DosCall 2Fh			;Get current DTA
	SetPtr	CS:OurDTA, ES, BX	;Save our DTA (in ES:BX)
	MOV	AX,PrefixSeg		;AX = our PSP
	MOV	CS:OurPSP,AX		;Save in CS-relative storage

	;check to see if it's an enhanced keyboard
	MOV	AH,05			;Stuff buffer function
	MOV	CX,0FFFFh		;Stuff FFFF
	INT	16h
	MOV	AH,11h			;Read enhanced function
	INT	16h
	CMP	AX,0FFFFh		;Did we read the FFFF?
	JNZ	EnhDone 		;if not, continue
	MOV	AH,10h			;Get the key out of the buffer
	INT	16h
	MOV	CS:IsEnhanced,True	;Store CS-relative variable
EnhDone:

	;allow Pascal routines access to CS-relative data
	SetPtrByOfst	PopTickerPtr, CS, PopTicker
	SetPtrByOfst	PopupsEnabledPtr, CS, PopupsEnabled
	SetPtrByOfst	PopupToCallPtr, CS, PopupToCall

	;Get current interrupt vectors

	GetVector	02h, CS:NewInt02	;NMI interrupt
	GetVector	05h, CS:OldInt05	;PrtSc interrupt
	GetVector	08h, CS:OldInt08	;Clock tick interrupt
	GetVector	09h, CS:OldInt09	;Keyboard interrupt
	GetVector	10h, CS:OldInt10	;Video interrupt
	GetVector	13h, CS:OldInt13	;Disk interrupt
	GetVector	14h, CS:OldInt14	;Comm. interrupt
	GetVector	16h, CS:OldInt16	;Keyboard interrupt
	GetVector	17h, CS:OldInt17	;Printer interrupt
	GetVector	25h, CS:OldInt25	;Disk read interrupt
	GetVector	26h, CS:OldInt26	;Disk write interrupt
	GetVector	28h, CS:OldInt28	;DOS multitasking interrupt
	GetVector	75h, CS:NewInt75	;8087 exception ???

	;Get the DOS version

	DosCall 30h			;Get DOS version
	XCHG	AL,AH			;Major version # in AH, minor in AL
	MOV	DosVersion,AX		;Save for the Pascal code

	;Get address of the DOS-in-use flag

	PUSH	DS			;Save DS
	DosCall 34h			;Undocumented call to get pointer to
					;DOS critical flag -- returned in ES:BX
	POP	DS			;Restore DS
	SetPtr	CS:DosInUsePtr, ES, BX	;Set DOS-in-use pointer

	;Determine the address of the DOS critical pointer based on DOS version

	SetZero CX			;Assume failure
	MOV	AX,DosVersion		;Get DosVersion back into AX
	CMP	AX,0200h		;Check for range 2.00 - 2.$FF
	JB	InitDosExit		;Exit with error if < 2.00
	CMP	AX,0300h		;Check for 3.00
	JA	Dos3x			;If higher, need extra checks
	JB	Dos2			;If less, it's DOS 2.x

	CMP	BX,019Ch		;Is this Compaq DOS 3.0?
	JE	DecOffset		;If so, critical flag below InDos
	SUB	BX,01AAh		;Else, this is MS/PC-DOS 3.0
	JMP	SHORT SetCriticalPtr	;Ready

Dos2:
	MOV	CS:Dos3Plus,False	;Not running DOS 3.x or above
	INC	BX			;Critical pointer after in-use pointer
	JMP	SHORT SetCriticalPtr	;Ready

Dos3x:
	CMP	AX,030Ah		;Check for 3.10 or higher
	JB	SetCriticalPtr		;This shouldn't happen
	CMP	AX,0363h		;If <=, DOS version is 3.10-3.99
	JA	SetCriticalPtr		;Higher version -- presumably OS/2
					;set DosCriticalPtr = DosInUsePtr

DecOffset:
	DEC	BX			;Critical pointer before in-use pointer

SetCriticalPtr:
	INC	CX			;Set success flag
	SetPtr	CS:DosCriticalPtr, ES, BX	;Set DOS critical pointer

InitDosExit:
	MOV	AX,CX			;Result into AX
	BooleanResult			;Set zero flag
	RET				;Return

InitTsrPtrs	ENDP

;****************************************************** Int24

;procedure Int24
;Interrupt handler for DOS critical errors

FailCode	= 3
IgnoreCode	= 0

Int24		PROC NEAR

	MOV	CS:Int24Err,True	;Set error flag
	XCHG	AX,DI			;DI has error code on entry
	MOV	CS:Int24ErrCode,AL	;Store error code for later
	XCHG	AX,DI			;Restore AX
	MOV	AL,FailCode		;Fail the DOS call
	CMP	CS:Dos3Plus,True	;DOS 3.x or higher?
	JE	Int24Exit		;If so, done
	MOV	AL,IgnoreCode		;else, tell DOS to I)gnore error instead
Int24Exit:
	IRET				;Return

Int24		ENDP

;****************************************************** Int24Result

;function Int24Result : Word;
;Returns word in AX. AH has Int24ErrCode, AL has IoResult.

Int24Result	PROC FAR

	CALL	IoResultPrim		;Get IoResult into AL
	SetZero AH			;Clear AH
	CMP	CS:Int24Err,True	;Critical error flag set?
	JNE	Int24ResultExit 	;No? Done

	;Merge critical error code into result

	MOV	AH,CS:Int24ErrCode	;Int24ErrCode into AH
	OR	AH,AH			;Is AH 0?
	JNZ	IrAHnot0		;If not, continue
	MOV	AH,0Dh			;Else, attempt to write to
					;write protected disk. Map to $0D.
IrAHnot0:
	MOV	CS:Int24ErrCode,0	;Reset Int24ErrCode to 0
	MOV	CS:Int24Err,False	;Clear error flag

Int24ResultExit:
	RET

Int24Result	ENDP

;****************************************************** NopISR

NopISR	PROC NEAR

	IRET				;For dummy ISR's

NopISR	ENDP

;****************************************************** UseCritical

;Force DOS 2.x to use DOS critical stack when getting/setting PSP

UseCritical	PROC NEAR

	CMP	CS:Dos3Plus,True	;Is this DOS 3.x?
	JE	UCexit			;if so, do nothing
	LES	DI,CS:DosCriticalPtr	;ES:DI => DosCriticalPtr
	MOV	BYTE PTR ES:[DI],-1	;Tell DOS to use critical stack
UCexit:
	RET

UseCritical	ENDP

;****************************************************** NotCritical

;Reset DOS 2.x for non-critical stack

NotCritical	PROC NEAR

	CMP	CS:Dos3Plus,True	;Is this DOS 3.x?
	JE	NCexit			;if so, do nothing
	LES	DI,CS:DosCriticalPtr	;ES:DI => DosCriticalPtr
	MOV	BYTE PTR ES:[DI],0	;Tell DOS to use non-critical stack
NCexit:
	RET

NotCritical	ENDP

;****************************************************** SetDosTraps

COMMENT |

  SetDosTraps
  -----------
  Makes preparations to insure that DOS is safe to use during a popup:

  -- Saves and restores current DTA and PSP, switching to ours in between
  -- Prevents ^Break/^C problems by taking over dangerous interrupts
     and changing the DOS BREAK level, restoring them after the call
  -- Sets up DOS critical error handler for the popup

  After these preparations have been made, the popup is called. Reentrancy
  problems are avoided by means of the DosTrapsSet flag, which prohibits
  this code from being executed twice before the first session is over.
|

SaveBreak	DB	0		;Saved ^Break state
SavePSP 	DW	0		;Saved PSP segment
SaveInt02	Pointer <>		;Saved INT $02 vector
SaveInt1B	Pointer <>		;Saved INT $1B vector
SaveInt23	Pointer <>		;Saved INT $23 vector
SaveInt24	Pointer <>		;Saved INT $24 vector
SaveInt75	Pointer <>		;Saved INT $75 vector
SaveDTA 	Pointer <>		;Saved DTA
OurReturn	DW	0		;Our return address
OurCall 	Pointer <>		;The address we're supposed to call

SetDosTraps	PROC NEAR

	OR	StateFlags,InSetTR	;setting DOS traps
	MOV	DosTrapsSet,True	;next time sys ok, DOS traps are set
	STI				;interrupts OK now

	MOV	AX,CS			;AX = CS
	MOV	DS,AX			;DS = CS

	assume	DS:CODE 		;Tell MASM that DS = CS

	POP	OurReturn		;POP our return address off the stack

	;save the address we're supposed to call (at ES:[BX-4])
	LES	BX,ES:[BX-4]		;Get the address
	SetPtr	OurCall, ES, BX 	;And save it

	DosCallAX	3300h		;Get current BREAK level
	MOV	SaveBreak,DL		;Save current level, returned in DL
	SetZero DL			;0 means relax break checking
	DosCallAX	3301h		;Set BREAK value in DL

	;save interrupt vectors we're taking over
	GetVector 02h, SaveInt02	;Save NMI vector
	GetVector 1Bh, SaveInt1B	;Save ^Break vector
	GetVector 23h, SaveInt23	;Save ^C vector
	GetVector 24h, SaveInt24	;Save critical error vector
	GetVector 75h, SaveInt75	;Save ??? vector

	;grab control of potentially dangerous interrupts
	MOV	DX,Offset NopIsr	;DS:DX to points to IRET
	DosCallAX	251Bh		;BIOS ^Break handler
	DosCallAX	2523h		;DOS ^C handler

	;set up our Int24 handler
	MOV	DX,Offset Int24 	;DS:DX points to Int24
	DosCallAX	2524h		;Set critical error handler

	;Restore Turbo's INT 2 and INT 75 handlers
	LDS	DX,CS:NewInt02		;Set new NMI vector
	DosCallAX	2502h
	LDS	DX,CS:NewInt75		;Set INT $75 vector
	DosCallAX	2575h
	MOV	AX,CS			;Reset DS to CS
	MOV	DS,AX

	;save current DTA and switch to ours
	DosCall 2Fh			;Get current DTA
	SetPtr	SaveDTA, ES, BX 	;Save current DTA (in ES:BX)
	LDS	DX,OurDTA		;DS:DX points to our DTA
	DosCall 1Ah			;Set DTA

	assume	DS:NOTHING		;we don't know what ds is

	;save current PSP and switch to ours
	CALL	UseCritical		;Switch to critical stack in DOS 2.x
	DosCallAX	5100h		;Get current PSP
	MOV	CS:SavePSP,BX		;Save PSP returned in BX
	MOV	BX,CS:OurPSP		;Get our PSP
	DosCallAX	5000h		;Switch to our PSP
	CALL	NotCritical		;Critical stack no longer needed

	;reset DS to DATA
	MOV	DS,CS:OurDS		;Restore our DS
	assume	DS:DATA 		;Tell MASM that DS = DATA

	;Save info for emergency exit and call the popup
	MOV	LastEntrySS,SS		;Save SS
	MOV	LastEntrySP,SP		;Save SP
	MOV	LastEntryIP,Offset SDTReentry	;Save reentry offset

	AND	StateFlags,NotSetTR	;done setting DOS traps
	CallFar OurCall 		;Call the popup
	OR	StateFlags,InSetTR	;resetting DOS traps

SDTReentry:
	;reset DS to CS and PUSH our return address back on the stack
	MOV	AX,CS			;Set DS to CS
	MOV	DS,AX
	assume	DS:CODE 		;Tell MASM that DS = CS

	PUSH	OurReturn		;PUSH the return address back up

	;restore saved PSP
	CALL	UseCritical		;Switch to critical stack in DOS 2.x
	MOV	BX,SavePSP		;BX = saved PSP
	DosCallAX	5000h		;Set PSP function
	CALL	NotCritical		;Critical stack no longer needed

	;restore saved DTA
	LDS	DX,SaveDTA		;DS:DX points to saved DTA
	DosCall 1Ah			;Set DTA function

	assume	DS:NOTHING		;Tell MASM that DS = ?

	;restore saved interrupt vectors
	LDS	DX,CS:SaveInt02 	;Restore NMI handler
	DosCallAX	2502h		;Set INT $02 vector
	LDS	DX,CS:SaveInt1B 	;Restore BIOS ^Break handler
	DosCallAX	251Bh		;Set INT $1B vector
	LDS	DX,CS:SaveInt23 	;Restore ^C handler
	DosCallAX	2523h		;Set INT $23 vector
	LDS	DX,CS:SaveInt24 	;Restore critical error handler
	DosCallAX	2524h		;Set INT $24 vector
	LDS	DX,CS:SaveInt75 	;Restore ??? handler
	DosCallAX	2575h		;Set INT $75 vector

	;restore DOS BREAK level
	MOV	AX,CS			;Reset DS to CS
	MOV	DS,AX
	assume	DS:CODE 		;Tell MASM that DS = CS

	MOV	DL,SaveBreak		;Restore saved break state
	DosCallAX	3301h		;Set break check level

	MOV	Int24Err,False		;Don't leave error flag set

SetTrapExit:
	MOV	DS,CS:OurDS		;Restore our DS
	assume	DS:DATA 		;Tell MASM that DS = DATA

	MOV	DosTrapsSet,False	;DOS traps are not set now
	AND	StateFlags,NotSetTR	;not setting DOS traps now

	RET

SetDosTraps	ENDP

;****************************************************** EmergencyExit

;Called by exit/error handler in case of runtime error while popped up

EmergencyExit	PROC NEAR

	CLI				;Interrupts off
	MOV	SS,LastEntrySS		;Switch stacks
	MOV	SP,LastEntrySP
	STI				;Interrupts on
	ADD	SP,4			;Get rid of the parameter to the popup
	MOV	BX,LastEntryIP		;Jump to re-entry point
	JMP	BX

EmergencyExit	ENDP

CODE	ENDS

	END
