PAGE	60,132
TITLE	Ram-Resident Program Shell, Preliminary Version (Lesson 2)
SUBTTL	Date 02-02-87 -- Tutorial for Microsoft Forum  Version 0.03
;-----------------------------------------------------------------------------
;	Ram-Resident Program Shell
;
;
;The software, documentation and source code are: 
; 
;	(C) Copyright 1986, 1987
;	Chip Rabinowitz
;	All Rights Reserved 
; 
;	51 East Rogues Path
;	Huntington Station NY 11746
;	Assistant SysOp, Microsoft Forum, Compuserve PPN 76703,350
; 
; 
;COPYRIGHT NOTICE AND WARRANTY INFORMATION 
;----------------------------------------- 
; 
;This document ("the source code"), other accompanying written and disk-based
;notes and specifications ("the documentation"), and all referenced and related
;program files accompanying the source code and the documentation ("the
;software") are copyrighted by Chip Rabinowitz.  
;
;This program has been uploaded to Compuserve as part of a Tutorial in how
;to write Terminate-But-Stay-Resident programs, being conducted on
;Compuserve's Microsoft Special Interest Group during early 1987/
;
;This code (and all other code uploaded by the Copyright Holder as a 
;part of this TSR Tutorial) may be used with the following restrictions: 
;
;(1) If all or part of this code is used as part of a software package of 
;ANY kind, the following acknowledgement must be used in the Documentation 
;accompanying said package:
;
;	'Parts of the Resident Program Management for this Product are'
;	'Copyright (C) 1986, 1987 by Chip Rabinowitz, and are incorporated'
;	'into this product courtesy of the Ringmaster Development Team'
;
;(2) If all or part of this code is used as part of a software package that
;is placed in the public domain, no further restrictions apply.
;
;(3) If all or part of this code is used as part of a software package that
;is being distributed as 'shareware', a one-time-only donation of $10 will be
;accepted for the express purpose of continuing research into TSR standards.
;Note that this is a voluntary contribution, and should be sent to the
;Ringmaster Development Team at the address printed above.
;
;(4) If all or part of this code is used as part of a software package that
;will be distributed as a commercial product, a one-time-only payment of
;$25 will be accepted the express purpose of continuing research into TSR 
;standards.  Note that in THIS CASE ONLY, this is not a voluntary 
;contribution.  Commercial Developers are REQUIRED to make payment prior to
;incorporating these routines into their product.
;
;No copy of the source code may be distributed or given away without the
;accompanying documentation; and this notice must not be removed. 
; 
;There is no warranty of any kind associated with this software, and the
;copyright owner is not liable for damages of any kind.  By using this
;software, you agree to this. 
; 
;

XON     equ	11h
XOFF    equ	13h
TRUE    equ	1
FALSE   equ	0
CR	equ	0dh
LF	equ	0ah
TAB	equ	9


HOTKEYON	equ	01h	;hot key pressed
SHIFTSON	equ	02h	;shift states match
TSRACTIVE	equ	04h	;tsr is running in foreground
INT28ACTIVE	equ	08h	;INT28 routine is running in background

KB_DATA	equ	60h
KB_CTL	equ	61h
KB_FLG1	equ	17h
KB_FLG2	equ	18h
BIOS_DATA	equ	40h

code segment para public 'CODE'
	org 100h
	assume	cs:code, ds:code, es:code
start:	jmp	begin

	db	1bh,'[2J'
cpyrt   db  'Ram-Resident Program Shell, Preliminary Version (Lesson 2)'
	db	CR,LF,LF
    db  'Copyright (C) 1986, 1987 Chip Rabinowitz'
	db	CR,LF,'All Rights Reserved',CR,LF,LF,'$'
hello	db	'Hello World!',CR,LF,'$'
bad_dos_msg	db	'Incompatible DOS version .... Aborting!',07h,CR,LF,'$'

tsr_parms:
	PSP	dw	?	;PSP of TSR
	OLDPSP	dw	?	;location to save current PSP
	DTA	dd	?	;DTA of TSR
	OLDDTA	dd	?	;location to save current DTA
	DSEG	dw	?	;TSR's Data Segment
	SSEG	dw	?	;TSR's Stack Segment ....
	SPTR	dw	0fffeh	;.... and Stack Pointer
	ISSEG	dw	0	;interrupted SS ...
	ISPTR	dw	0	;..... and SP
	SHIFTST	db	0	;Shift State to Activate
	HOTKEY	db	0ffh	;scan code to activate
	STATUS	dw	0	;current status words
	POPUP	dd	0	;far pointer to popup routine

intflags	db	0
ININT13		equ	04h	;Bios disk interrupt
ININT21		equ	08h	;DOS interrupt

dosversion	db	0	;current version of DOS
waitcount	db	0	;wait to activate count

indosflag	dd	0	;Pointer to INDOS flag
doscriterr	dd	0	;Pointer to DOS Critical Error Flag

dossseg		dw	0	;pointer to dos stack segment
dossptr		dw	0	;pointer to dos stack pointer
dossize		dw	0	;dos stack size

dos21funcs	label	byte
	db	0,	0,	0,	0,	0,	0,	0,	0	;0-7
	db	0,	0,	0,	0,	0,	0ffh,	0ffh,	0ffh	;8-f
	db 	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh	;10-17
	db	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh	;18-1f
	db	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0,	0ffh	;20-27
	db	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0	;28-2f
	db	0ffh,	0,	0,	0,	0,	0,	0ffh,	0ffh	;30-37
	db	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh	;38-3f
	db	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh	;40-47
	db	0,	0,	0,	0,	0ffh,	0,	0ffh,	0ffh	;48-4f **
	db	0,	0,	0,	0,	0ffh,	0,	0ffh,	0ffh	;50-57
	db	0,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0,	0	;58-5f
	db	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh,	0ffh	;60-67

;
; ***************************************************************************
;	These are the bios interrupt vectors
;

BIOSI8	equ	08h		;Bios Timer interrupt
BIOSI9	equ	09h		;BIOS Hardware Keyboard interrupt
BIOSIB	equ	0Bh
BIOSIC	equ	0Ch
BIOSI10	equ	10h
BIOSI13	equ	13h		;Bios Disk interrupt
BIOSI14	equ	14h
BIOSI15	equ	15h
BIOSI16	equ	16h		;Bios Keyboard interrupt
BIOSI17	equ	17h
BIOSI1C	equ	1Ch
DOSI21	equ	21h		;DOS service router interrupt
DOSI28	equ	28h		;DOS Idle interrupt
DOSI33	equ	33h		;mouse interrupt
BIOSI1B	equ	1Bh
DOSI23	equ	23h
DOSI24	equ	24h
;
oldint8		dd	0	;BIOS Hardware Timer Interrupt
		db	BIOSI8
		dw	offset newint8	;replacement vector
oldint9		dd	0	;BIOS Hardware Keyboard Interrupt
		db	BIOSI9
		dw	offset newint9	;replacement vector
oldint13	dd	0	;BIOS Disk Interrupt
		db	BIOSI13
		dw	offset newint13	;replacement vector
oldint16	dd	0	;BIOS Software Keyboard Interrupt
		db	BIOSI16
		dw	offset newint16	;replacement vector
oldint21	dd	0	;DOS Services Interrupt
		db	DOSI21
		dw	offset newint21	;replacement vector
oldint28	dd	0	;DOS Idle Interrupt
		db	DOSI28
		dw	offset newint28	;replacement vector
oldint1B	dd	0	;control-C vector
		db	BIOSI1B
		dw	offset intret	;replacement vector
oldint23	dd	0	;control-C vector
		db	DOSI23
		dw	offset intret	;replacement vector
oldint24	dd	0	;critical error vector
		db	DOSI24
		dw	offset newint24	;replacement vector

;
; ***************************************************************************
;	Function to restore interrupts.
;
;	If AX=0, this function does popup-interrupts (1B, 23 and 24)
;	If AX=anything else, this function does initial interrupts
;
restore_int proc near
	push	ds
	push	ax
	push	dx
	push	si
	push	di
	push	cx
	mov	di,7			;7 bytes per structure

	or	ax,ax
	jnz	restore1

	mov	cx,3			;three times through
	mov	si,offset oldint1B	;start at table beginning
	jmp	restore_loop
restore1:
	mov	cx,06h			;six interrupts!
	mov	si,offset oldint8	;top of table

restore_loop:
	push	cx
	push	di			;save structure size
	push	ds
	mov	al,4[si]		;interrupt number
	mov	dx,[si]			;IP of interrupt vector
	mov	ds,2[si]		;CS of interrupt vector
	mov	ah,25h			;set interrupt vector function
	int	21h
	pop	ds
	xor	ax,ax			;clear this entry from table
	mov	[si],ax			;so it shows not used
	mov	2[si],ax
	pop	di			;get size back
	add	si,di			;do it again
	pop	cx
	loop	restore_loop

	pop	cx
	pop	di
	pop	si
	pop	dx
	pop	ax
	pop	ds
	ret
restore_int endp
;
; ***************************************************************************
;	Function to set interrupts.
;
;	If AX=0, this function does popup-interrupts (1B, 23 and 24)
;	If AX=anything else, this function does initial interrupts
;
setup_int proc near
	push	es
	push	ax
	push	bx
	push	dx
	push	di
	push	si
	push	cx
	mov	di,7			;7 bytes per structure

	or	ax,ax
	jnz	setup1

	mov	cx,3			;three times through
	mov	si,offset oldint1B	;start at table beginning
	jmp	setup_loop
setup1:
	mov	cx,06h			;six interrupts!
	mov	si,offset oldint8	;top of table

setup_loop:
	push	cx
	push	di			;save structure size
;	push	ds

	mov	ax,[si]			;check for value
	or	ax,ax			;if not zero, next one
	jnz	setup2
	mov	al,4[si]		;interrupt number
	mov	ah,35h			;get interrupt vector function

	int	21h
;	pop	ds
	mov	[si],bx			;save IP
	mov	2[si],es		;save CS

	mov	al,4[si]		;interrupt number
					;DS already contains correct segment
	mov	dx,5[si]		;IP of interrupt vector
	mov	ah,25h			;set interrupt vector function
	int	21h

setup2:
	pop	di			;get size back
	add	si,di			;do it again
	pop	cx
	loop	setup_loop
ex_set:
	pop	cx
	pop	si
	pop	di
	pop	dx
	pop	bx
	pop	ax
	pop	es
	ret
setup_int endp

setdta	macro	ddptr
	push	ds
	mov	ax,1a00h	;Function used to get current DTA address }
	mov	dx,word ptr ddptr	;Offset of DTA returned }
	mov	ds,word ptr ddptr+2	;Segment of DTA returned by DOS }
	int	21h		;Execute MSDos function request }
	pop	ds
	endm

