page    60,132
;Preamble:
;       Received this file from Raymond Michels, author of "Undocumented
;DOS Internals," (1989 Programmers' Journal 7.2, pp. 32-37).
;       Although it makes no mention, it clearly builds on TSRDEMO2.ASM
;that is authored by Thomas Brandenborg (Lundbyesgade 11, DK-8000
;Aarhus C., DENMARK)  which is shareware found on Channel 1 BBS (617-354-
;8873, Boston)
;       There are a few notable differences between the two:
;       1.  DEMO only went so far as to popup a message while this program
;       will free 192K of memory used by the current application and then run
;       another program in that space (NOTE: this program should not be;
;       confused with SWAP being distributed as SWAPIT.ZIP by Nico Mak,
;       Mansfield Software Group, Storrs,CT and described in Dr. Dobbs
;       April, 1989)
;       2.  SWAP handles screen storage and restoration
;       3.  SWAP adds scrolldown and scrollup in SCAN Codes
;       4.  SWAP adds an sti (opcode) in OurInt13 just after the
;             call    OldInt13
;       5.  SWAP adds a deinstall TSR routine
;       6.  SWAP modifies handling of ^C with addition of PrevInt23 variable
;       7.  SWAP changes messages in general
;       8.  SWAP adds routine CLS to clear screen
;       9.  SWAP adds saving of screen mode (@ IsColor:)
;      10.  SWAP removed treatment of EnvSeg at very end
;
;              W. Curtiss Priest, Humanic Systems
;               Lexington, MA, 02173
;

;               CONTEXT SWITCH


;==============================================================================
; DEFINE BIOS DATA SEGMENT OFFSETS
;==============================================================================

BiosData        segment at 40h
                org     17h
KbFlag          label   byte            ;current shift status bits
                org     18h
KbFlag1         label   byte            ;current key status of toggle keys
BiosData        ends

;==============================================================================
; DEFINE OFFSETS WITHIN BIOS EXTRA DATA SEG
;==============================================================================

BiosXX          segment at 50h
                org     0
StatusByte      label   byte            ;PrtSc status
BiosXX          ends

ErrPrtSc        equ     -1              ;err during last PrtSc
InPrtSc         equ     1               ;PrtSc in progress

;==============================================================================
; DEFINE OFFSETS WITHIN OUR PSP
;==============================================================================

Cseg            segment byte public
                org     2
TopSeg          label   word            ;last seg in alloc block
                org     2ch
EnvSeg          label   word            ;seg of our environment copy
Cseg            ends

;==============================================================================
; DOS COM-FILE ENTRY POINT
;==============================================================================

Cseg            segment public byte
                assume  cs:Cseg, ds:nothing, es:nothing, ss:nothing
                org     100h


ComEntry:       jmp     Init            ;JMP to init at bottom of seg




;==============================================================================
; IDENTIFICATION CODES FOR THIS TSR (MUST BE UNIQUE FOR EACH CO-EXISTING TSR)
; HIGH BYTE OF GetId MUST NOT MATCH ANY AH REQUEST CODES FOR INT16H.
;==============================================================================

GetId           equ     "bn"                    ;INT16h AX val to get MyId
MyId            equ     "BN"                    ;ID of this TSR

;==============================================================================
; FLAGS AND PTRS FOR RESIDENT HANDLING
;==============================================================================

TsrMode         db      0                       ;bits for various modes
InInt08         equ     1 SHL 0                 ;timer0 tick handler
InInt09         equ     1 SHL 1                 ;keyboard handler
InInt13         equ     1 SHL 2                 ;BIOS disk I/O
InInt28         equ     1 SHL 3                 ;INT28 handler
In28Call        equ     1 SHL 4                 ;we have issued INT28
InPopup         equ     1 SHL 5                 ;popup routine activated
NewDos          equ     1 SHL 6                 ;DOS 2.x in use
InDosClr        equ     1 SHL 7                 ;InDos 0 at popup time

KeyMode         db      0                       ;bits for hotkey status
HotIsShift      equ     1 SHL 0                 ;hotkey is shift state
InHotMatch      equ     1 SHL 1                 ;so far keys match hotkey seq
HotKeyOn        equ     1 SHL 2                 ;full hotkey pressed

InDosPtr        label   dword                   ;seg:off of InDos flag
InDosOff        dw      0
InDosSeg        dw      0

CritErrPtr      label   dword                   ;seg:off of CritErr flag
CritErrOff      dw      0
CritErrSeg      dw      0

GetOutFlag      dw      0                       ;set to cause de-install

;==============================================================================
; DATA FOR INT09H HANDLER TO CHECK FOR HOTKEY COMBINATION
;==============================================================================

; ------------  EQU'S FOR BIT SHIFTS WITHIN KEYBOARD FLAGS

InsState        equ     80h
CapsState       equ     40h
NumState        equ     20h
ScrollState     equ     10h
AltShift        equ     08h
CtlShift        equ     04h
LeftShift       equ     02h
RightShift      equ     01h

InsShift        equ     80h
CapsShift       equ     40h
NumShift        equ     20h
ScrollShift     equ     10h
HoldState       equ     08h

; ------------  SCAN CODES FOR VARIOUS SHIFT KEYS

LeftDown        equ     42                      ;scan code of left shift key
LeftUp          equ     LeftDown OR 80h
RightDown       equ     54                      ;scan code of right shift key
RightUp         equ     RightDown OR 80h
AltDown         equ     56                      ;scan code of alt key
AltUp           equ     AltDown OR 80h
CtlDown         equ     29                      ;scan code of ctrl key
CtlUp           equ     CtlDown OR 80h
ScrollDown      equ     70                      ;scan code of scroll lock
ScrollUp        equ     ScrollDown OR 80h

; ------------  MISC KEYBOARD DATA

KbData          equ     60h                     ;keyboard data input

;==============================================================================
; TO USE A SHIFT KEY COMBINATION AS HOT KEY:
;  -    SET THE FLAG HotIsShift IN KeyMode
;  -    DEFINE THE SHIFT STATUS BITS IN THE VARIABLE HotKeyShift
;
; TO USE A SERIES OF SCAN CODES AS HOT KEY:
;  -    CLEAR THE FLAG HotIsShift IN KeyMode
;  -    INSERT THE MAKE AND BREAK SCAN CODES IN THE HotKeySeq STRING

;       NOTE:   WITH THIS IMPLEMENTATION YOU SHOULD NOT USE A HOT KEY
;               SEQUENCE WHICH PRODUCES A KEY IN THE BIOS KEYBOARD QUEUE,
;               SINCE THE KEY IS NOT REMOVED BEFORE CALLING THE POPUP ROUTINE.
;
; NOTE: HOTKEY TYPE AND CONTENTS OF HOTKEY VARIABLES MAY BE CHANGED AT RUN TIME
;==============================================================================

HotKeyShift     db      AltShift OR RightShift  ;shift state IF HotIsShift=FF

HotKeySeq       db      AltDown,RightDown
HotKeyLen       equ     $-HotKeySeq

HotIndex        db      0                       ;# key in seq to compare next
BetweenKeys     db      0                       ;timeout count between keys
KeyTimeOut      equ     10                      ;more ticks means not a hotkey

;==============================================================================
; DATA FOR INT08H HANDLER TO CHECK FOR POPUP
;==============================================================================

SafeWait        db      0                       ;count-down for safe popup
MaxWait         equ     8                       ;wait no more 8/18 sec

;==============================================================================
; PROCESS & SYSTEM DATA
;==============================================================================

OurSS           dw      0                       ;stack for popup routine
OurSP           dw      0
StackSize       equ     512                     ;bytes to reserve for stack

OldSS           dw      0                       ;old stack seg
OldSP           dw      0                       ;old stack off

OurPSP          dw      0                       ;our PSP seg
OldPSP          dw      0                       ;old PSP seg

OldDTA          label   dword                   ;seg:off of old DTA area
OldDTAOff       dw      0
OldDTASeg       dw      0

OurDTA          label   dword                   ;seg:off of our DTA
OurDTAOff       dw      0
OurDTASeg       dw      0

OldBreak        db      0                       ;old ctrl-break state
OldExtErr       dw      3 dup (0)               ;AX,BX,CX of ext err

;==============================================================================
; LOCATIONS FOR SAVED INTERRUPT VECTORS
;==============================================================================

OldInt08        label   dword                   ;Timer0 loaded before this
OldInt08Off     dw      0
OldInt08Seg     dw      0

OldInt09        label   dword                   ;Kb handler loadde before this
OldInt09Off     dw      0
OldInt09Seg     dw      0

OldInt13        label   dword                   ;BIOS diskette I/O
OldInt13Off     dw      0
OldInt13Seg     dw      0

OldInt16        label   dword                   ;BIOS kb Q-handler
OldInt16Off     dw      0
OldInt16Seg     dw      0

OldInt1B        label   dword                   ;^break of process we steal
OldInt1BOff     dw      0
OldInt1BSeg     dw      0

OldInt1C        label   dword                   ;timer tick of process we steal
OldInt1COff     dw      0
OldInt1CSeg     dw      0

OldInt21        label   dword                   ;DOS function dispatcher
OldInt21Off     dw      0
OldInt21Seg     dw      0

