How 2 write TSR's by Viper / Serenity

In my previous efforts to help you, I've manage to confuse you more than help you. I tried to give you some clues on a smaller TSR.

What is a TSR ( Terminate and Stay Resident ) ?
A tsr is a program that goes memory resident. Control of this program is freed from the user and the system ( DOS/WINDOWS ) takes control.

What is a ISR ?
A ISR (Interrupt Service Routine) is a program that goes resident but as soon as the program is terminated it DOES NOT stay resident.

TSR vs ISR
So whats the deal here? A TSR always starts out as an ISR and only if a special set of interrups are called will it Terminate and go memory resident.

So what does the TSR do?
Most times you program will chain itself to an interrupt like 0x9 which is the keyboard interrupt. Everytime you press a key interrupt 0x9 get polled ( called). Our TSR will attach itself on 0x9 so that our program also gets called. We can find out where the Adress of the program is that gets called by default. We then change the 0x9 vector to point to our program instead and when our program is done we call the old routine that would have been called. This is refered to as chaining the interrupt.

So how do we write a TSR ?
Here is the procedure you should follow

    1.  find out what Interrupt we want to use.
        The logical ones to use most of the time is
        0x1C , this interrupt gets called on every system tick
        0x9    , this interrupt gets called on every key press
        0x8    , this is the realtime clock interrupt it timing can be
                 reprogrammed to make it fire at a desired rate.

For our TSR problem int 0x8 is just fine. Beware of int 0x1c if you don't give controll back critical system functions could be lost.

    2.  find the location of the program that gets called by default by the
        interrupt hander.

        mov AH, 35h    ; get interrupt vector info
        mov AL, 01Ch   ; AL = the interrupt number
        int 21h        ; do it.

        the results as as follow
        ES = Interrupt Handler Segment
        BX = Interrupt Handler Offset

        Now we know where the default program (handler) resides ES:BX

    3.  Change the interrupt vector table to point to our hander and not the
        default one.

         mov AH, 25h         ; set the interrupt vector
         mov AL, 01Ch        ; AL = the interrupt number
         mov DX, OFFSET      ; our new handler
         int  21h            ; do it

        REMEMBER at the end of your program to jump back to the old Interrupt
        hander. If you don't you may get strange results and a blank hard drive.

        In essence up to now our program is a ISR if we terminate it now with the
        TSR interrupt it will go memory resident and become a TSR.

    4.  Terminate and stay resident

        mov AL,31h     ; terminate and stay resident
        mov DX, size   ; the size of the program in paragraphs
        int 21h        ; do it

        DX must contain the number of paragraphs of memory  required
        by  the  program (one paragraph = 16 bytes).  AL contains an
        exit code.

        Be careful when using this function with .EXE programs.  The
        value  in  DX must be the total size to remain resident, not
        just the size  of  the  code  segment  which  is  to  remain
        resident.   A typical error is to forget about the 100H byte
        program header prefix and give a value in DX  which  is  10H
        too small.

        There is another function you can use to make your program resident.
        It used in COM files mostly and is much simpler!
        mov DX, size
        int 27h

    5.  All done!

Example of a ISR going TSR. TASM example.asm ; TLINK /t example.obj

    ----------------------------------8----------------------------------------
    CODE SEGMENT                  ; Code segment start here
    .286                          ; 286 instruction
    ASSUME CS:CODE,DS:CODE        ; The code+data segments point to CODE segement
    ORG 100H                      ; Com files always start at 100h
    @@MAIN:
            ; Get the Interrupt vector info
            mov AH,35h            ; Get interrupt vector info
            mov AL,09h            ; we want 9h's info
            int 21h

            mov WORD PTR [oldh+3],es  ; Save the adress of the old handler
            mov WORD PTR [oldh+1],bx  ; which resides in ES:BX

            ; Set our Handler active
            mov AH,25h            ; Set the interrupt vector
            mov AL,9h             ; we want to set 9h
            mov DX,OFFSET @@ISR   ; DX is where our ISR is
            int  21h

            ; Go TSR
            mov dx,offset oldh    ; dx is the offset of the last byte of code + 1
            add dx, 5
            int 27h               ; do it
    @@ISR:                        ; The ISR
         pusha                    ; Save all the registers
         cli                      ; clear maskable interrups so that the
                                  ; interrupt does not get called when we're
                                  ; in it;
         sti                      ; enable the interrups again
         popa                     ; Restore them

    oldh DB 0EAh                  ; Chain our old hander
                                  ; WHAT DOES THIS LAST LINE DO ???
                                  ; We place a BYTE into the code segment EAh
                                  ; EAh = jmp , What we then do is to place the
                                  ; adress it should jump to behind it.
                                  ; This translates into JMP ES:BX
    CODE ENDS
    END @@MAIN