getdta	macro	ddptr
	push	es
	mov	ax,2f00h	;Function used to get current DTA address }
	int	21h		;Execute MSDos function request }
	mov	word ptr ddptr+2,es		;Segment of DTA returned by DOS }
	mov	word ptr ddptr,bx		;Offset of DTA returned }
	pop	es
	endm

;
; ***************************************************************************
;	S E T   P S P
;
;	A bug in DOS 2.0, 2.1, causes DOS to clobber its standard stack   }
;	Then the PSP get/set functions are issued at the DOS prompt. The  }
;	following checks are made, forcing DOS to use the "critical"      }
;	stack when the TSR enters at the INDOS level.                     }
;
;		BX contains the PSP segment to set
;
setpsp	proc	near
	call	checkindos		;If Version < 3, and INDOS,...
	jz	do_set_psp
	mov	byte ptr es:[di],0ffh	;set critical flag


do_set_psp:
	mov	ax,5000h		;set PSP* function
					;BX already holds segment
	int 21h				;DOS function request

	call	checkindos		;If Version < 3, and INDOS,...
	jz	set_out
	mov	byte ptr es:[di],0h	;set critical flag

set_out:
	ret

setpsp	endp

;
; ***************************************************************************
;	G E T   P S P
;
;	A bug in DOS 2.0, 2.1, causes DOS to clobber its standard stack   }
;	Then the PSP get/set functions are issued at the DOS prompt. The  }
;	following checks are made, forcing DOS to use the "critical"      }
;	stack when the TSR enters at the INDOS level.                     }
;
;		The PSP Segment is returned in BX
;
getpsp	proc	near
	call	checkindos		;If Version < 3, and INDOS,...
	jz	do_get_psp
	mov	byte ptr es:[di],0ffh	;set critical flag

