                            TSRs Under DOS


                            Matthew Probert
                           Servile Software



A TSR is a program which remains dormant in memory while other 
programs are loaded and run, and becomes active upon receipt of a 
signal. This signal may be a clock tick, or more usefully a 
preset key press combination. Upon receipt of the signal the TSR "pops 
up" and runs, before once again going dormant until the next time it 
is signalled.

Programming TSRs under DOS is not easy. They are hard to predict and 
almost impossible to debug. Having said that, they are not impossible 
and if you are patient, quite feasible.

A number of problems arise. Firstly, DOS is non-reenterant. You cannot 
issue a DOS API command within a resident program while DOS is in the 
middle of another. Secondly, when DOS runs a program an area of memory 
is set aside for variables such as file handles for that program 
called the PSP. A TSR program must tell DOS to use it's PSP when it 
activates as DOS is probably running another program at the time. 
There is also the stack to be concerned about. Thirdly, the TSR must 
remember to save and restore the cursor position. 

The following code is a very usable TSR skeleton. Simply write your 
program as a stand-alone application, in the small memory model and 
debug it as usuall. When you are happy it is working, add it to the 
skeleton here placing the main() function from your program within the 
exec() function in the skeleton. In this way, rather than calling 
main() from DOS, the TSR will call exec() and carry out the same 
process.

The skeleton shown is quite stable. When the TSR goes resident it 
waits for a key press combination of [Alt] and [.], though you can 
change this in the code. When this combination is detected the TSR 
flags that activation is required. A function tied into the timer loop 
checks the activation flag for a request to activate, and when it 
detects such a request issues a request for the TSR to run. The TSR 
tests to see if DOS is busy, and if the disk is busy and if not starts 
running the code in exec() and sets the activate and running flags 
accordingly to signal that the TSR is running. This means in practice 
that sometimes, when DOS is busy for a long time, that a request to 
activate will not be fulfilled. But that's preferable to crashing the 
host computer!

The TSR can be unloaded from memory with the command:

        TSR /U

It can only be unloaded if no other TSRs have been loaded on top of it 
and taken over control of certain vital interrupt vectors.

/*
    A practical TSR program

    Compile in small memory model with stack checking OFF
*/

#include <stdio.h>
#include <dos.h>
#include <string.h>

/*
    Size of the program to remain resident
    experimentation is required to make this as small as possible
*/
unsigned sizeprogram = 20000/16;

/*
    Activation keys
*/
unsigned scancode = 52;        /* . */
unsigned keymask = 8;        /* ALT */

/*
    Signature string
*/
char signature[]= "TSR";

/*
    Other variables
*/
union REGS reg;
struct SREGS seg;
unsigned mcbseg;
unsigned dosseg;
unsigned enddos;
char far *intdta;
unsigned intsp;
unsigned intss;
char far *mydta;
unsigned myss;
unsigned stack;
unsigned ctrl_break;
unsigned mypsp;
unsigned intpsp;
unsigned pids[2];
int pidctr = 0;
int running = 0;            /* Active/Dormant flag */
int activate = 0;            /* Activate request flag */
int diskflag = 0;            /* Disk busy flag */
unsigned dosbusy;            /* Address of DOS busy/Idle flag */
int avec = 0;
int key;
int cflag;

void interrupt (*oldtimer)();
void interrupt (*old28)();
void interrupt (*oldkb)();
void interrupt (*olddisk)();
void interrupt (*oldcrit)();

/*
    Resident program variables
*/
int handle;
char fpath[40];
char buffer[4000];


void dores(void);
void pidaddr(void);
void interrupt newtimer();
void interrupt new28();
void interrupt newkb();
void interrupt newdisk();
void interrupt newcrit();
void save_cursor(int *x, int *y);
void exec(void);
unsigned resident(char *, void interrupt(*)());
void resinit(void);
void terminate(void);
void resident_psp(void);
void exec(void);
void READ_SCREEN(void);
void SAVE_BIN(void);