PrevInt23       label   dword                   ;^C of process we steal
PrevInt23Off    dw      0
PrevInt23Seg    dw      0

OldInt23        label   dword                   ;^C internal handler (IRET)
OldInt23Off     dw      0
OldInt23Seg     dw      0

OldInt24        label   dword                   ;crit err of process we steal
OldInt24Off     dw      0
OldInt24Seg     dw      0

OldInt28        label   dword                   ;DOS idles loaded before this
OldInt28Off     dw      0
OldInt28Seg     dw      0

ApplPSP         dw      0                       ;stores application PSP
CurrentAlloc    dw      0                       ;application memory allocation

;==============================================================================
; SPEAKER/TONE GENERATION DATA
;==============================================================================

PB0port         equ     61h                     ;port for speaker bit
ErrLen1         equ     10                      ;# outer err beep cycles
ErrLen2         equ     80                      ;# inner err beep cycles
ErrLow          equ     100                     ;low tone wait in err beep
ErrHi           equ     40                      ;hi tone wait in err beep

;==============================================================================
; ErrBeep - PRODUCE ERROR-INDICATING SOUND ON SPEAKER
;==============================================================================

ErrBeep         proc    near
                assume  ds:nothing, es:nothing, ss:nothing

                push    ax                      ;save regs used
                push    bx
                push    cx
                push    dx

                mov     cx,ErrLen1              ;# mix-cycles for beep

ErrBeep1:       mov     dx,ErrLow               ;wait time for half-cycle
                mov     bx,ErrLen2              ;len of one tone
                call    DoTone                  ;output low err tone
                mov     dx,ErrHi                ;wait time for half-cycle
                mov     bx,ErrLen2              ;len of one tone
                call    DoTone                  ;output low err tone

                loop    ErrBeep1                ;loop for some time

                pop     dx
                pop     cx                      ;restore regs
                pop     bx
                pop     ax
                ret
ErrBeep         endp

;==============================================================================
; DoTone - OUTPUT ONE TONE ON THE SPEAKER
;
; INPUT:        DX:     LOOP WAIT TIME FOR HALF CYCLE IN TONE
;               BX:     NUMBER OF CYCLES FOR TONE DURATION
; OUTPUT:       NONE
; REGS:         ALL PRESERVED
;==============================================================================

DoTone          proc    near
                assume  ds:nothing, es:nothing, ss:nothing

                push    ax                      ;save regs used
                push    bx
                push    cx
                in      al,PB0port              ;get PB0 reg pattern
                mov     ah,al                   ;save it

DoTone1:        and     al,0fch                 ;mask off speaker bit
                out     PB0port,al              ;pull!
                mov     cx,dx                   ;half cycle in counter
DoTone2:        loop    DoTone2                 ;leave there for half a cycle
                or      al,2                    ;turn on speaker bit
                out     PB0port,al              ;push!
                mov     cx,dx                   ;half cycle in counter
DoTone3:        loop    DoTone3                 ;leave there for half a cycle

                dec     bx                      ;count down tone duration
                jnz     DoTone1                 ;go through full tone

                mov     al,ah                   ;AL=original PB0 reg value
                out     PB0port,al              ;restore

                pop     cx                      ;restore regs
                pop     bx
                pop     ax
                ret
DoTone          endp

;==============================================================================
; TestSafe - CHECK IF THIS IS A SAFE TIME TO DO A POP UP
; 
; RETURN CLC IF SAFE TO POP UP, CY IF NOT SAFE.
;
; CHECK IF ANY INTs ARE IN CRITICAL AREAS (InInt09 & InInt13)
; CHECK IF WE ARE IN AN OUR OWN INT28 CALL (In28Call)
; CHECK 8259A PIC ISR REGISTER FOR MISSING EOIs
; CHECK IF DOS IS STABLE FOR POP UP
; CHECK IF A PRINT SCREEN IS IN PROGRESS
;==============================================================================

TestSafe        proc    near
                assume  ds:nothing, es:nothing

                push    ax                      ;save regs used
                push    bx
                push    ds

; ------------  CHECK INTs TO SEE IF THEY WERE INTERRUPTED AT BAD TIMES

                test    TsrMode,InInt09 OR InInt13 OR In28Call
                jnz     NotSafe                 ;jump if any INTs are chopped

; ------------  CHECK THE 8259A PIC ISR REGISTER FOR NON-EOIed HW INTs

                mov     al,00001011b            ;tell 8259A we want the ISR
                out     20h,al                  ;8259A command reg
                nop
                nop
                nop                             ;now, ISR should be ready
                in      al,20h                  ;AL=mask of active INTs
                or      al,al                   ;test all (IRQ0 *did* EOI)
                jnz     NotSafe                 ;jump if active INTs

; ------------  NOW, ENSURE THAT DOS WAS NOT INTERRUPTED

                assume  ds:nothing

                lds     bx,InDosPtr             ;now, DS:BX=InDos
                mov     al,byte ptr [bx]        ;get InDos to AL
                lds     bx,CritErrPtr           ;now, DS:BX=CritErr
                or      al,byte ptr [bx]        ;both flags zero?
                jz      DosSafe                 ;YES - DOS is really idle
                test    TsrMode,InInt28         ;is this an INT28h
                jz      NotSafe                 ;NO - not safe, should be idle
                cmp     al,1                    ;YES - one InDos entry only?
                ja      NotSafe                 ;NO - jump if more than one
DosSafe:

; ------------  CHECK TO SEE IF A PRINT SCREEN IS IN PROGRESS

                mov     ax,BiosXX
                mov     ds,ax                   ;move DS to BIOS extra data seg
                assume  ds:BiosXX

                cmp     StatusByte,InPrtSc      ;print screen in progress?
                je      NotSafe                 ;YES - jump if prtsc

; ------------  SEEMS TO BE A SAFE TIME FOR POPUP

IsSafe:         clc                             ;CLC=safe to popup
                jmp     short ExitSafe          ;end this then

; ------------  APPARENTLY THIS IS JUST NOT THE TIME TO DO A POPUP

NotSafe:        stc                             ;CY=don't popup now

; ------------  RETURN TO CALLER WITH CARRY SET/CLEAR

ExitSafe:       pop     ds                      ;restore regs
                pop     bx
                pop     ax
                ret
TestSafe        endp

;==============================================================================
; OurInt08 - TSR INT08H HANDLER TO WATCH FOR HOTKEY AND SAFE POPUP TIMES
;
; CALL OldInt08
; CHECK FOR RE-ENTRANCE INTO CRITICAL INT08 CODE
; SET InInt08 FLAG
; CHECK FOR TIMEOUT BETWEEN KEYS IN HOTKEY SEQUENCE
; CHECK IF HOTKEY WAS PRESSED
; CHECK IF ALREADY InPopup OR InInt28
; CHECK IF SAFE TIME FOR SYSTEM TO POPUP
; UPDATE FLAGS AND CALL POPUP IF SAFE
; GIVE ERROR BEEP IF POPUP WAS UNSAFE FOR A LONG TIME
; RESET InInt08 FLAG
; DO IRET
;==============================================================================

; ------------  NEAR JUMP DESTINATION FOR FAST IRET'S

Exit08:         iret                            ;IRET (!)

; ------------  ACTUAL INT08 ENTRY POINT

OurInt08        proc    far
                assume  ds:nothing, es:nothing, ss:nothing

                pushf                           ;simulate INT08
                cli                             ;in case others forgot it
                call    OldInt08                ;call TSRs loaded before us

; ------------  ENSURE NO RECURSION INTO CRITICAL INT08 CODE

                sti                             ;we'll manage INTs

                test    TsrMode,InInt08         ;already in here somewhere?
                jnz     Exit08                  ;YES - don't re-enter
                or      TsrMode,InInt08         ;tell people we are here

                push    ax                      ;need a few regs in this code

; ------------  COUNT DOWN TIME-OUT BETWEEN KEYS IN HOTKEY SEQUENCE

                test    KeyMode,InHotMatch      ;are we in a key match?
                jz      TestHot08               ;NO - don't care then
                dec     BetweenKeys             ;count down timeout val
                jnz     TestHot08               ;jump if no timeout yet
                mov     HotIndex,0              ;start match from beginning
                and     KeyMode,not InHotMatch  ;just so we know it next time

; ------------  CHECK FOR POSSIBLE POPUP ACTIONS

TestHot08:      test    KeyMode,HotKeyOn        ;has hotkey been pressed?
                jz      ExitInt08               ;NO - jump if no fun here

                test    TsrMode,InInt28 OR InPopup
                jnz     ExitInt08               ;jmp if not alr in business

; ------------  HOTKEY PRESSED, CHECK TO SEE IF IT IS SAFE TO POPUP

                cmp     SafeWait,0              ;first time we find hotkey?
                ja      TestSafe08              ;NO - wait has alr been set
                mov     SafeWait,MaxWait        ;# ticks to wait at most

TestSafe08:     call    TestSafe                ;now, CY clear if popup is safe
                jc      NotSafe08               ;jump if popup is bad idea