do_get_psp:
	mov	ax,5100h		;get PSP function
	int 21h				;DOS function request
					;BX now holds PSP segment
	call	checkindos		;If Version < 3, and INDOS,...
	jz	get_out
	mov	byte ptr es:[di],0h	;clear critical flag

get_out:
	ret

getpsp	endp

;
; ***************************************************************************
;	This routine checks the version of DOS in use, and returns the
;	ZF=0 (Not zero) if version < 3.0, and INDOS is set.  Internal function
;	is called ONLY by getpsp and setpsp.
;
checkindos proc near
	mov	al,dosversion
	cmp	al,3			;If Version is less than 3.0 ...
	jge	ok_ret
	mov	es,word ptr indosflag+2	; ... and ...
	mov	di,word ptr indosflag
	mov	al,byte ptr es:[di]
	or	al,al			;INDOS is set ...
	jz	ok_ret
	mov	es,word ptr doscriterr+2; ... then ...
	mov	di,word ptr doscriterr
	jmp	fixit			;exit with not zero
ok_ret:
	xor	al,al			;set zero flag
fixit:
	ret

checkindos endp

;
; ***************************************************************************
;	Internal routine to establish addressibility of the data segment
;
dds proc near
	push	cs
	pop	ds
	ret
dds endp

newint9 proc far