main(int argc, char *argv[])
{
    void interrupt ifunc();
    int ivec;

    if ((ivec = resident(signature,ifunc)) != 0)
    {
        /* TSR is resident */
        if (argc > 1)
        {
            if (stricmp(argv[1],"/u") == 0)
            {
                /*
                    To unload use TSR /U
                */

                /*
                    Now try to unload from memory
                */
                int86(ivec,&reg,&reg);
                return;
            }
        }
        printf("\nAlready resident");
    }
    else
    {
        /* Initial load of TSR program */
        printf("\nResident!\nPress [Alt][.] to activate\n");
        resinit();
    }
}

void interrupt ifunc(bp,di,si,ds,es,dx,cx,bx,ax)
{
    terminate();
}


void resinit()
{
    segread(&seg);
    myss = seg.ss;

    reg.h.ah = 0x34;
    intdos(&reg,&reg);
    dosseg = _ES;
    dosbusy = reg.x.bx;

    mydta = getdta();
    pidaddr();

    /* Store old vectors */
    oldtimer = getvect(0x1c);
    old28 = getvect(0x28);
    oldkb = getvect(9);
    olddisk = getvect(0x13);

    /* Set vectors to my functions */
    setvect(0x1c,newtimer);
    setvect(9,newkb);
    setvect(0x28,new28);
    setvect(0x13,newdisk);

    /* Set a stack */
    stack = (sizeprogram - (seg.ds - seg.cs)) * 16 - 300;

    /* Terminate and stay resident */
    reg.x.ax = 0x3100;
    reg.x.dx = sizeprogram;
    intdos(&reg,&reg);
}

void interrupt newdisk(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs)
{
    diskflag++;
    (*olddisk)();
    ax = _AX;
    newcrit();
    flgs = cflag;
    --diskflag;
}

void interrupt newcrit(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs)
{
    ax = 0;
    cflag = flgs;
}

void interrupt newkb()
{
    if (inportb(0x60) == scancode)
    {
        key = peekb(0,0x417);
        if (((key & keymask) ^ keymask) == 0)
        {
            key = inportb(0x61);
            outportb(0x61,key | 0x80);
            outportb(0x61,key);
            disable();
            outportb(0x20,0x20);
            enable();
            if (!running)
                activate = 1;
            return;
        }
    }
    (*oldkb)();
}

void interrupt newtimer()
{
    (*oldtimer)();
    if (activate && peekb(dosseg,dosbusy) == 0)
        if(diskflag == 0)
        {
            outportb(0x20,0x20);
            activate = 0;
            dores();
        }
}
void interrupt new28()
{
    (*old28)();
    if (activate && peekb(dosseg,dosbusy) != 0)
    {
        activate = 0;
        dores();
    }
}

void resident_psp()
{
    int n;

    intpsp = peek(dosseg,*pids);
    for(n = 0; n < pidctr; n++)
        poke(dosseg,pids[n],mypsp);
}

interrupted_psp()
{
    int n;

    for(n = 0; n < pidctr; n++)
        poke(dosseg,pids[n],intpsp);
}

void dores()
{
    /* Signal that the resident program is running */
    running = 1;

    /* Disable interrupts */
    disable();

    /* Save old stack pointer */
    intsp = _SP;
    intss = _SS;

    /* Set stack to my own internal */
    _SP = stack;
    _SS = myss;

    /* Allow interruptions! */
    enable();

    /* Set critical error vector */
    oldcrit = getvect(0x24);
    setvect(0x24,newcrit);

    reg.x.ax = 0x3300;
    intdos(&reg,&reg);
    ctrl_break = reg.h.dl;
    reg.x.ax = 0x3301;
    reg.h.dl = 0;
    intdos(&reg,&reg);
    intdta = getdta();
    setdta(mydta);
    resident_psp();
    popup();
    interrupted_psp();
    setdta(intdta);
    setvect(0x24,oldcrit);
    reg.x.ax = 0x3301;
    reg.h.dl = ctrl_break;
    intdos(&reg,&reg);
    disable();

    /* Restore old stack pointer */
    _SP = intsp;
    _SS = intss;
    enable();

    /* Signal that the resident program is dormant */
    running = 0;
}