; ------------  SEEMS SAFE TO POPUP AT THIS TIME, SO DO!

                xor     al,al                   ;fast zero
                mov     SafeWait,al             ;don't count any more
                and     KeyMode,not HotKeyOn    ;clear hotkey status
                or      TsrMode,InPopup         ;tell'em we enter popup routine
                and     TsrMode,not InInt08     ;OK to enter critical INT08
                call    InitPopup               ;do actual popup
                or      TsrMode,InInt08         ;back in INT08 code here
                and     TsrMode,not InPopup     ;not in popup code any more
                mov     SafeWait,al             ;in case of hotkey during popup
                and     KeyMode,not HotKeyOn    ;clear hotkey status

                jmp     short ExitInt08         ;finally done

; ------------  UNSAFE POPUP TIME, COUNT DOWN SafeWait

NotSafe08:      dec     SafeWait                ;count down waiter
                jnz     ExitInt08               ;jump if still no timeout

; ------------  NO SAFE TIMES FOUND FOR QUITE SOME TIME, ERROR

                and     KeyMode,not HotKeyOn    ;might as well clear hotkey
                call    ErrBeep                 ;do an error beep

; ------------  NORMAL INT08H EXIT, RESET InInt08

ExitInt08:      pop     ax                      ;restore regs used
                and     TsrMode,not InInt08     ;clear that flag
                iret                            ;straight back
OurInt08        endp

;==============================================================================
; OurInt09 - TSR INT09H HANDLER TO WATCH FOR HOTKEY
;
; SAVE SCAN CODE
; CALL OldInt09
; CHECK FOR RECURSION INTO CRITICAL INT09 CODE
; SET InInt09 FLAG
; CHECK IF HOTKEY ALREADY SET
; DETERMINE HOTKEY TYPE (SHIFT STATE OR KEY SEQENCE)
; CHECK SHIFT STATE IF HotIsShift
; COMPARE FOR KEY MATCH IF (NOT HotIsShift)
; SET HotKeyOn IF HOTKEY PRESSED
; RESET InInt09 FLAG
; DO IRET
;==============================================================================

; ------------  NEAR JUMP DESTINATION FOR EARLY EXITS

Exit09:         pop     bx                      ;restore regs
                pop     ax
                iret                            ;flags restored from stack

; ------------  ACTUAL INT09 ENTRY POINT

OurInt09        proc    far
                assume  ds:nothing, es:nothing, ss:nothing

                push    ax                      ;save regs used
                push    bx

; ------------  READ SCAN CODE, IN CASE SEQUENCE MATCHING SELECTED

                in      al,KbData               ;Al=key, preserved by BIOS

; ------------  CALL BIOS TO PERFORM ITS DUTIES

                pushf                           ;simulate INT (CLI alr set)
                cli                             ;in case others forgot it
                call    OldInt09                ;call BIOS/earlier TSRs

; ------------  ENSURE NO RECURSION INTO CRITICAL INT09 CODE

                sti                             ;we'll manage INTs

                test    TsrMode,InInt09         ;alr in business?
                jnz     Exit09                  ;YES - skip test till clear
                or      TsrMode,InInt09         ;tell them we arrived here

; ------------  DETERMINE HOT KEY TYPE SELECTED

                test    KeyMode,HotKeyOn        ;already hotkey there?
                jnz     ExitInt09               ;YES - no double hotkeys here

                test    KeyMode,HotIsShift      ;shift state type hotkey?
                jz      CompSeq09               ;NO - go compare sequence

; ------------  COMPARE CURRENT SHIFT STATUS AGAINST HOTKEY

                push    ds                      ;save current ds
                mov     ax,BiosData             ;move DS to BIOS data seg
                mov     ds,ax                   ;DS can now access keyb vars
                assume  ds:BiosData             ;tell MASM about our DS
                mov     al,KbFlag               ;get BIOS shift state bits
                pop     ds                      ;restore
                assume  ds:nothing              ;last thing we know about him

                and     al,HotKeyShift          ;isolate relevant bits
                cmp     al,HotKeyShift          ;our shift state in effect?
                jne     ExitInt09               ;NO - not that shift state
                or      KeyMode,HotKeyOn        ;YES - flag hotkey
                jmp     short ExitInt09         ;now we can be proud to leave

; ------------  MATCH KEY IN SCAN CODE SEQUENCE

CompSeq09:      mov     bl,HotIndex             ;next scan code to match
                xor     bh,bh                   ;must be word
                cmp     al,HotKeySeq[bx]        ;does key match?
                je      HotMatch09              ;YES - jump if match
                mov     HotIndex,bh             ;search from start next time
                and     KeyMode,not InHotMatch  ;current no match
                jmp     short ExitInt09         ;now end this

; ------------  KEY MATCHED...NEXT SCAN CODE IN HotKeySeq

HotMatch09:     inc     bl                      ;new code at next pass
                cmp     bl,HotKeyLen            ;did we match whole sequence?
                jae     HotHit09                ;YES - jump if full sequence
                mov     HotIndex,bl             ;NO - save new count
                mov     BetweenKeys,KeyTimeOut  ;reset counter between keys
                or      KeyMode,InHotMatch      ;we are in a match now
                jmp     short ExitInt09         ;time to end this

; ------------  KEY MATCHED ALL SCAN CODES IN HOTKEY SEQUENCE

HotHit09:       or      KeyMode,HotKeyOn        ;say hotkey was pressed
                mov     HotIndex,bh             ;match 1st code next time
                and     KeyMode,not InHotMatch  ;that's the end of a match

; ------------  EXIT FROM INT09H, RESET InInt09 FLAG

ExitInt09:      and     TsrMode,not InInt09     ;tell'em we left this code
                pop     bx                      ;restore regs
                pop     ax
                iret                            ;flags restored from stack
OurInt09        endp

;==============================================================================
; OurInt13 - SET InInt13 FLAG TO SAY THAT WE ARE IN AN INT13H
;==============================================================================

OurInt13        proc    far
                assume  ds:nothing, es:nothing, ss:nothing

                pushf                           ;save flags we use
                or      TsrMode,InInt13         ;remember we are in BIOS now
                popf                            ;restore flags

                pushf                           ;simulate INT13
                cli                             ;just in case others forgot
                call    OldInt13                ;let BIOS handle it all
                sti                             ;turn 'em back on

                pushf                           ;BIOS uses flag return
                and     TsrMode, not InInt13    ;tell people we left INT13h
                popf

                ret     2                       ;throw flags off stack
OurInt13        endp

;==============================================================================
; OurInt16 - TSR INT16H HANDLER, INT28 CHAIN INTERFACE
;
; INPUT:        AX = GetId
; OUTPUT:       AX = MyId
; REGS:         AX LOST, ALL OTHERS PRESERVED
; DESCRIPTION:  DETERMINE IF TSR WITH THIS ID IS ALREADY IN MEMORY
;
; INPUT:        AH = 00
; OUTPUT:       AX = NEXT KEY FROM BUFFER
; REGS;         AX LOST, ALL OTHERS PRESERVED
; DESCRIPTION:  RETURN A KEY FROM KEYBOARD BUFFER, WAIT TILL KEY IS PRESSED
;
; INPUT:        AH = 02
; OUTPUT:       AX = KEY FROM BUFFER IN ANY
;               ZF = NO KEYS IN BUFFER (AX PRESERVED)
;               NZ = KEY IN BUFFER (RETURNED IN AX, KEY STILL IN BUFFER)
; DESCRIPTION:  CHECK BUFFER FOR ANY PENDING KEYS, RETURN KEY IF ANY
;
; NOTE: ALL OTHER AX REQUEST CODES ARE PASSED ON TO BIOS INT16H HANDLER.
;
; NOTE: DURING INT28 POPUP (InPopup AND NOT InDosClr) FUNCTIONS AH=0 AND
;       AH=1 WILL ISSUE INT28, UNLESS InDos HAS FROM VALUE AT POPUP OR
;       CritErr HAS BEEN SET.
;==============================================================================

OurInt16        proc    far
                assume  ds:nothing, es:nothing, ss:nothing

                sti                             ;we'll manage INTs
                pushf                           ;save callers flags
                cmp     ax,GetId                ;return ID request?
                jne     NotId16                 ;NO - jump if not

; ------------  TSR DIAGNOSTIC REQUEST, RETURN SPECIAL VALUE TO SAY WE ARE HERE

                mov     ax,MyId                 ;ID val returned in AX
                popf                            ;restore flags
                iret                            ;return to caller

; ------------  PASS CONTROL TO BIOS, FLAGS ON STACK

GoBios16:       popf                            ;restore flags at INT time
                jmp     OldInt16                ;continue in the woods

; ------------  REGULAR BIOS INT16 REQUEST, CHECK FOR ANY FANCY ACTIONS

NotId16:        test    TsrMode,InPopup         ;are we in a popup?
                jz      GoBios16                ;NO - leave rest with BIOS
                test    TsrMode,InDosClr        ;InDos clear at popup?
                jnz     GoBios16                ;YES - no need to signal INT28

                popf                            ;restore original flags
                push    bx                      ;we need a few regs here
                push    cx
                push    si
                push    ds
                pushf                           ;original flags back on stack