;NOTE:  None of this pusing/popping is really necessary for THIS particular
;TSR, so I;ve commented it out.
;
;	pushf
;	cli
;	push	ds
;	push	es
;	push	ax
;	push	bx
;	push	cx
;	push	dx
;	push	di
;	push	si

;	push	cs
;	pop	ds

;internal INT9 processing here

;getkey_out:
;	pop	si
;	pop	di
;	pop	dx
;	pop	cx
;	pop	bx
;	pop	ax
;	pop	es
;	pop	ds
;out_9:
;	popf

;	cli
	jmp	dword ptr cs:oldint9


;
; ***************************************************************************
;	This is the int return (IRET) dummy pointer 
;
intret:
	iret

;This routine will be used at some future point ... commented out for now.
;
;ignore_9:
;	in	al,60h
;	push	ax
;	pop	ax
;	in	al,61h
;	mov	ah,al
;	or	al,80h
;	out	61h,al
;	xchg	ah,al
;	out	61h,al
;	mov	al,20h
;	out	20h,al
;	pop	ax
;	jmp	short out_9

newint9 endp

new_bios_flag	db	0

newint16 proc far
start_again:
	cmp	ah,00		;if char request,
	je	func00		;loop for character
	cmp	ah,01		;if character availability test
	je	func01		;go check for char
	push	ax
	and	ah,10h		;test for extra bios
	pop	ax
	jz	gobios16
	mov	cs:new_bios_flag,10h
	and	ah,3
	jmp	short start_again
gobios16:
	or	ah,cs:new_bios_flag
	jmp	dword ptr cs:[oldint16]
				;go to bios interrupt 16
func01:
	push	ds
	push	si
	push	di
	call	dds