unsigned resident(char *signature,void interrupt(*ifunc)())
{
    char *sg;
    unsigned df;
    int vec;

    segread(&seg);
    df = seg.ds-seg.cs;
    for(vec = 0x60; vec < 0x68; vec++)
    {
        if (getvect(vec) == NULL)
        {
            if (!avec)
                avec = vec;
            continue;
        }
        for(sg = signature; *sg; sg++)
            if (*sg != peekb(peek(0,2+vec*4)+df,(unsigned)sg))
                break;
        if (!*sg)
            return vec;
    }
    if (avec)
        setvect(avec,ifunc);
    return 0;
}

void pidaddr()
{
    unsigned adr = 0;

    reg.h.ah = 0x51;
    intdos(&reg,&reg);
    mypsp = reg.x.bx;
    reg.h.ah = 0x52;
    intdos(&reg,&reg);
    enddos = _ES;
    enddos = peek(enddos,reg.x.bx-2);

    while(pidctr < 2 && (unsigned)((dosseg << 4) + adr) < (enddos << 4))
    {
        if (peek(dosseg,adr) == mypsp)
        {
            /* Set active PSP to that of resident program */

            reg.h.ah = 0x50;
            reg.x.bx = mypsp + 1;
            intdos(&reg,&reg);

            if (peek(dosseg,adr) == mypsp + 1)
                pids[pidctr++] = adr;

            reg.h.ah = 0x50;
            reg.x.bx = mypsp;
            intdos(&reg,&reg);
        }
        adr++;
    }
}

resterm()
{
    /*
        Unload the resident program from memory
    */

    /* Restore old vectors */
    setvect(0x1c,oldtimer);
    setvect(9,oldkb);
    setvect(0x28,old28);
    setvect(0x13,olddisk);
    setvect(avec,(void interrupt (*)()) 0);

    /* Locate resident program's MCBs */
    reg.h.ah = 0x52;
    intdos(&reg,&reg);
    mcbseg = _ES;
    mcbseg = peek(mcbseg,reg.x.bx-2);
    segread(&seg);
    while(peekb(mcbseg,0) == 0x4d)
    {
        if(peek(mcbseg,1) == mypsp)
        {
            /* Free resident program's MCBs */
            reg.h.ah = 0x49;
            seg.es = mcbseg+1;
            intdosx(&reg,&reg,&seg);
        }
        mcbseg += peek(mcbseg,3) + 1;
    }
}

void terminate()
{
    /*
        Attempt to unload the resident program from memory
        this can only be done if no other TSR has hooked
        any of the vectors we have hooked
    */

    if (getvect(0x13) == (void interrupt (*)()) newdisk)
        if (getvect(9) == newkb)
            if(getvect(0x28) == new28)
                if(getvect(0x1c) == newtimer)
                {
                    resterm();
                    return;
                }
}

void cursor(int y, int x)
{
    /* Set text cursor position for current display page */

    reg.x.ax = 0x0f00;
    int86(0x10,&reg,&reg);

    reg.x.ax = 0x0200;
    reg.x.dx = ((y << 8) & 0xff00) + x;
    int86(0x10,&reg,&reg);
}

void save_cursor(int *y, int *x)
{
    /* Read cursor location for current display page */

    reg.x.ax = 0x0f00;
    int86(0x10,&reg,&reg);

    reg.x.ax = 0x0300;
    int86(0x10,&reg,&reg);
    *x = reg.h.dl;
    *y = reg.h.dh;
}


popup()
{
    int x,y;

    /*
        Save cursor
    */
    save_cursor(&x,&y);

    /*
        Call the TSR C program here
    */
    exec();

    /*
        Restore cursor
    */
    cursor(x,y);
}

void exec()
{
    /*
        The resident code goes here
        it can open files etc etc
    */
}