; ------------  GET REQUEST CODE TO BH ENHANCED BIT TO BL

                mov     bh,ah                   ;BH=function request code
                and     bh,not 10h              ;zap enhanced kybd bit
                cmp     bh,1                    ;any function above 1?
                ja      ExitBios16              ;YES - leave rest with BIOS

                mov     bl,ah                   ;BL used for enhanced bit
                and     bl,10h                  ;BL=value of enhanced bit

; ------------  GET InDos To CL, CritErr to CH, SETUP REGS

                assume  ds:nothing

                lds     si,InDosPtr             ;DS:[SI]=InDos
                mov     cl,byte ptr [si]        ;CL=InDos value
                lds     si,CritErrPtr           ;ES:[SI]=CritErr
                mov     ch,byte ptr [si]        ;CH=CritErr value

                mov     si,ax                   ;save AX call value

                mov     ax,cs                   ;move DS here, now we got it
                mov     ds,ax
                assume  ds:Cseg                 ;everybody should know

; ------------  CHECK KEYBOARD BUFFER, ORIGINAL FLAGS ON STACK

Wait16:         mov     ah,1                    ;AH=1=test buffer status
                or      ah,bl                   ;maintain enhanced bit value

                popf                            ;restore original flags
                pushf                           ;simulate INT
                cli                             ;in case others forgot
                call    OldInt16                ;now, ZF set if no keys
                pushf                           ;save result flags
                jnz     TestSkip16              ;jump if a key was found

; ------------  NO KEY FOUND, CALL INT28 IF DOS InDos ALLOWS

                cmp     cx,0001h                ;CritErr=0, InDos=1 ?
                jne     NextKey16               ;NO - wait for next key
                or      TsrMode,In28Call        ;tell people we called this INT
                int     28h                     ;now take your chance
                and     TsrMode,not In28Call    ;end of that call

; ------------  TEST BUFFER AGAIN IF INT16.00, IRET IF INT16.01

NextKey16:      or      bh,bh                   ;is this a wait for key?
                jz      Wait16                  ;YES - then go wait for it!
                mov     ax,si                   ;restore original AX contents
                jmp     short Exit16            ;NO - exit with status we got

; ------------  KEY IN BUFFER, IF CTRL-C WE MAY HAVE TO SKIP IT, FLAGS ON STACK

TestSkip16:     cmp     al,3                    ;is this Ctrl-C?
                jne     TestExit16              ;NO - determine exit method
                test    cx,not 0001h            ;anything but InDos=1?
                jz      TestExit16              ;NO - determine exit method

; ------------  SKIP CTRL-C IN KEYBOARD BUFFER

                mov     ah,bl                   ;AH=0 + enhanced bit
                popf                            ;restore original INTs
                pushf                           ;save again
                pushf                           ;simulate INT
                cli                             ;simulate properly!
                call    OldInt16                ;now, key should be gone
                jmp     short Wait16            ;do as if nothing had happened

; ------------  KEY IN AX, IRET IF INT16.01, LEAVE WITH BIOS IF INT16.00

TestExit16:     or      bh,bh                   ;is this a wait for key?
                jnz     Exit16                  ;NO - do fast return
                mov     ax,si                   ;YES - restore AX code

; ------------  PASS CONTROL TO BIOS, FLAGS & REGS ON STACK

                assume  ds:nothing

ExitBios16:     popf                            ;restore work flags
                pop     ds                      ;restore regs
                pop     si
                pop     cx
                pop     bx
                cli                             ;should look like an INT
                jmp     OldInt16                ;leave rest with BIOS

; ------------  RETURN FROM INT16, FLAGS & REGS ON STACK

                assume  ds:nothing

Exit16:         popf                            ;restore proper flags
                pop     ds                      ;restore regs
                pop     si
                pop     cx
                pop     bx
                ret     2                       ;IRET, without flags restore

OurInt16        endp

;==============================================================================
; OurInt21 - INT21 FILTER TO THROW DANGEROUS DOS CALLS ON CRITICAL STACK
;
; CHECK IF InPopup AND InDosClr
; CHECK FUNCTION USES CONSOLE STACK
; SET CritErr IN DOS IF CONSOLE STACK USED
; CALL OldInt21
; RESTORE CritErr IF CRITICAL STACK USED
;==============================================================================

OurInt21        proc    far
                assume  ds:nothing, es:nothing

                pushf                           ;save calling flags
                sti

                test    TsrMode,InPopup         ;are we in a popup?
                jz      GoDos21                 ;NO - don't worry then
                test    TsrMode,InDosClr        ;console stack idle?
                jnz     GoDos21                 ;YES - nothing fancy then

; ------------  THIS IS 2ND CALL INTO DOS, SEE IF USING CONSOLE STACK

                cmp     ah,0ch                  ;any function 00-0C?
                jbe     UseCrit21               ;YES - use critical stack
                test    TsrMode,NewDos          ;NO - is this DOS 3.x?
                jnz     GoDos21                 ;YES - no other to worry about
                cmp     ah,50h                  ;set PSP function?
                je      UseCrit21               ;YES - use critical stack
                cmp     ah,51h                  ;get PSP function?
                jne     GoDos21                 ;NO - leave it with DOS

; ------------  FORCE USE OF CRITICAL STACK FOR THIS CALL

UseCrit21:      assume  ds:nothing              ;nothing to say about DS

                push    si                      ;save regs
                push    ds
                lds     si,CritErrPtr           ;now, DS:[SI]=InDos
                mov     byte ptr [si],-1        ;FF=use crit stack now
                pop     ds                      ;restore regs
                pop     si

                popf                            ;retsore flags setting
                pushf                           ;simulate INT
                cli                             ;in case others forgot
                call    OldInt21                ;flags already on stack

                push    si                      ;save regs
                push    ds
                lds     si,CritErrPtr           ;now, DS:[SI]=InDos
                mov     byte ptr [si],0         ;0=back to default stack
                pop     ds                      ;restore regs
                pop     si

                ret     2                       ;IRET throw old flags

; ------------  PASS CONTROL TO DOS, FLAGS ON STACK

GoDos21:        popf                            ;restore original flags
                cli                             ;just in case someone forgot
                jmp     OldInt21                ;let DOS handle the rest
OurInt21        endp

;==============================================================================
; OurInt24 - SAFE DOS CRITICAL ERROR HANDLER
; 
; IF DOS 3.X, FAIL THE SYSTEM CALL
; IF NOT DOS 3.X, IGNORE ERROR
;==============================================================================

OurInt24        proc    far
                assume  ds:nothing, es:nothing, ss:nothing

                mov     al,3                    ;AL=3=fail system call
                test    TsrMode,NewDos          ;are we using DOS 3.x?
                jnz     Exit24                  ;YES - OK to use AL=3
                xor     al,al                   ;NO - have to ignore err then
Exit24:         iret                            ;return to DOS
OurInt24        endp

;==============================================================================
; OurInt28 - TSR INT28H HANDLER, ALLOWS POPUP DURING DOS IDLE CALLS
;
; CALL OldInt28
; CHECK FOR RECURSION INTO CRITICAL INT28 CODE (& OTHER INTs AS WELL)
; SET InInt28 FLAG
; CHECK FOR HOTKEY
; CHECK IF SAFE TO POPUP
; DO POPUP IF SAFE AT THIS TIME
; RESET InInt28 FLAG
; DO IRET
;==============================================================================

; ------------  NEAR JUMP DESTINATION FOR FAST IRET'S

Exit28:         iret                            ;IRET (!)

; ------------  ACTUAL INT28 ENTRY POINT

OurInt28        proc    far
                assume  ds:nothing, es:nothing, ss:nothing

                pushf
                cli                             ;in case others forgot it
                call    OldInt28                ;call TSRs loaded before this

; ------------  ENSURE NO RECURSION ON CRITICAL INT28 CODE

                sti                             ;we'll manage INT's after this
                test    TsrMode,InInt08 OR InInt28 OR In28Call OR InPopup
                jnz     Exit28                  ;exit fast if already going
                or      TsrMode,InInt28         ;tell'em we are here

; ------------  CHECK FOR POSSIBLE POPUP ACTIONS

                test    KeyMode,HotKeyOn        ;any hotkeys pressed?
                jz      ExitInt28               ;NO - don't check any more then

; ------------  HOTKEY WAS PRESSED, ENSURE IT'S SAFE TO DO POPUP

                call    TestSafe                ;now, CY clear if popup is OK
                jc      ExitInt28               ;jump if not to popup

; ------------  SEEMS OK TO DO POPUP, SO DO!

                and     KeyMode,not HotKeyOn    ;clear hotkey status
                or      TsrMode,InPopup         ;tell'em we enter popup routine
                and     TsrMode,not InInt28     ;OK to enter critical INT28
                call    InitPopup               ;then do popup
                or      TsrMode,InInt28         ;back in INT28 code here
                and     TsrMode,not InPopup     ;not in popup code any more
                and     KeyMode,not HotKeyOn    ;clear hotkeys during popup

; ------------  NORMAL INT28H EXIT, RESET InInt28 FLAG

ExitInt28:      and     TsrMode,not InInt28     ;tell'em we left this code
                iret                            ;we have nothing more to say
OurInt28        endp

;==============================================================================
; NopInt - DUMMY IRET INSTRUCTION USED BY EMPTY INT HANDLERS
;==============================================================================