func01A:
	call	keystat		;look at key buffer
	pushf			;save return flags
	jz	fret01		;return if no key
	call	hotcheck	;test for hot key
	jnz	fret01		;not there
	popf			;flags back for loop
	jmp	func01A		;it was a hot key -- skip it!
fret01:
	mov	new_bios_flag,0
	popf			;flags back
	pop	di
	pop	si
	pop	ds
	ret	2		;return to user
func00:
	push	ds
	push	si
	push	di
	call	dds
func00A:
	call	keystat		;wait until character available
	jz	func00A		;loop it
	call	hotcheck	;test for a hot key
	jz	func00A		;it sure was -- start loop again

b_func0:
	mov	ah,0		;get the next user key
	or	ah,new_bios_flag
	pushf			;simulate it ...
	call	dword ptr cs:oldint16	;do bios

func_out:
	pushf			;save return flags
	jmp	fret01		;restore and return

newint16 endp

;
;       call the background task if no key is available
;
keystat proc near
				;preserves all registers except
				;AX,SI,DI,DS & flags
	push	cx
	push	bx

	push	ax
	push	ds
	cld

	mov	ax,BIOS_DATA
	mov	ds,ax		;set data segment
	mov	si,KB_FLG1	;set source

	lodsw			;get the high and low bytes
	pop	ds
	mov	bx,ax		;save it away
	pop	ax


	mov	al,SHIFTST	;shift state check

	or	al,al		;is it zero?
	jz	sim_16		;yes -- set flag for simulation

	push	bx		;save current flags
	and	bl,al		;mask bits
	cmp	al,bl		;are we the same?
	pop	bx
	jne	skip_16A	;nope -- next one
sim_16:
	or	word ptr STATUS,SHIFTSON	;set the flag

	mov	al,HOTKEY	;is this control block the last one?

	or	al,al
	jnz	short skip_16
	or	word ptr STATUS,HOTKEYON	;turn on hotkey flag
	jmp	short skip_16
skip_16A:
	and	word ptr STATUS,NOT SHIFTSON	;set the flag

skip_16:
	mov	ah,1			;see if character is available
	or	ah,new_bios_flag
	pushf				;simulate interrupt
	call	dword ptr cs:oldint16

	jnz	exit_161		;got one

chkdos_161:
	cld
	push	ds			;check if dos critical error in effect
	push	si
        lds     si,cs:doscriterr	;zero says dos is interruptable
	lodsb				;$ff  says dos is in a critical state
	lds     si,cs:indosflag		;if indos then int $28 issued by dos
	or	al,[si]			;so we dont have to.
					;account for active interrupts
	or	al,cs:intflags		;any flags says we dont issue call
	pop	si			;to the background.
	pop	ds
	cmp	al,01			;must be indos flag only
	jg	skip28			;dos cannot take an interrupt yet
	int	28h			;call dos idle function (background dispatch).
skip28:
	xor	ax,ax			;show  no keycode available
exit_161:
	pop	bx
	pop	cx
        ret

keystat	endp

hotcheck proc near	;DI points to TSR Block character is from
			;SI holds ID of TSR routine

	push	cx
	push	ax		;save character
	push	di

	mov	al,HOTKEY

	or	al,al
	jz	only_shift	;special case for shift-state only

	cmp	al,ah		;do scan codes match?
	jne	again_16H

only_shift:
	push	ax
	mov	ax,STATUS	;get TSR status flags
	test	ax,SHIFTSON	;are shift codes set?
	jz	again_16H1	;nope -- try next one
	
	or	ax,HOTKEYON	;turn on hotkey flag
	mov	STATUS,ax	;put new flags back

	pop	ax
	or	al,al		;test for special case
	pop	di		;get back Block pointer
	pop	ax		;throwing this away

	jz	hot_out		;special case for flags only

b_func1:
	mov	ah,0		;get rid of the character
	or	ah,new_bios_flag
	pushf			;simulate interrupt
	call	dword ptr cs:oldint16	;do bios

