Terminate and Stay Resident Programming
See below for an example of TSR programming. This one turns off the internal speaker.
See below for an example of TSR programming. This one sends the current DOS screen to the Windows Clipboard.
See below for an example of TSR programming to hook INT 21h.
Terminate and Stay Resident (TSR) programming is one of the most interesting
services provided by MS-DOS. (Norton's Programming Bible)
The following code runs a simple routine. It installs the TSR, invokes the TSR service (which removes the installation code form memory but not the actual TSR code). Then when installed, it hooks into the keyboard interrupt and looks to see if the
Ctrl-Del sequence was entered. If it wasn't, it sends control to the keyboard interrupt
and allows it and DOS to function normally. If the Ctrl-Del sequence has been entered,
then it prints an 'A' on the screen and jumps to the previous interrupt.
This DOS service restores the interrupt vectors for 22h, 23h, and 24h to the defaults,
so changing their vector interrupt address' will not do you any good.
When using the TSR service, you should release the memory of the environment block
that your installation program has.
Also, this DOS service does not work in DOS 1.0
Now for the hard part. DOS is not reentrant. This means that you can not call a DOS service while DOS is currently running a DOS service. If the user presses a key, the hardware sets and runs the code pointed to be the INT 09h vector instantly, whether in DOS or not. If DOS is running, and you call a DOS service, you will most likely crash the machine.
So, how do we write our code so that it doesn't crash the machine? DOS has left us a few things that we can do to make our TSR writing and running a lot easier. The first is the INDOS flag. It is exactly what it sounds like. Are we INDOS or not. DOS will set this flag to non-zero if it is currently running a DOS service. This flag is located in low memory (see code below), and we have to get the address of this flag and save it in the TSR code for later use.
Also, DOS has given us INT 28h, or the IDLE INTERRUPT. This interrupt is called when DOS has nothing to do. So if we hook this interrupt and call our interrupt handler code from this interrupt, we can pretty much assume that DOS is not busy, and we can do our code without crashing the machine.
But what if DOS is busy? How do we wait for DOS to be done and then run our interrupt handler? Well, this is where another interrupt handler comes into play. Interrupt 08h, the timer interrupt. We can place a variable in the timer interrupt to decrement each time this interrupt is called. Now we use interrupt 28h (described above), to see if we are waiting to call our interrupt code. In the interrupt 28h handler, we check to see if our wait variable is above zero. If so, we set the wait variable to zero (so that we don't try to call it again), and call our interrupt vector. If we actually decrement the variable to zero without calling our interrupt vector, this means that DOS is probably hung, and your interrupt vector will not be called. This usually does not happen, if we have a decent delay time. A value of 9 is usually quite enough time for DOS to get done.
So, how do we safely hook and call these interrupts? The safest thing to do is to call the old interrupt handler either before our code (most likely the better case), or after our code. What if we didn't call the previously loaded interrupt handler, and this handler set all the BIOS data for us? The key would not get into the BIOS buffer, the scan code would not be created, and other things like this. So we always call the previously loaded interrupt handler.
Another thing that we have to worry about. What if the user presses a key, which in turn calls our interrupt handler, while we are currently processing our interrupt handler? This could be bad. The simplest thing for us to do is to call the previously loaded interrupt handler, and forget that the keypress was ever made. However, if you needed this keypress badly, you could create a handler to save it on a stack, run the interrupt handler, and then see if a there are any keys on this stack. However, this is not likely with a regular DOS TSR. A system program, now that's a different story.
How about another thing to worry about. When we first load our TSR into memory, how do we know if we have previously loaded our TSR? We don't want it loaded, and do the process twice. This is actually quite simple. Place a known string of characters at a known offset in your interrupt handler. Then when you load the TSR, first check to see if this string of characters is at this location in the current interrupt handler. If it is, exit with an error.
Another item to talk about is the stack. You don't know where or how much stack there is when your interrupt handler gets called. You can pretty much assume that there will be about a paragraph of stack free for your use. However, a paragraph can only hold eight (8) 16-bit values. So we can create our own stack to practically whatever we want to use. However, before we create this stack, we must save in memory all registers used. You cannot modify ANY registers without saving and restoring them.
We also need to wait for the break code of the key press instead of the make code. If we caught the make code, we would call our interrupt handler repeatedly until the key was released. This is not what we want to do. The break code is actually the make code with bit 7 set.
Let us get to the code.
;
; assembled with NBASM
;
Delay equ 09 ; delay while INDOS != 0
.model tiny
.code
.186
org 100h
start: jmp Init ; at first load up, just to initialization code
ourint09: jmp DoIt ; our actual interrupt handler starts here.
OurID db 'FY' ; our id, to make sure we don't load twice
OurKey db 00h ; this byte saves the key press for our handler
oldint09O dw 00h ; this dword points to old interrupt vector
oldint09S dw 00h ;
SavedAX dw 00h ; temp word to store ax before our stack is setup
SavedDX dw 00h ; ditto for dx
SavedES dw 00h ; ditto for es
SavedSP dw 00h ; saved SP
SavedSS dw 00h ; saved SS
OurStack dup 126,0 ; Our Stack (64 word stack)
TopofStack dw 00h ; top of stack
; this is our interrupt code for hardware int 09h
DoIt: pushf ; save flags
mov cs:SavedAX,ax ; save ax (not on stack)
in al,60h ; get data from keyboard
cmp byte cs:tsraktiv,0 ; are we INDOS
jne short NotOurs ;
cmp byte cs:tsrnow,0 ; can we run the tsr now
jne short NotOurs ;
cmp al,(53h|80h) ; 53h (set bit 7 for break code)
je short IsOurs
NotOurs: mov ax,cs:SavedAX ; restore ax (not from stack)
popf ; restore flags
jmp far cs:oldint09O ; jump to old interrupt handler
IsOurs: mov cs:SavedDX,dx ; save dx (not on stack)
mov cs:SavedES,es ; save es (not on stack)
xor dx,dx
mov es,dx
test byte es:[0417h],00000100b ; was CTRL key pressed
mov es,cs:SavedES ; restore es (not from stack)
mov dx,cs:SavedDX ; restore dx (not from stack)
jz short NotOurs
mov cs:OurKey,al ; we need to save the key that got us here
call dosaktiv ; are we INDOS?
je short i9_tsr
W4BIOS: mov byte cs:tsrnow,Delay ; delay timer ticks to wait for INDOS
jmp short NotOurs
i9_tsr: mov byte cs:tsraktiv,1 ; set flag so that we don't reenter our code
mov byte cs:tsrnow,0 ; and clear the delay flag
pushf ; simulate an INT call to previous handler
call far cs:oldint09O ; (by pushing the flags and doing a FAR CALL)
call DoOurInt09 ; then do our code
popf ; restore flags
iret ;
; here is where we do our TSR code because HOTKEY was found
DoOurInt09 proc near
; we need to create our own stack so that we don't modify the parent stack
cli ; temp stop interrupts
mov cs:SavedAX,ax ; save ax
mov cs:SavedSS,ss ; save ss:sp
mov cs:SavedSP,sp ;
mov ax,cs ; create new stack
mov ss,ax ;
mov sp,offset cs:TopofStack ;
mov ax,cs:SavedAX ; restore ax
sti ; restore interrupts
pusha ; save registers used (on our stack)
push ds ;
push es ;
push cs ; make es = ds = cs
push cs ;
pop ds ;
pop es ;
mov al,OurKey ; get key entered
; this is where you do your code. I have found that if you are careful,
; and write good clean code, you can do just about anything you want
; within reason.
; exit code (done with TSR code, so exit back to parent program)
NoErrExit: pop es ; restore all registers we used
pop ds ;
popa ;
cli ; temp stop interrupts
mov sp,cs:SavedSP ; restore org stack
mov ss,cs:SavedSS ;
sti ; restore interrupts
mov byte cs:tsraktiv,0 ; clear flag so that we can do
; this again, next time
ret
DoOurInt09 endp
tsrnow db 00h ; the delay flag
tsraktiv db 00h ; the 'are we in here' flag
oldint08O dw 00h ; this dword points to old interrupt vector
oldint08S dw 00h
OurInt08 proc near
cmp byte cs:tsrnow,0 ; don't try to decrement it
je short i8_end ; if it is already zero!
dec byte cs:tsrnow ; else do the delay
call dosaktiv ; check to see if INDOS
je short i8_tsr ; if we are not, do our interrupt
i8_end: jmp far cs:oldint08O ; else just do the previous handler
i8_tsr: mov byte cs:tsrnow,0 ; clear delay flag
mov byte cs:tsraktiv,1 ; set 'in here' flag
pushf ; simulate an INT
call far cs:oldint08O ; using INT 08H emulation
call DoOurInt09 ; then do our handler
iret
OurInt08 endp
oldint28O dw 00h ; this dword points to old interrupt vector
oldint28S dw 00h
OurInt28 proc near
cmp byte cs:tsrnow,0 ; Is TSR waiting for activation?
jne short i28_tsr ; No --> Return to caller
i28_end: jmp far cs:oldint28O ; Return to old handler
i28_tsr: mov byte cs:tsrnow,00 ; TSR no longer waiting for activation
mov byte cs:tsraktiv,1 ; TSR is (already) active
pushf ; Call old interrupt handler
call far cs:oldint28O ; using INT 28H emulation
call DoOurInt09
iret ; Return to caller
OurInt28 endp
InDOSfoff dw 00h
InDOSfseg dw 00h
dosaktiv proc near uses ax bx es ; this is the 'get INDOS flag' code
mov bx,cs:InDOSfoff ; we loaded these two words
mov ax,cs:InDOSfseg ; with the address at startup
mov es,ax
cmp byte es:[bx],00
ret
dosaktiv endp
; this is the initialization procedure. Once we are done with this
; and load the TSR, this part is not needed and removed from memory.
;
Init: push cs ; make sure ds=cs
pop ds ;
mov ax,3508h ; get old interrupt vector (08h)
int 21h ;
mov [oldint08O],bx ; and save in static data area
mov [oldint08S],es ;
mov ax,3528h ; get old interrupt vector (08h)
int 21h ;
mov [oldint28O],bx ; and save in static data area
mov [oldint28S],es ;
mov ax,3509h ; get old interrupt vector (09h)
int 21h ;
mov [oldint09O],bx ; and save in static data area
mov [oldint09S],es ;
mov ax,es:[bx+03] ; Check to see if already loaded
cmp ax,'YF' ; is it 'FY'?
jne short OKtoLoad
mov dx,offset AlreadyS
mov ah,09
int 21h
.exit 01
OKtoLoad: push cs ; set es = cs
pop es
mov ah,34h ; get indos flag address
int 21h ; returns address in es:bx
mov InDOSfoff,bx ; save for our interrupt handler
mov InDOSfseg,es ;
; you can do other initialization code here.
; maybe check the command line for parameters, etc.
mov es,[002Ch] ; free environment block
mov ah,49h ;
int 21h ;
mov dx,offset OurInt08 ; set new interrupt vector (08h)
mov ax,2508h ;
int 21h ;
mov dx,offset ourint09 ; set new interrupt vector (09h)
mov ax,2509h ;
int 21h ;
mov dx,offset OurInt28 ; set new interrupt vector (28h)
mov ax,2528h ;
int 21h ;
mov dx,offset Init ; get paragraphs needed
sub dx,offset Start ;
shr dx,04 ;
add dx,17 ; add 16 paras for PSP + 1 extra
mov ax,3100h ; terminate and stay resident
int 21h ; (00h ERRORLEVEL)
AlreadyS db 13,10,'Already loaded',13,10,36
.end start
Again, be careful with this code if you do any modifications, or the such, you could crash your system and have to reboot.
If you have any other questions or see a mistake that I made, please e-mail me and I will
do my best to help out.
Another TSR demo that turns off the PC internal speaker (on older machines)
The following code creates a 292 byte .COM file, and when installed will take up only 464 bytes in memory.
When run for the first time, this code "hooks" into two interrupts. Interrupt 16h (the keyboard interrupt) and Interrupt 1Ch (the timer interrupt). The reason why we hook into the keyboard interrupt is that it is an easy way to check for our TSR in memory.
What we do is invoke INT 16h with a sub-service of 66h and a job number (0 or 1) in AL. (Service 66h is not included with the BIOS keyboard interrupt service, it is one we install the first time NOSOUND is run). If a 66h is returned in AL then we have already installed NOSOUND.
If AL does not = 66h, then we must install NOSOUND. We first "hook" into the timer interrupt (1Ch). We then "hook" in to the keyboard interrupt (16h). We then install the TSR.
Now once the TSR is installed, the hook to INT 1Ch will check the "job" flag that we have set and
either "stop" the speaker if it is zero. When the INT 16h, service 66h (our service) is called, it checks for installation; and if it is installed it returns 66h in AL.
Since we hooked into INT 16h, we can call it every time NOSOUND is run to check for installation. This way we won't install more than one TSR at a time.
This code is for learning purposes only. Please see the above code to make this a correctly written TSR. Typing 'NOSOUND off' turns sound off, 'NOSOUND on' turns sound on.
;
; assembled with NBASM
.model tiny
.code
org 100h
start: jmp install
Copyright db 13,10,'NOSOUND Quiets the hardware speaker.'
db 13,10,'Copyright 1984-2023 Forever Young Software',13,10,36
AllowY db 13,10,' Allowing Sound$'
AllowN db 13,10,' Not Allowing Any Sound$'
Old1CAdr dw 00h ; these remember the original Int 1Ch address
Old1CSeg dw 00h ; they must be in the code segment
Old16Adr dw 00h ; these remember the original Int 16h address
Old16Seg dw 00h ; they must be in the code segment
job db 00h ; 0 = sound off, else sound on
NewInt1Ch: pushf ; save flags
cmp byte cs:job,00h ; if job != 0 then skip ours
jnz short SoundOn ;
push dx ;
;If you notice any delay between hitting <Enter> after your "NOSOUD off"
;command, it's because of the 18th of a second delay (maximum) before
;the speaker is actually turned off.
mov dx,61h ; turn sound off
in al,dx ; .
and al,0FCh ; clear bits 1 & 0
out dx,al ; .
pop dx ;
SoundOn: popf ; restore flags
jmp far cs:Old1CAdr
NewInt16h: pushf ; save flags
cmp ah,66h ; if our service number
jne short SkipOurs
mov cs:job,al ; then put 'job' in job above
mov al,ah ; send installed flag
iret ;
SkipOurs: popf ; restore flags
jmp far cs:Old16Adr ;
Install: mov dx,offset Copyright ; print message
mov ah,09h ;
int 21h ;
xor al,al ; assume no sound
mov dx,offset AllowN ; print message
mov ah,[0083h] ; get command line 'n' or 'f'
cmp ah,'n' ; if 'n' then job != 0
jne short SoundOff1 ;
mov al,0FFh ;
mov dx,offset AllowY ; print message
SoundOff1: push ax ; save al
mov ah,09h ;
int 21h ;
pop ax ; restore al
mov ah,66h ; call interrupt 16h w/our service #
int 16h ; on return:
cmp al,66h ;
jne short NotInstld ; if al = 66h, then is installed
.exit ; and exit (no TSR it)
NotInstld: mov es,[002Ch] ; free environment block
mov ah,49h
int 21h
mov ax,351Ch ; ask DOS for existing Int 1Ch vector address
int 21h ; DOS returns the segment:address in ES:BX
mov Old1CAdr,bx ; save it locally
mov Old1CSeg,es
mov ax,251Ch ; point Interrupt 1Ch to our own handler
mov dx,offset NewInt1Ch
int 21h
mov ax,3516h ; ask DOS for existing Int 16h vector address
int 21h ; DOS returns the segment:address in ES:BX
mov Old16Adr,bx ; save it locally
mov Old16Seg,es
mov ax,2516h ; point Interrupt 16h to our own handler
mov dx,offset NewInt16h
int 21h
mov dx,(install-start+256+15) ; save all TSR code + PSP
mov cl,04h ; + 15 bytes to make sure we get all of TSR
shr dx,cl ; (paragraphs)
mov ax,3100h ; exit to DOS but stay resident
int 21h ;
.end start
Another TSR demo that sends the current DOS screen contents to the clipboard.
This TSR sends the characters stored in screen mode 3's memory (which are at physical address 0xB8000) to the Windows clipboard.
I created this because there were times that I wanted to send some info from a file I was viewing in DOS's EDIT to the Windows clipboard. (Sure I could have just pressed Alt-PrtScr but then I couldn't have created another TSR demo source for you.)
This TSR works fairly well on most machines. However, it does not like DOSKEY at all.
; This is a TSR that when activated and the CTRL-ALT-C
; key combination is pressed, it will send whatever text is on the
; screen (memory at 0B800h) to the Windows Clipboard.
;
; assembled with NBASM
Delay equ 09 ; delay while INDOS != 0
.model tiny
.186
.code
start: jmp install
comment |
We could put the Copyright Notice in the install part of the code
so that when we are a TSR, it would make for a smaller memory
allocation, but this way if someone is disassembling Int 09h,
they see our copyright notice. |
ourint09: jmp short NewInt9S ; skip data area
ID db 'FY' ; ID word to see if we are already here
Copyright db 13,10,'DOS Screen to Windows Clipboard v1.00'
db 13,10,'Copyright 1984-2024 Forever Young Software'
db 13,10,36
oldint09O dw 00h ; these remember the original Int 9 address
oldint09S dw 00h ; they must be in the code segment
SavedAX dw 00h
SavedDX dw 00h
SavedES dw 00h
OurKey db 00h
NewInt9S: pushf ; save flags
mov cs:SavedAX,ax ; save ax (not on stack)
in al,60h ; get data from keyboard
cmp byte cs:tsraktiv,0 ; are we INDOS
jne short NotOurs ;
cmp byte cs:tsrnow,0 ; can we run the tsr now
jne short NotOurs ;
cmp al,(2Eh|80h) ; 2Eh (set bit 7 for break code)
jne short IsOurs
NotOurs: mov ax,cs:SavedAX ; restore ax (not from stack)
popf ; restore flags
jmp far cs:oldint09O ; jump to old interrupt handler
IsOurs: mov cs:SavedDX,dx ; save dx (not on stack)
mov cs:SavedES,es ; save es (not on stack)
xor dx,dx
mov es,dx
test byte es:[0417h],00000100b ; was CTRL key pressed
mov es,cs:SavedES ; restore es (not from stack)
mov dx,cs:SavedDX ; restore dx (not from stack)
jz short NotOurs
mov cs:OurKey,al ; we need to save the key that got us here
call dosaktiv ; are we INDOS?
je short i9_tsr
W4BIOS: mov byte cs:tsrnow,Delay ; delay timer ticks to wait for INDOS
jmp short NotOurs
i9_tsr: mov byte cs:tsraktiv,1 ; set flag so we don't reenter our code
mov byte cs:tsrnow,0 ; and clear the delay flag
pushf ; simulate an INT call to previous handler
call far cs:oldint09O ; (by pushing flags & doing a FAR CALL)
call DoOurInt09 ; then do our code
popf ; restore flags
iret ;
; here is where we do our TSR code because HOTKEY was found
DoOurInt09 proc near
pusha ; save the registers we'll be using
push es
mov ax,1700h ; check clipboard for usage
int 2Fh ;
cmp ax,1700h ; if 1700h then error
je short NoErrExit
mov ax,1701h ; open clipboard
int 2Fh
push ds
push cs
pop es
mov cx,25
xor si,si
mov di,offset cs:Install
mov ax,0B800h
mov ds,ax
GetScrL1: push cx
mov cl,80 ; we know that ch = 0 from mov cx,25 above
GetScrL2: lodsw
stosb
loop GetScrL2
mov ax,0A0Dh ; put a CRLF at end of line
stosw
pop cx
loop GetScrL1
pop ds
mov ax,1703h ; put screen 03h into clipboard
push cs
pop es
mov bx,offset cs:Install
mov dx,01h
mov cx,2050 ; (25x80) + 25 CRLF's
xor si,si
int 2Fh
mov ax,1708h ; close clipboard
int 2Fh
; print smiley face in upper right corner
mov ax,0B800h ;
mov es,ax
mov bx,es:[009Eh]
mov word es:[009Eh],1701h ; make print smiley white on blue
; pause for a few milliseconds
mov cx,06
mov dx,03DAh
delay_l1: in al,dx
test al,08h
jne short delay_l1
delay_l2: in al,dx
test al,08h
je short delay_l2
loop delay_l1
; restore char that was originally there
mov es:[009Eh],bx
; exit code (done with TSR code, so exit back to parent program)
NoErrExit: pop es ; restore all registers we used
popa ;
mov byte cs:tsraktiv,0 ; clear flag so that we can
; do this again, next time
ret
DoOurInt09 endp
tsrnow db 00h ; the delay flag
tsraktiv db 00h ; the 'are we in here' flag
oldint08O dw 00h ; this dword points to old interrupt vector
oldint08S dw 00h
OurInt08 proc near
cmp byte cs:tsrnow,0 ; don't try to decrement it
je short i8_end ; if it is already zero!
dec byte cs:tsrnow ; else do the delay
call dosaktiv ; check to see if INDOS
je short i8_tsr ; if we are not, do our interrupt code
i8_end: jmp far cs:oldint08O ; else just do the previous handler
i8_tsr: mov byte cs:tsrnow,0 ; clear delay flag
mov byte cs:tsraktiv,1 ; set 'in here' flag
pushf ; simulate an INT
call far cs:oldint08O ; using INT 08H emulation
call DoOurInt09 ; then do our handler
iret
OurInt08 endp
oldint28O dw 00h ; this dword points to old interrupt vector
oldint28S dw 00h
OurInt28 proc near
cmp byte cs:tsrnow,0 ; Is TSR waiting for activation?
jne short i28_tsr ; No --> Return to caller
i28_end: jmp far cs:oldint28O ; Return to old handler
i28_tsr: mov byte cs:tsrnow,00 ; TSR no longer waiting for activation
mov byte cs:tsraktiv,1 ; TSR is (already) active
pushf ; Call old interrupt handler
call far cs:oldint28O ; using INT 28H emulation
call DoOurInt09
iret ; Return to caller
OurInt28 endp
InDOSfoff dw 00h
InDOSfseg dw 00h
dosaktiv proc near uses ax bx es ; this is the 'get INDOS flag' code
mov bx,cs:InDOSfoff ; we loaded these two words
mov ax,cs:InDOSfseg ; with the address at startup
mov es,ax
cmp byte es:[bx],00
ret
dosaktiv endp
; This is the install part. It gets wiped out by our buffer used above.
; After TSR'ed, we no longer need this code.
;
Install: mov dx,offset Copyright ; print our copyright string
mov ah,09
int 21h
mov ax,3508h ; get old interrupt vector (08h)
int 21h ;
mov [oldint08O],bx ; and save in static data area
mov [oldint08S],es ;
mov ax,3528h ; get old interrupt vector (08h)
int 21h ;
mov [oldint28O],bx ; and save in static data area
mov [oldint28S],es ;
mov ax,3509h ; get old interrupt vector (09h)
int 21h ;
mov [oldint09O],bx ; and save in static data area
mov [oldint09S],es ;
mov ax,es:[bx+02] ; Check to see if already loaded
cmp ax,'YF' ; is it 'FY'?
jne short OKtoLoad
mov dx,offset AlreadyS
mov ah,09
int 21h
.exit 01
OKtoLoad: push cs ; set es = cs
pop es
mov ah,34h ; get indos flag address
int 21h ; returns address in es:bx
mov InDOSfoff,bx ; save for our interrupt handler
mov InDOSfseg,es ;
mov es,[002Ch] ; free environment block
mov ah,49h ;
int 21h ;
mov dx,offset OurInt08 ; set new interrupt vector (08h)
mov ax,2508h ;
int 21h ;
mov dx,offset ourint09 ; set new interrupt vector (09h)
mov ax,2509h ;
int 21h ;
mov dx,offset OurInt28 ; set new interrupt vector (28h)
mov ax,2528h ;
int 21h ;
mov dx,offset install ; get paragraphs needed
sub dx,offset Start ;
shr dx,04 ;
add dx,17 ; add 16 paras for PSP + 1 extra
mov ax,3100h ; terminate and stay resident
int 21h ; (00h ERRORLEVEL)
AlreadyS db 13,10,'Already installed...',13,10,36
.end start
The following code hooks into INT 21h and allows you to do a little bit of code before and a little bit after each time the original INT 21h gets called. Remember, you can only do a little bit of stuff. Nothing major and absolutely nothing that calls INT 21h or you will be in an endless loop (unless you add code to make it recursive). Be careful, this is for example purposes only.
This TSR hooks into INT 21h. When the processor calls INT 21h, this code prints a '1' to row 6, column 1 of the screen, calls the original 21h interrupt, and then prints a '2' to the screen just after the 1.
; Assemble with NBASM
.model tiny
.186
.code
org 100h
start: jmp short Install
olddosint dw 00h,00h
newDOSInt:
cli ; put TODO stuff here
push es ; .
push 0B800h ; .
pop es ; .
mov byte es:[1920],'1' ; . row 6, col 1 : print '1'
pop es ; .
sti ; end TODO stuff here
pushf ; create our own descriptor
push cs ; (could be a call far instead)
push offset Back ;
jmp far cs:olddosint ; jmp to old interrupt handler
Back: pushf ; save flags returned by orig int 21h
cli ; put TODO stuff here
push es ; .
push 0B800h ; .
pop es ; .
mov byte es:[1922],'2' ; . row 6, col 2 : print '2'
pop es ; .
sti ; end TODO stuff here
cli ; clean up the stack
push ax ; so that it will ret to the
push bp ; correct place
mov bp,sp ;
mov ax,[bp-6] ;
mov [bp-8],ax ;
mov ax,[bp-4] ;
mov [bp-6],ax ;
pop bp ;
pop ax ;
popf ; restore flags ret'd by orig 21h
sti ;
retf 2 ;
Install: mov ax,3521h ; Save old interrupt vector 21h
int 21h
mov [olddosint],bx
mov [olddosint+2],es
push cs
pop ds
mov dx,offset cs:newdosint ; set new interrupt vector 21h
mov ax,2521h ; to newdosint
int 21h
mov es,cs:[002Ch] ; free environment block
mov ah,49h
int 21h
mov dx,offset cs:Install ; get paragraphs needed
sub dx,offset cs:Start
shr dx,04
add dx,17 ; add 16 paras for PSP + 1 extra
mov ax,3100h ; termination and stay resident
int 21h
.end start