NopInt:         iret                            ;immediate return


;==============================================================================
; DE-INSTALL THIS TSR
;
;             NOTE!
; ***this is only supposed to be***
; ***invoked at a DOS prompt!!!!***
;
; - RESTORE INTERRUPT VECTORS
; - GIVE BACK THE MEMORY WE STOLE
; - DO A NORMAL "4C" RETURN TO DOS
;
;==============================================================================

DeInstall       proc    near
                push    ds

                mov     bx,OurPSP               ;restore PSP to point
                mov     ax,5000h                ;to us
                int     21h

                pop     ds
                push    ds
                mov     ax, 2528h
                lds     dx, OldInt28
                int     21h                     ;give back dos idle

                pop     ds
                push    ds
                mov     ax, 2521h
                lds     dx, OldInt21
                int     21h                     ;give back dos funct call

                pop     ds
                push    ds
                mov     ax, 2516h
                lds     dx, OldInt16
                int     21h                     ;give back keyboard int

                pop     ds
                push    ds
                mov     ax, 2513h
                lds     dx, OldInt13
                int     21h                     ;give back disk int

                pop     ds
                push    ds
                mov     ax, 2509h
                lds     dx, OldInt09
                int     21h                     ;give back kbd handler

                pop     ds
                push    ds
                mov     ax, 2508h
                lds     dx, OldInt08
                int     21h                     ;give back timer tick

                pop     ds
                push    ds
                mov     ax, 2523h
                lds     dx, PrevInt23
                int     21h                     ;reset ^C handler

                pop     ds

                push    es

                push    cs
                pop     es                      ;free this block
                mov     ah, 49h
                int     21h

                mov     es, EnvSeg
                mov     ah, 49h                 ;free environment block
                int     21h

                pop     es
                mov     ax, 4C00h
                int     21h

DeInstall       endp

;==============================================================================
; SCREEN SAVE/RESTORE ROUTINES
;==============================================================================
scrnhold        dw      2000 dup (?)
videoflag       dw      0
videocard       dd      0
savearea        dd      0

cursorsave      dw      0
cursortype      dw      0


fromscr proc    near
        push    ds              ; Save DS
        push    es              ;  and ES
        push    bp              ;  and BP
        mov     ax, videoflag
        mov     dx,3dah         ; Point DX to CGA status port
        les     di, savearea    ; Dest pointer into ES:DI
        mov     cx, 2000        ; Length value into CX
        lds     si, videocard   ; Source pointer into DS:SI
        cld                     ; Set string direction to forward
        cmp     ax,1            ; mono?
        jne     .3              ; no, have to watch for flicker
   rep  movsw                   ; yes, do a fast move
        jmp     fromscrexit     ;

.3:     in      al,dx           ; Get 6845 status
        rcr     al,1            ; Check horizontal retrace
        jb      .3              ; Loop if in horizontal retrace: this prevents
                                ; starting in mid-retrace, since there is
                                ; exactly enough time for 1 and only 1 LODSW
                                ; during horizontal retrace
        cli                     ; No ints during critical section
.4:     in      al,dx           ; Get 6845 status
        rcr     al,1            ; Check for horizontal retrace: LODSW is 1
                                ; clock cycle slower than STOSW; because of
                                ; this, the vertical retrace trick can't be
                                ; used because it causes flicker!  (RCR AL,1
                                ; is 1 cycle faster than AND AL,AH)
        jnb     .4              ; Loop if not in retrace
        lodsw                   ; Load the video word
        sti                     ; Allow interrupts
        stosw                   ; Store the video word
        loop    .3              ; Go do next word
fromscrexit:
        pop     bp              ; Restore BP
        pop     es              ;     and ES
        pop     ds              ;     and DS

        mov     ah, 3
        mov     bh, 0
        int     10h
        mov     cursorsave, dx
        mov     cursortype, cx

        ret
fromscr endp



toscr   proc    near
        push    ds              ; Save DS
        push    es              ;  and ES
        push    bp              ;  and BP
        mov     ax, videoflag
        mov     dx,3dah         ; Point DX to CGA status port
        les     di, videocard   ; Dest pointer into ES:DI
        mov     cx,2000         ; Length value (words) into CX
        lds     si, savearea    ; Source pointer into DS:SI
        cld                     ; Set string direction to forward
        cmp     ax,1            ; mono?
        jne     .0              ; no, do flicker trick
  rep   movsw                   ; yes, do it fast
        jmp     toscrexit

.0:     lodsw                   ; Grab a video word
        mov     bp,ax           ; Save it in BP
        mov     ah,9            ; Move horiz. + vertical retrace mask
.1:     in      al,dx           ; Get 6845 status
        rcr     al,1            ; Check horizontal retrace
        jb      .1              ; Loop if in horizontal retrace: this prevents
                                ; starting in mid-retrace, since there is
                                ; exactly enough time for 1 and only 1 STOSW
                                ; during horizontal retrace
        cli                     ; No ints during critical section
.2:     in      al,dx           ; Get 6845 status
        and     al,ah           ; Check for both kinds of retrace: IF the
                                ; video board does not report horizontal
                                ; retrace while in vertical retrace, this
                                ; will allow several characters to be
                                ; stuffed in during vertical retrace
        jz      .2              ; 
        mov     ax,bp           ; Get the video word
        stosw                   ; Store the video word
        sti                     ; Allow interrupts
        loop    .0              ; Go do next word

toscrexit:
        pop     bp              ; Restore BP
        pop     es              ;     and ES
        pop     ds              ;     and DS

        mov     dx, cs:cursorsave
        mov     ah, 2
        mov     bh, 0
        int     10h
        mov     cx, cs:cursortype
        mov     ah, 1
        int     10h

        ret
toscr   endp

;==============================================================================
; CLS---CLEAR THE SCREEN
;==============================================================================


CLS     proc    near            ; do a BIOS screen clear
        push    ax
        push    bx
        push    cx
        push    dx
        push    bp
        mov     ax, 0600h
        mov     cx, 0
        mov     dx, 184Fh
        mov     bh, 07h
        int     10h
        pop     bp
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        ret
CLS     endp


;==============================================================================
; GOTOXY---PUT THE CURSOR SOMEWHERE
;==============================================================================

Gotoxy  proc    near            ; row, column is already in DH, DL
        push    ax
        push    bx
        mov     ah, 2
        mov     bh, 0
        int     10h
        pop     bx
        pop     ax
        ret
Gotoxy  endp

; --------------------------------------
; CURSORON---MAKES THE CURSOR VISIBLE.
; --------------------------------------

CursorOn proc   near
        mov     cx, 0708h
        push    cx
        mov     ah, 0Fh
        int     10h
        pop     cx
        cmp     al, 7
        je      COmono
COsetit:
        mov     ah, 1
        int     10h
        ret
COmono:
        mov     cx, 0D0Eh
        jmp     COsetit
CursorOn endp



;==============================================================================
; InitPopup - PREPARES SYSTEM FOR POPUP, THEN CALLS Popup, THEN RESTORES
;
; ESTABLISH INTERNAL WORK STACK
; SAVE CPU REGS
; UPDATE InDosClr FLAG WITH CURRENT VALUE OF InDos
; SAVE PROCESS RELATED SYSTEM INFO
; SAVE USER INTERRUPT VECTORS
; INSERT SAFE USER INTERRUPT VECTORS
; SAVE CURRENT SCREEN 
; CALL POPUP ROUTINE
; RESTORE SCREEN
; RESTORE USER INTERRUPT VECTORS
; RESTORE PROCESS AND SYSTEM INFO
; CLEAR InDosClr FLAG TO PREVENT UNSAFE INT28 CALLs
; SEE IF DE-INSTALL SHOULD BE DONE (GetOutFlag==1)
; RESTORE CPU REGS
;==============================================================================

InitPopup       proc    near
                assume  ds:nothing, es:nothing, ss:nothing

; ------------  SWITCH TO PSP INTERNAL STACK

                mov     OldSS,ss                ;save current stack frame
                mov     OldSP,sp

                cli                             ;always CLI for the old chips
                mov     ss,OurSS                ;move SS here
                mov     sp,OurSP                ;move SP into position
                sti                             ;OK guys

; ------------  SAVE ALL REGS

                push    ax
                push    bx
                push    cx
                push    dx
                push    bp
                push    si
                push    di
                push    ds
                push    es

                mov     ax,cs
                mov     ds,ax                   ;mov DS here
                assume  ds:Cseg                 ;tell MASM that

; ------------  TAG VALUE OF InDos FLAG AT TIME OF POPUP

                or      TsrMode,InDosClr        ;assume InDos=0
                les     si,InDosPtr             ;now, ES:[SI]=InDos
                cmp     byte ptr es:[si],1      ;InDos set? (>2 impossible)
                jb      InDosSaved              ;NO - jump if all clear DOS
                and     TsrMode,not InDosClr    ;clear flag for popup InDos
InDosSaved:

; ------------  SAVE DOS 3.X EXTENDED ERROR INFO

                test    TsrMode,NewDos          ;really DOS 3.x?
                jz      Dos3Saved               ;NO - jump if not 3.x

                mov     ah,59h                  ;to get err info from DOS
                xor     bx,bx                   ;BX must be zero
                push    ds                      ;save DS (killed by DOS)
                int     21h                     ;ext err info in AX,BX,CX
                pop     ds                      ;restore
                mov     OldExtErr[0],ax         ;save
                mov     OldExtErr[2],bx
                mov     OldExtErr[4],cx