hot_out:
	xor	ax,ax		;set zero flag
	jmp	got_hot

again_16H1:
	pop	ax		;get rid of flag
again_16H:
	mov	al,0ffh
	or	al,al		;clear zero flag if set
	pop	di
	pop	ax		;get back keystroke
got_hot:
	pop	cx		;get back cx register
	ret

hotcheck endp

newint8 proc far

	pushf
	push	ds
	push	di
	push	cx			;save regs
	push	ax
	call	dds

	mov	ax,STATUS
	test	ax,HOTKEYON	;is hot-key flag set?
	jz	again_8_2
	test	ax,TSRACTIVE	;are we already running?
	jnz	again_8_2	;yep -- next one

wait_8:
	cmp	waitcount,0	;if waiting, check time
	jnz	wait2_8		;decrement count

chkdos_8:
	cld
	push	ds			;check if dos critical error in effect
	push	si
        lds     si,cs:doscriterr	;zero says dos is interruptable
	lodsb				;$ff  says dos is in a critical state
	lds     si,cs:indosflag		;add in second status byte
	or	al,[si]
	or	al,cs:intflags		;any flags says we're busy
	pop	si
	pop	ds
	jnz	wait1_8			;we're busy -- set count & EXIT
	call	dopopup			;call the popup stuff
	jmp	end_8			;and out we go
wait1_8:
	mov	waitcount,10h		;set the count
wait2_8:
	dec	waitcount
	jz	chkdos_8

again_8_2:
end_8:
	pushf				;simulate interrupt ...
	call dword ptr oldint8		;do the original

	pop	ax		;get back registers
	pop	cx
	pop	di
	pop	ds
	popf

exit_8:
	iret

newint8 endp

newint28 proc far

	pushf				;simulate interrupt ...
	call dword ptr cs:oldint28	;do the original

	push	ds
	push	di
	push	cx			;save regs
	push	ax

	push	cs
	pop	ds

again_28:
	mov	ax,STATUS	;get TSR status flags
	test	ax,HOTKEYON	;is hot-key flag set?
	jz	again_28_2
	test	ax,TSRACTIVE	;are we already running?
	jnz	again_28_2	;yep -- next one

chkdos_28:
	cld
	push	ds			;check if dos critical error in effect
	push	si
        lds     si,cs:doscriterr	;zero says dos is interruptable
	lodsb				;$ff  says dos is in a critical state
	or	al,cs:intflags		;any flags says we're busy
					;NOTE:  Indos flag is always set here!
					; ... so don't have to check it!
	pop	si
	pop	ds
	jnz	end_28			;we're busy-EXIT & try again next time

	mov	waitcount,0		;clear waiting count and ...	
	call	dopopup			;... call the popup stuff
	jmp	end_28			;and out we go

again_28_2:
end_28:
	pop	ax		;get back registers
	pop	cx
	pop	di
	pop	ds

exit_28:
	iret

newint28 endp

di28	dw	0
ds28	dw	0