Dos3Saved:

; ------------  SAVE CURRENT BREAK STATE, RELAX BREAK CHECKING

                mov     ax,3302h                ;to swap DL with BREAK value
                xor     dl,dl                   ;DL=0=relax checking
                int     21h                     ;current level in DL
                mov     OldBreak,dl             ;save current level

; ------------  SAVE CURRENT USER INT VECTORS

                mov     ax,351bh                ;BIOS ctrl-break int
                int     21h                     ;ES:BX=vector
                mov     OldInt1BOff,bx          ;save it
                mov     OldInt1BSeg,es

                mov     ax,351ch                ;BIOS timer tick
                int     21h                     ;ES:BX=vector
                mov     OldInt1COff,bx          ;save it
                mov     OldInt1CSeg,es

                mov     ax,3523h                ;DOS ctrl-C
                int     21h                     ;ES:BX=vector
                mov     OldInt23Off,bx          ;save it
                mov     OldInt23Seg,es

                mov     ax,3524h                ;DOS crit err handler
                int     21h                     ;ES:BX=vector
                mov     OldInt24Off,bx          ;save it
                mov     OldInt24Seg,es

; ------------  INSERT DUMMY IRET INTO DANGEROUS VECTORS

                mov     dx,offset NopInt        ;now, DS:DX=dunny iret
                mov     ax,251bh                ;BIOS ctrlk-break handler
                int     21h                     ;set to IRET
                mov     ax,251ch                ;BIOS timer tick
                int     21h                     ;set to IRET
                mov     ax,2523h                ;DOS ctrl-C handler
                int     21h                     ;set to IRET

; ------------  ESTABLISH SAFE CRITICAL ERROR HANDLER

                mov     dx,offset OurInt24      ;now, DS:DX=safe crit err
                mov     ax,2524h                ;to set crit err handler
                int     21h

; ------------  SAVE CURRENT DTA AREA, SET OUR DEFAULT DTA

                mov     ah,2fh                  ;to obtain current DTA from DOS
                int     21h                     ;DTA addr now in ES:BX
                mov     OldDTAOff,bx            ;save it
                mov     OldDTASeg,es

                push    ds                      ;save DS for a while
                lds     dx,OurDTA               ;DS:DX=our DTA addr
                mov     ah,1ah                  ;to set DTA via DOS
                int     21h                     ;set that addr
                pop     ds                      ;restore DS

; ------------  SAVE CURRENT PSP, ESTABLISH OURS INSTEAD

                mov     ax,5100h                ;to get PSP from DOS
                int     21h                     ;current PSP now in BX
                mov     OldPSP,bx               ;save it
                mov     bx,OurPSP               ;het our PSP instead
                mov     ax,5000h                ;to set our PSP
                int     21h

; ------------  CALL USER POPUP ROUTINE

                call    FromScr                 ;save screen
                call    Popup                   ;finally!
                call    ToScr                   ;put screen back

; ------------  RESTORE TO SAVED CURRENT PROCESS

                mov     bx,OldPSP               ;new current process in BX
                mov     ax,5000h                ;to set PSP via DOS
                int     21h                     ;restore original PSP

; ------------  RESTORE SAVED DTA

                push    ds                      ;save DS for a while
                lds     dx,OldDTA               ;DS:DX=our DTA addr
                mov     ah,1ah                  ;to set DTA via DOS
                int     21h                     ;set that addr
                pop     ds                      ;restore DS

; ------------  RESTORE SAVED INTERRUPT VECTORS

                push    ds                      ;save for a while
                assume  ds:nothing              ;be careful about MASM

                lds     dx,OldInt1B             ;BIOS ctrl-break handler
                mov     ax,251bh
                int     21h

                lds     dx,OldInt1C             ;BIOS timer tick
                mov     ax,251ch
                int     21h

                lds     dx,OldInt23             ;ctrl-C handler (an IRET)
                mov     ax,2523h
                int     21h

                lds     dx,OldInt24             ;DOS crit err handler
                mov     ax,2524h
                int     21h

                pop     ds                      ;restore data seg DS
                assume  ds:Cseg

; ------------  RESTORE SAVED BREAK CHECKING LEVEL

                mov     ax,3301h                ;to set break check level
                mov     dl,OldBreak             ;get saved break state
                int     21h

; ------------  RESTORE DOS 3.X SPECIFIC SYSTEM INFO

                test    TsrMode,NewDos          ;using DOS 3.x
                jz      Dos3Restored            ;NO - jump if old DOS 2
                mov     dx,offset OldExtErr     ;DS:DX=3 words of ext err
                mov     ax,5d0ah                ;to set ext err info
                int     21h
Dos3Restored:

; ------------  RESET InDosSet FLAG VALUE TO PREVENT UNSAFE INT28

                or      TsrMode,InDosClr        ;now we only care that InDos=0

; ------------  DO WE WANT TO DE-INSTALL?

                cmp     GetOutFlag, 0           ;set in POPUP if user said
                je      RestoreTheRegs          ;to remove ourselves

                jmp     DeInstall               ;go die gracefully

; ------------  RESTORE USER REGS

RestoreTheRegs:
                pop     es
                pop     ds
                pop     di
                pop     si
                pop     bp
                pop     dx
                pop     cx
                pop     bx
                pop     ax
                assume  ds:nothing

; ------------  RETURN TO USER STACK

                cli                             ;always CLI for the old chips
                mov     ss,OldSS                ;restore SS
                mov     sp,OldSP                ;restore SP
                sti                             ;OK guys

                ret
InitPopup       endp

;==============================================================================
; Popup - POPUP USER ROUTINE
;
; ALL REGISTERS EXCEPT SS:SP AND DS MAY BE CHANGED.
; DS IS PRESET TO THE TSR DATA SEGMENT.
;
; NOTE: UPON ENTRY TO THIS ROUTINE ALL DOS FUNCTIONS MAY BE CALLED.
;       IF POPUP WAS DONE ON INT28, WITH CritErr-1, ALL DOS FUNCTIONS
;       THAT WOULD NORMALLY USE THE CONSOLE STACK, WILL GO TO THE CRITICAL
;       STACK, HENCE PREVENTING FURTHER POPUP DURING THE DOS CALL.
;       (HOWEVER, MOST TSRs WOULD NOT POPUP ANYWAY, SINCE InDos-2).
;
;       ADDRESSES OF THE InDos AND CritErr ARE STORED IN THE DOUBLE WORDS
;       InDosPtr AND CritErrPtr.
;
;       AT ENTRY CritErr FLAG IS 0 (ZERO), InDos NO GREATER THAN 1 (ONE).
;
;       THE SCREEN HAS ALREADY BEEN SAVED (BUT NOT CLEARED).
;       IT WILL BE RESTORED AUTOMATICALLY UPON EXIT.
;
;==============================================================================

ImageHandle     dw      0
ImageName       db      "C:\Memory.Img", 0        ;in root directory

NamePrompt      db      13, 10
                db      "Enter name of program to execute: "
NamePromptLen   equ     $-NamePrompt

WaitMsg1        db      13, 10, 13, 10
                db      "Swapping out...."
WaitMsgLen1     equ     $-WaitMsg1

KeyWaitMsg      db      13, 10
                db      "Press a key to continue."
KeyWaitMsgLen   equ     $-KeyWaitMsg

WaitMsg2        db      13, 10, 13, 10
                db      "Swapping in...."
WaitMsgLen2     equ     $-WaitMsg2

CRLFMsg         db      13, 10
CRLFMsgLen      equ     $-CRLFMsg

PrevSS          dw      0
PrevSP          dw      0
PrevDS          dw      0
PrevES          dw      0
Partition       dw      0

CmdLin          db      80 dup(0)


Popup           proc    near
                assume  ds:Cseg, es:nothing, ss:nothing

; ------------  IF NO PROGRAM IS ACTIVE, DON'T SAVE 192K
CheckCurrPgm:
                mov     ax, cs                  ;point to our MCB
                dec     ax                      ;(is 16 bytes below PSP)
                mov     es, ax                  ;'Z' means last one
CheckThisMCB:
                cmp     Byte Ptr es:[0000], "Z" ;MCB signature byte
                je      DoLastMCB               ;this is last in chain
                cmp     Byte Ptr es:[0000], "M" ;'M' is other possibility
                je      GotoNextMCB             ;try the next one

                jmp     PopExit                 ;MCB's are corrupted.

GotoNextMCB:
                add     ax, es:[0003]           ;length of this block
                inc     ax                      ;plus 1
                mov     es, ax                  ;points to next MCB
                jmp     CheckThisMCB

DoLastMCB:
                mov     ax, Word Ptr es:[0003]  ;get the length
                cmp     ax, 12288               ;is it size we need? (paras)
                ja      DoThisBlock             ;yep.

                jmp     PopExit                 ;nope; don't get fancy

DoThisBlock:
                mov     ApplPSP, 0              ;default to "it's free"
                mov     bx, Word Ptr es:[0001]  ;get owner of block
                cmp     bx, 0                   ;is it a free block?
                jne     SetupForSwap            ;no, have to swap it out

                jmp     ExecIt                  ;free block (don't swap)

SetupForSwap:
                mov     ax, es
                inc     ax                      ;point up to PSP
                mov     ApplPSP, ax

; ------------  IF COMMAND.COM OWNS THAT BLOCK, FORGET IT.

                mov     ax, cs                  ;point to us
                cmp     ax, bx                  ;owner of block we want
                jbe     SwapMemory              ;it's okay to use Command

                call    ErrBeep                 ;oh-oh, we can't do anything
                jmp     PopExit

; ------------  SAVE 192K OF UPPER MEMORY IN FILE "MEMORY.IMG"
SwapMemory:
                call    CLS

                mov     cx, 80
                mov     di, offset CmdLin
                mov     al, 0
        rep     stosb

                mov     ah, 40h
                mov     bx, 1
                mov     cx, NamePromptLen
                mov     dx, offset NamePrompt
                int     21h

                call    CursorOn

                mov     ah, 3Fh
                mov     bx, 0
                mov     cx, 80
                mov     dx, offset CmdLin
                inc     dx
                int     21h
                mov     CmdLin, al

                mov     si, offset CmdLin
                add     si, ax
                mov     Byte Ptr [si], 0

                mov     ah, 40h
                mov     bx, 1
                mov     cx, WaitMsgLen1
                mov     dx, offset WaitMsg1
                int     21h

; ------------  FIGURE OUT HOW MUCH MEMORY THERE IS; SWAP TOP 192K

                int     12h             ;should return ax=640
                sub     ax, 192         ;subtract what we need
                mov     cx, 6           ;shift left 6 bits in order
                shl     ax, cl          ;to get it in paragraphs
                mov     Partition, ax   ;remember para addr for later

                mov     ah, 3Ch
                mov     dx, offset ImageName
                mov     cx, 0
                int     21h             ;create memory image file
                jnc     SaveTheHndl

                call    ErrBeep         ;Error!
                jmp     PopExit         ;unable to create file

SaveTheHndl:
                mov     ImageHandle, ax ;we'll need it later
Write192K:
                mov     PrevDS, ds      ;we fool with ds quite a bit...
                mov     bx, ax          ;we also need handle now
                mov     ah, 40h
                mov     cx, 0FFF0h      ;unsigned 65520 quantity
                mov     dx, 0
                mov     ds, cs:Partition ;recall para addr from above
                int     21h             ;write 64K to disk (well, almost)
                jc      Error192        ;if I/O error...
                cmp     ax, cx          ;disk full?
                jne     Error192

                mov     ax, ds
                add     ax, 4095        ;bump up to next segment
                mov     ds, ax
                mov     ah, 40h
                int     21h             ;write next 64K (little less, really)
                jc      Error192        ;if I/O error...
                cmp     ax, cx          ;disk full?
                jne     Error192

                mov     ax, ds
                add     ax, 4095        ;final segment
                mov     ds, ax
                mov     ah, 40h
                int     21h             ;write last 64K segment
                jc      Error192        ;if I/O error...
                cmp     ax, cx          ;disk full?
                jne     Error192

                mov     ah, 3Eh
                int     21h             ;now close it (196,560 bytes written)

                mov     ax, cs:PrevDS   ;restore our ds
                mov     ds, ax

                jmp     FreeIt

Error192:
                call    ErrBeep         ;do it twice here for id
                call    ErrBeep
                mov     ah, 3Eh         ;close it and get out
                int     21h
                mov     ds, cs:PrevDS   ;who knows where it wound up??
                jmp     PopExit


; ------------  FREE THAT MEMORY SPACE
FreeIt:
                push    es
                mov     ax, 0
                mov     es, ax
                mov     Byte Ptr es:[050Fh], 0
                pop     es

                mov     bx, ApplPSP             ;pretend we're in Appl.
                mov     ax,5000h                ;to set PSP via DOS
                int     21h                     ;set Application PSP

                mov     ax, ApplPSP
                dec     ax
                mov     es, ax          ;look at memory control block
                mov     bx, word ptr es:[3]     ;get current allocation
                mov     CurrentAlloc, bx        ;and save it
                sub     bx, 12160       ;190K = 12160 paragraphs
                mov     es, ApplPSP     ;block to be shrunk
                mov     ah, 4Ah         ;setblock
                int     21h             ;we now have some free memory
                jnc     ExecIt

                mov     bx,OurPSP       ;our current process in BX
                mov     ax,5000h        ;to set PSP via DOS
                int     21h             ;restore our PSP

                call    ErrBeep         ;memory request failed
                call    ErrBeep         ;do twice
                jmp     PopExit         ;let's get out of here

; ------------  RUN PROGRAM
ExecIt:
        push    ds
        pop     es

        call    CLS

        mov     bx,OurPSP       ;our current process in BX
        mov     ax,5000h        ;to set PSP via DOS
        int     21h             ;restore our PSP

        mov     cs:PrevES, es
        mov     cs:PrevDS, ds
        mov     cs:PrevSS, ss
        mov     cs:PrevSP, sp

        mov     si, offset CmdLin
        Int     2Eh

        CLI
        mov     ss, cs:PrevSS
        mov     sp, cs:PrevSP
        mov     ds, cs:PrevDS
        mov     es, cs:PrevES
        STI

; ------------  GRAB THE MEMORY BACK (BY ALLOCATING ALL OF IT AGAIN)
ResetMemory:
                cmp     ApplPSP, 0              ;is application alive?
                jne     GetMain                 ;yes, re-alloc the memory

                jmp     PopExit                 ;no, no need to re-alloc

GetMain:
                mov     bx,ApplPSP              ;pretend we're the appl.
                mov     ax,5000h                ;to set PSP via DOS
                int     21h                     ;restore our PSP

                mov     es, ApplPSP             ;point to Appl's PSP
                mov     bx, CurrentAlloc        ;what it used to have
                mov     ah, 4Ah                 ;ask for old amt. back
                int     21h                     ;will DOS do it?
                jnc     RestoreImage            ;yep, no error

                call    ErrBeep                 ;realloc-memory failed
                call    ErrBeep                 ;thrice
                call    ErrBeep                 ;and fall through restore

; ------------  RESTORE THE 192K MEMORY IMAGE FROM DISK
RestoreImage:
                push    ds
                pop     es

                mov     ah, 40h
                mov     dx, offset KeyWaitMsg
                mov     cx, KeyWaitMsgLen
                mov     bx, 1
                int     21h

                mov     ax, 0
                int     16h

                mov     ah, 40h
                mov     dx, offset WaitMsg2
                mov     cx, WaitMsgLen2
                mov     bx, 1
                int     21h

                mov     bx,OurPSP       ;our current process in BX
                mov     ax,5000h        ;to set PSP via DOS
                int     21h             ;restore our PSP

                mov     ax, 3D00h               ;"compatibility mode" open
                mov     dx, offset ImageName
                int     21h
                jnc     Read192K

                call    ErrBeep                 ;a "whoops!"
                jmp     PopExit

Read192K:
                mov     ImageHandle, ax         ;it may have changed
                mov     bx, ax
                mov     PrevDS, ds              ;so we can mess with it
                mov     ah, 3Fh                 ;read back the image
                mov     ds, cs:Partition        ;point to upper 192K
                mov     dx, 0
                mov     cx, 0FFF0h              ;same segment size as before
                int     21h                     ;read it
                jc      Error192r               ;if I/O error...
                cmp     ax, cx                  ;not all bytes read?
                jne     Error192r

                mov     ax, ds
                add     ax, 4095                ;bump up to next segment
                mov     ds, ax
                mov     ah, 3Fh                 ;here we go again
                int     21h
                jc      Error192r               ;if I/O error...
                cmp     ax, cx                  ;not all bytes?
                jne     Error192r

                mov     ax, ds
                add     ax, 4095                ;bump up to last segment
                mov     ds, ax
                mov     ah, 3Fh                 ;for the last time
                int     21h
                jc      Error192r               ;if I/O error...
                cmp     ax, cx                  ;everything there?
                jne     Error192r

                mov     ah, 3Eh                 ;close it
                int     21h
                mov     ds, cs:PrevDS           ;set it back

                jmp     PopExit                 ;all done!

Error192r:
                call    ErrBeep                 ;3 times for id
                call    ErrBeep
                call    ErrBeep
                mov     ah, 3Eh                 ;close error file
                int     21h                     ;restore ds
                mov     ds, cs:PrevDS           ;and fall through to exit

; ------------  RETURN TO THE ORIGINAL APPLICATION
PopExit:
                push    ds
                pop     es
                ret

; ------------  UNINSTALL DOESN'T APPLY HERE

UnInstall:
                mov     GetOutFlag, 1           ;will do it later
                ret
Popup           endp

;==============================================================================
; TSR IRON CURTAIN 
;==============================================================================

TsrCurtain:


;***********************************************************



;==============================================================================
; NON-RESIDENT MESSAGES FOR INIT
;==============================================================================

SecondMsg       label   byte
db      7,13,10
db      "The Context Switch program is already loaded!",13,10
db      "$"

HotKeyMsg       label   byte
db      13,10,13,10
db      "CONTEXT SWITCH"
db      13,10
db      "Hit <Alt Right-Shift> to invoke Context Switch."
db      13,10,13,10
db      "$"

Dos1Msg label   byte
db      7,13,10
db      "Must use DOS release 2.00 or later.",13,10,10
db      "$"

BadDosMsg       label   byte
db      7,13,10
db      "Did not recognize DOS version.",13,10,10
db      "$"

; ------------  DOS ERROR LEVEL EXIT CODES

xOk             equ     0                       ;normal, OK exit
xSecond         equ     1                       ;TSR already loaded
xBadDos         equ     2                       ;CritErr flag not found

;==============================================================================
; DISPLAY BANNER, INITIALIZE SYSTEM DATA, CHECK IF ALREADY LOADED,
; HOOK INTO INTERRUPT CHAIN, TERMINATE, BUT STAY RESIDENT.
;==============================================================================

Init            proc    near
                assume  ds:Cseg, es:nothing, ss:nothing

                call    CLS
                mov     dx,0
                call    Gotoxy

; ------------  USE INT16H DIAGNOSTIC TO SEE IF TSR ALREADY INSTALLED

                mov     ax,GetId                ;INT16h diagnostic request
                int     16h                     ;now, AX=MyId if installed
                cmp     ax,MyId                 ;TSR already installed?
                jne     CheckDos                ;NO - jump if not installed

; ------------  TSR ALREADY INSTALLED, DISPLAY MSG, EXIT

                mov     dx,offset SecondMsg
                mov     ah,9
                int     21h                     ;display already-installed
                mov     dx,offset HotKeyMsg
                mov     ah,9
                int     21h                     ;be kind & disp hot key
                mov     ax,4c00h + xSecond      ;error level in AL
                int     21h                     ;abort now

; ------------  It's only DOS 1!  Too bad.

Dos1:           mov     dx,offset Dos1Msg
                mov     ah,9
                int     21h                     ;display msg about DOS 1
                int     20h                     ;no err level for DOS 1

; ------------  ENSURE DOS VERSION IS NEWER THAN 2.00

CheckDos:       or      TsrMode,NewDos          ;assume suing DOS 3.x
                mov     ah,30h                  ;to get DOS version number
                int     21h                     ;version is AL.AH
                cmp     al,2                    ;release 2 or newer?
                jb      Dos1                    ;NO - jump if DOS 1 in use
                ja      DosFlags                ;jump if DOS 3.x
                and     TsrMode,not NewDos      ;now, say we use DOS 2.x

; ------------  INITIALIZE PTRS TO DOS FLAGS - 1ST InDos

DosFlags:       mov     ax,3400h                ;to get InDos ptr
                int     21h                     ;ES:BX=seg:off of InDos
                mov     InDosOff,bx             ;save ptr
                mov     InDosSeg,es

; ------------  WE NEED CritErr TO USE PSP FUNCTIONS IN DOS 2.X

                xor     dl,dl                   ;DL=0=this is 1st scan
                mov     CritErrSeg,es           ;DOS seg still in ES
CritScan:       mov     di,bx                   ;start search at InDos
                mov     cx,2000h                ;search max 1000h words
                mov     ax,3e80h                ;opcode CMP BYTE PTR [CritErr]
                cld                             ;better serach forward

CritScan2:      repne   scasw                   ;search till found or end
                jne     NoCritFound             ;jump if CMP not found
                                                ;ES:[DI-2] at:
                                                ;       CMP BYTE PTR [CritErr]
                                                ;       JNZ ...
                                                ;       MOV SP,stack addr
                cmp     byte ptr es:[di][5],0bch ;really CMP SP there?
                jne     CritScan2               ;NO - scan again if not
                mov     ax,word ptr es:[di]     ;now, AX=CritErr offset
                mov     CritErrOff,ax           ;save it
                jmp     short InitData          ;OK to end this now

NoCritFound:    or      dl,dl                   ;was this1 st scan?
                jnz     BadDos                  ;NO - CritErr not founbd at all
                inc     dl                      ;DL=1=this is 2nd scan
                inc     bx                      ;try scan at odd/even offset
                jmp     CritScan                ;scan again
                
; ------------  COULD NOT LOCATE DOS CritErr FLAG - THAT'S AN ERROR

BadDos:         mov     dx,offset BadDosMsg
                mov     ah,9
                int     21h                     ;display msg about that
                mov     ax,4c00h + xBadDos      ;err level in AL
                int     21h                     ;OK to use 4C (DOS >= 2)

; ------------  INITIALIZE SYSTEM DATA VARIABLES

InitData:                                       ;store position for stack
                mov     OurSP,TsrCurtain - ComEntry + 100h + StackSize
                mov     OurSS,cs                ;stack seg is code seg

                mov     ax,5100h                ;to get current PSP from DOS
                int     21h                     ;PSP now in BX
                mov     OurPSP,bx               ;save our PSP

                mov     ah,2fh                  ;to get current DTA from DOS
                int     21h                     ;now, ES:BX=current DTA
                mov     OurDTAOff,bx            ;save it
                mov     OurDTASeg,es

                and     KeyMode,not HotIsShift  ;hotkey is not shift state
                or      TsrMode,InDosClr        ;will prevent unsafe INT28s

; ------------  SAVE VECTORS FOR OUR MONITOR INTERRUPTS

                mov     ax,3508h                ;BIOS timer0 tick handler
                int     21h                     ;ES:BX=vector
                mov     OldInt08Off,bx
                mov     OldInt08Seg,es

                mov     ax,3509h                ;BIOS kb HW handler
                int     21h                     ;ES:BX=vector
                mov     OldInt09Off,bx
                mov     OldInt09Seg,es

                mov     ax,3513h                ;BIOS disk I/O service
                int     21h                     ;ES:BX=vector
                mov     OldInt13Off,bx
                mov     OldInt13Seg,es

                mov     ax,3516h                ;BIOS kb read
                int     21h                     ;ES:BX=vector
                mov     OldInt16Off,bx
                mov     OldInt16Seg,es

                mov     ax,3521h                ;DOS functions dispatcher
                int     21h                     ;ES:BX=vector
                mov     OldInt21Off,bx
                mov     OldInt21Seg,es

                mov     ax,3528h                ;DOS idle hook
                int     21h                     ;ES:BX=vector
                mov     OldInt28Off,bx
                mov     OldInt28Seg,es

                mov     ax,3523h                ;Previous ^C handler
                int     21h                     ;ES:BX=vector
                mov     PrevInt23Off,bx
                mov     PrevInt23Seg,es


; ------------  ESTABLISH IRET INT23 TO PREVENT BREAK DURING VECTOR FIX

                mov     dx,offset NopInt        ;DS:DX=dummy vector to set
                mov     ax,2523h                ;to set ^C handler through DOS
                int     21h                     ;now, no break will occur

; ------------  SAVE VECTORS FOR OUR MONITOR INTERRUPTS

                mov     ax,2508h                ;to set our INT08h handler
                mov     dx,offset OurInt08      ;DS:DX=new vector
                int     21h                     ;let DOS set vector

                mov     ax,2509h                ;to set our INT09h handler
                mov     dx,offset OurInt09      ;DS:DX=new vector
                int     21h                     ;let DOS set vector

                mov     ax,2513h                ;to set our INT13h handler
                mov     dx,offset OurInt13      ;DS:DX=new vector
                int     21h                     ;let DOS set vector

                mov     ax,2516h                ;to set our INT16h handler
                mov     dx,offset OurInt16      ;DS:DX=new vector
                int     21h                     ;let DOS set vector

                mov     ax,2521h                ;to set our INT21h handler
                mov     dx,offset OurInt21      ;DS:DX=new vector
                int     21h                     ;let DOS set vector

                mov     ax,2528h                ;to set our INT28h handler
                mov     dx,offset OurInt28      ;DS:DX=new vector
                int     21h                     ;let DOS set vector

                mov     word ptr savearea+2, ds ;seg of scrnhold
                mov     ax, offset scrnhold     ;ofs of scrnhold
                mov     word ptr savearea, ax   ;for scrn-save

                mov     ah, 15
                int     10h                     ;get current video mode

                cmp     al, 7                   ;if BW, then we need
                jne     IsColor
                mov     word ptr videocard + 2, 0B000h ;to set video address
                mov     word ptr videocard, 0          ;to B000:0000
                mov     videoflag, 1                   ;and remember mode 
                jmp     SayHello
IsColor:
                mov     word ptr videocard + 2, 0B800h ;if color mode, then
                mov     word ptr videocard, 0          ;address is B800:0000
                mov     videoflag, 0                   ;remember we're in color

; ------------  DISLAY MSG ABOUT HOW WELL THIS IS ALL RUNNING
SayHello:
                mov     dx,offset HotKeyMsg
                mov     ah,9
                int     21h                     ;disp hot key msg.

                mov     dx,(TsrCurtain-ComEntry+100h+StackSize+15) SHR 4
                mov     ax,3100h + xOk          ;TSR, AL=err level
                int     21h
Init            endp

;==============================================================================

Cseg            ends
                end     ComEntry


===============================================================

Cseg            ends
                end     ComEntry