dopopup proc near

	cli			;no interrupts now!!!
	
	or	word ptr STATUS,TSRACTIVE	;set active bit

	mov	ISSEG,ss		;save user's stack segment
	mov	ISPTR,sp		;... and stack pointer
					;DS was saved in INT8 or INT28

	mov	ss,SSEG		;load TSR stack segment ...
	mov	sp,SPTR		;... and stack pointer

	push	bp			;save all registers except for
	push	bx			;...those saved by calling routine,
	push	dx			;...DS,DI,CX,AX are restored on exit
	push	si			;...from this procedure
	push	es

	mov	[di28],di		;save TSR pointer until after saving
	mov	[ds28],ds		;...Indos stack

	mov	di,sp			;destination is our stack
	dec	di
	dec	di			;back off current word
	mov	ax,ss			;move stack segment
	mov	es,ax			;...to extra segment

	mov	si,cs:dossptr		;source is DOS Indos Primary Stack
	mov	ds,cs:dossseg		;...and segment, of course
	dec	si			;point to last word on DOS stack
	dec	si

	mov	cx,40h			;stack size to save

	mov	ax,sp			;save current stack pointer ...
	sub	ax,cx			;...and make room to avoid
	sub	ax,cx			;...overwriting during REP
	mov	sp,ax			;...and put it back

	std				;set the direction flag ...
	rep	movsw			;and move the 40 word stack
	mov	sp,di
	cld				;direction flag back

	mov	di,cs:di28		;get TSR pointer back
	mov	ds,cs:ds28

	sti				;interrupts back on!!

	getdta	OLDDTA			;save current DTA

	call	getpsp			;get current PSP segment
	mov	OLDPSP,bx		;...returned in BX

	mov	bx,PSP			;set TSR's PSP in BX
	call	setpsp			;...and do it

	setdta	DTA

	xor	ax,ax
	call	setup_int		;replace with our handler

	push	ds			;save DS:DI for after popup
	push	di
	mov	ds,DSEG		;load user's data segment


	call	dword ptr cs:POPUP	;call the user routine!!


	pop	di			;restore ds:di for addressibility
	pop	ds

	mov	bx,OLDPSP		;replace interrupted PSP
	call	setpsp

	setdta	OLDDTA		;restore interrupted DTA

	xor	ax,ax
	call	restore_int		;restore interrupts as well

;terminate checking here!

	cli				;no interrupts while manipulating stack

	mov	cx,40h			;stack size here

	mov	di28,di			;save TSR pointer until after saving
	mov	ds28,ds			;...Indos stack

	mov	ax,ss			;source is current stack
	mov	ds,ax
	mov	si,sp			;point to last word on our stack
	inc	si
	inc	si

	mov	es,cs:dossseg		;destination is dos stack
	mov	di,cs:dossptr
	sub	di,cx			;point backward to last used word 
	sub	di,cx			;...on dos stack
	cld
	rep	movsw

	mov	sp,si			;skip over moved words

	mov	di,cs:[di28]		;get TSR pointer back
	mov	ds,cs:[ds28]

	pop	es
	pop	si
	pop	dx
	pop	bx
	pop	bp

;
; ***************************************************************************
;
;	CAUTION!!!! THE USER ROUTINE MUST RETURN SS:SP WITH THE SAME
;		VALUES AS ON ENTRY.  OTHER REGISTERS WILL BE RESTORED
;		BY THIS ROUTINE.
;
; ***************************************************************************
;
back_popup:

	mov	ss,ISSEG		;restore user's stack segment
	mov	sp,ISPTR		;... and stack pointer
					;DS will be restored in INT8 or INT28
	and	STATUS,NOT HOTKEYON+TSRACTIVE
					;clear hot-key and inuse bits

	sti				;enable interrupts again
dopopup_end:
	ret

dopopup endp

newint13 proc far

	pushf
	or 	cs:intflags,ININT13
	popf			;get back original flags

	pushf				;simulate interrupt ...
	call dword ptr cs:oldint13	;do the original

	pushf
	and	cs:intflags,NOT ININT13
	popf

	ret	2

newint13 endp

newint21 proc far
	pushf
	sti				;allow interrupts

	cmp	ah,63h			;max number
	jg	notme
	push	ax			;some INT21 functions must be left
	push	bx			;...alone.  They either never return,
	mov	bx,offset dos21funcs	;...grab parameters from the stack
	mov	al,ah			;...or can be interrupted.
	xlat	cs:dos21funcs		;test function against table
	or	al,al			;wait for functions marked zero
	pop	bx			;these ,ust be left alone
	pop	ax
	jz	notme			;jump to original INT21
	jmp	short set_21
notme:
	popf
	jmp	dword ptr cs:oldint21

set_21:
	or 	cs:intflags,ININT21	;say int21 is active
	popf				;get original flags back
	pushf				;save flags and all regs that

	call	dword ptr cs:oldint21	;do the interrupt
	sti

	pushf				;save return registers
	and	cs:intflags,NOT ININT21	;not active any more
	popf				;they're back

	ret	2			;return with flags set

newint21 endp

error24	dw	0

newint24 proc far
	pushf
	mov	cs:error24,di		;save the error
	mov	al,0
	iret
newint24 endp

pop_routine proc far	;just prints a msg and gets out
	lea	dx,hello
	mov	ah,9
	int	21h		; display copyright notice
	ret
pop_routine endp


TSR_stack 	db	512 dup(0)
TSR_stack_end:

res_part:	; this is the end of my resident code so I can tell DOS
		; how big I am
;
;
; ***************************************************************************
;
;	Initialization Code -- load routine and point new int14
;
second_time	db	0	;flag used to test for odd-byte check

begin:
	lea	dx,cpyrt
	mov	ah,9
	int	21h		; display copyright notice

	call	getpsp		;Current PSP returned in BX
	mov	PSP,bx
	getdta	DTA		;macro to get Disk Transfer Address

	mov	ax,ds
	mov	DSEG,ax		;save it
	mov	SSEG,ax		;save it here too!
	mov	word ptr POPUP+2,ax	;save it here three!
	mov	ax,offset TSR_stack_end
	sub	ax,2
	mov	SPTR,ax

	mov	SHIFTST,08h	;ALT-Key + ...
	mov	HOTKEY,2dh	;...'X' to pop up

	mov	word ptr POPUP,offset pop_routine

	mov	ax,3400h		;find the DOS Indos Byte
	int	21h
;IF SOMEBODY HAS AT&T DOS 2.11, would you please try something?
;
;Move the label 'crit2' to the second location (where it is currently)
;commented out, and see how it works.  There is still some dissension
;about where the critical byte actually is in that version of DOS!

crit2:
	mov	word ptr indosflag,bx	;offset returned in BX
	mov	word ptr indosflag+2,es	;segment returned in ES
	mov	dossseg,es		;this is also the DOS stack segment
	mov	word ptr doscriterr+2,es;as well as the segment for the critical flag
;crit2:
	mov	cx,2000h 		;search for instruction: CMP [crit flag],00
	mov	ax,3e80h 		;3e80 is opcode to search for
	mov	di,bx 			;start search here
crit3:
	repnz	scasw 			;search until a match is found
	jnz	no_crit_found 		;couldn't find critical byte
					;we're going to search for the
					;address of the critical flag
					;es:[di-2] now points to:
					;       CMP [crit flag],00
					;       JNZ ...
					;       MOV SP,indos stack address      
	mov	al,byte ptr es:[di+5] 	;MOV SP,xxxx
	cmp	al,0bch 		;opcode for MOV SP
	jne	crit3	 		;bad place.  Sorry
	mov	ax,word ptr es:[di] 	;here's the critical byte address
	mov	word ptr doscriterr,ax
	mov	ax,word ptr es:[di+6] 	;...and here's the stack pointer
	mov	word ptr dossptr,ax
	mov	ax,1                                                     
	jmp	no_error
no_crit_found:
	mov	al,second_time		;did we already try odd bytes?
	or	al,al
	jz	crit_again		;nope ...
	jmp	bad_dos_version		;we tried!
crit_again:
	mov	second_time,1		;set the flag
	inc	bx			;increment pointer (needed for AT&T
	jmp	crit2			;DOS 2.11 and some others

no_error:
	mov	ax,1
	call	setup_int

	lea	dx,res_part	; load offset of top of code
	add	dx,15		;round up
	shr	dx,1
	shr	dx,1
	shr	dx,1
	shr	dx,1		; divide by 16 to get paragraphs
	xor	al,al		; no errors
	mov	ah,31h		; load terminate & stay resident code
	int	21h

bad_dos_version:
	lea	dx,bad_dos_msg
	mov	ah,9
	int	21h		; display error message
	mov	ax,4cffh			;exit with -1 return code
	int	21h

code	ends

	end	start
