/* 
TSREXAMP.C
by Raymond J. Michels
with revisions by Tim Paterson
and Andrew Schulman
*/

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <dos.h>
#include <bios.h>
#include "tsr.h"

#define STACK_SIZE      8192  /* must be 16 byte boundaray */
#define SET_DTA         0x1a  /* SET Disk Transfer Address */
#define GET_DTA         0x2f  /* GET Disk Transfer Address */   

#define DOS_EXIT        0x4C  /* DOS terminate (exit) */

#define KEYBOARD_PORT   0x60  /* KEYBOARD Data Port */

#define PSP_TERMINATE   0x0A  /* Termination addr. in our PSP */
#define PSP_PARENT_PSP  0x16  /* Parent's PSP from our PSP */
#define PSP_ENV_ADDR    0x2c  /* environment address from PSP */

#define HOT_KEY         32    /* Hot Key along with ALT (D)*/

#define RIGHT_SHIFT     1
#define LEFT_SHIFT      2
#define CTRL_KEY        4
#define ALT_KEY         8

#define MULTIPLEX_ID    0xC0
#define INSTALL_CHECK   0x00
#define INSTALLED       0xFF
#define DEINSTALL       0x01

#define PARAGRAPHS(x)   ((FP_OFF(x) + 15) >> 4)

unsigned char multiplex_id = MULTIPLEX_ID;    
char far *stack_ptr;           /* pointer to TSR stack */
unsigned ss_save;              /* slot for stack segment register */
unsigned sp_save;              /* slot for stack pointer register */
int tsr_already_active = 0;    /* true if TSR active */
int popup_while_dos_busy = 0;  /* true if hot key hit while dos busy */
int int_28_in_progress = 0;    /* true if INT 28 in progress */
int unsafe_flag = 0;           /* true if INT 13 in progress */
unsigned keycode;                
unsigned foreground_psp;       /* PSP of process we've interrupted */
unsigned foreground_dta_seg;   /* DTA of process we've interrupted */
unsigned foreground_dta_off;
char buf[20];                  /* work buffer */
unsigned long TerminateAddr;   /* used during de-install */
union REGS regs;               /* register work structures */
struct SREGS sregs;     
struct ExtErr ErrInfo;         /* save area for extended error info */
int hot_key;                   /* keycode for activation */
int shift_key;                 /* shift status bits (alt,  ctrl..) */
int user_key_set = 0;
    
/* Save areas for old interrupt pointers */
INTVECT old_int8, old_int9, old_int10, old_int13, old_int1b, old_int23;
INTVECT old_int24, old_int28, old_int2f;

#ifdef DOS_SWAP
extern int dos_critical;     /* used by DOSSWAP.C */
INTVECT old_int2a;
void interrupt far new_int2a(INTERRUPT_REGS);
#endif

/* PROTOTYPES FOR THIS MODULE */
void interrupt far new_int8(INTERRUPT_REGS);
void interrupt far new_int9(INTERRUPT_REGS);
extern void interrupt far new_int13(void);  /* in TSRUTIL.ASM */
void interrupt far new_int1b(INTERRUPT_REGS);
void interrupt far new_int23(INTERRUPT_REGS);
void interrupt far new_int24(INTERRUPT_REGS);
void interrupt far new_int28(INTERRUPT_REGS);
void interrupt far new_int2f(INTERRUPT_REGS);
void tsr_function(void);
void tsr_exit(void);
void usage(char *);
int UnlinkVect(int Vect, INTVECT NewInt, INTVECT OldInt);
void parse_cmd_line(int argc, char *argv[]);
void main(int argc,char *argv[]);

/*********
* TIMER INTERRUPT HANDLER
*********/
void interrupt far new_int8(INTERRUPT_REGS r)
{
#ifdef DOS_SWAP
    if (!tsr_already_active && popup_while_dos_busy &&
        !dos_critical && !unsafe_flag)
#else
    if (!tsr_already_active && popup_while_dos_busy &&
        !DosBusy() && !unsafe_flag) 
#endif
    {
        popup_while_dos_busy = 0;
        tsr_already_active = 1;
		(*old_int8)();	/* process timer tick */
        _enable(); /* turn interrupts back on */
        tsr_function();
        tsr_already_active = 0;
    }
	else
		(*old_int8)();	/* process timer tick */
} 

/**********
* KEYBOARD INTERRUPT HANDLER
**********/
void interrupt far new_int9(INTERRUPT_REGS r)
{
    if (! tsr_already_active)
    {
         if ((keycode = inp(KEYBOARD_PORT)) != hot_key)
            _chain_intr(old_int9);

         if ((_bios_keybrd(_KEYBRD_SHIFTSTATUS) & 
            shift_key) == shift_key)
         {      
#ifdef USES_DISK
            if (!unsafe_flag)
            {
#endif
                popup_while_dos_busy = 0;   
                tsr_already_active = 1;
                (*old_int9)();     /* send key to old int routine */
                tsr_function();
                tsr_already_active = 0; 

#ifdef USES_DISK
            }
            else
			{
                popup_while_dos_busy = 1;
				_chain_intr(old_int9);
			}
#endif
         }
         else
            _chain_intr(old_int9);
     }      
     else 
         _chain_intr(old_int9);     
} 

/*********
* CTRL-BREAK INTERRUPT HANDLER
*********/
void interrupt far new_int1b(INTERRUPT_REGS r)
{
    /* do nothing */
} 

/**********
* CTRL-C INTERRUPT HANDLER
**********/
void interrupt far new_int23(INTERRUPT_REGS r)
{
    /* do nothing */
} 

/**********
* CRTITICAL ERROR INTERRUPT HANDLER
**********/
void interrupt far new_int24(INTERRUPT_REGS r)
{
    if (_osmajor >= 3)
        r.ax = 3;   /* fail dos function */
    else
        r.ax = 0;        
} 

/**********
* DOS IDLE INTERRUPT HANDLER
**********/
void interrupt far new_int28(INTERRUPT_REGS r)
{
    int_28_in_progress++;

#ifdef DOS_SWAP
    if (popup_while_dos_busy && !dos_critical 
        && !tsr_already_active && !unsafe_flag)
#else
    if (popup_while_dos_busy && (!Int28DosBusy()) 
        && !tsr_already_active && !unsafe_flag) 
#endif
    {
         tsr_already_active = 1;
         tsr_function();
         tsr_already_active = 0;
    }

    int_28_in_progress--;
    _chain_intr(old_int28);  
} 

#ifdef DOS_SWAP
/*********
* DOS INTERNAL INTERRUPT HANDLER
*********/
void interrupt far new_int2a(INTERRUPT_REGS r)
{
   switch (r.ax & 0xff00)
   {
      case 0x8000:    /* start critical section */
         dos_critical++;
         break;
      case 0x8100:    /* end critical section */
      case 0x8200:    /* end critical section */
         if (dos_critical)    /* don't go negative */
            dos_critical--;
         break;
      default:
         break;
    }
   _chain_intr(old_int2a);
} 
#endif

/*********
* DOS MULTIPLEX INTERRUPT HANDLER
*********/
void interrupt far new_int2f(INTERRUPT_REGS r)
{
    unsigned ah = r.ax >> 8;
    unsigned al = r.ax & 0xFF;
    
    if (ah == multiplex_id)
    {
        if (al == INSTALL_CHECK)
            r.ax |= INSTALLED;
        else if (al == DEINSTALL)
        {
            // because of stack swap, pass arg in static variable.
             TerminateAddr = ((long)r.bx << 16) + r.dx;
             if (! tsr_already_active)  /* don't exit if we're active */
             {
                 _enable(); /* STI */
                 tsr_exit();
                 // If we got here, we weren't able to unlink
                 r.ax = 0xFFFF;  //let caller know we're still there
                 // MSC 6.0 /Ox optimizes the above instruction away
                 // get it back by using the value in ax
                 tsr_already_active = -r.ax;  
                 // set to 1 to prevent any more action
             }
         }
    }
    else
         _chain_intr(old_int2f);
} 

/**********
* TSR ACTIVE SECTION
**********/
void tsr_function()
{
    set_stack();

#ifdef DOS_SWAP
    if (SaveDosSwap() && !int_28_in_progress)
#else
    if (DosBusy() && !int_28_in_progress)
#endif
        popup_while_dos_busy = 1; /* set flag: next INT 8,28 activates us */
    else
    {
        popup_while_dos_busy = 0;

        /* save old interrupt-CTRL-BREAK, CTRL-C and CRIT ERROR */ 
        old_int1b = _dos_getvect(0x1b); 
        old_int23 = _dos_getvect(0x23); 
        old_int24 = _dos_getvect(0x24); 

        /* set our interrupts functions */
       _dos_setvect(0x1b, new_int1b);
       _dos_setvect(0x23, new_int23);
       _dos_setvect(0x24, new_int24);

        /* save current PSP and set to ours */
        /* not needed for DOSSWAP, but can be used by application */
        foreground_psp = GetPSP();

        SetPSP(_psp);   // _psp in STDLIB.H

#ifndef DOS_SWAP
        /* get foreground DTA */
        regs.h.ah = GET_DTA;
        intdosx(&regs, &regs, &sregs); 
        foreground_dta_seg = sregs.es;
        foreground_dta_off = regs.x.bx;
#endif

        /* set up our DTA */
        regs.h.ah = SET_DTA;
        regs.x.dx = 0x80;   /* use default in PSP area */
        sregs.ds = _psp;
        intdosx(&regs, &regs, &sregs); 

#ifndef DOS_SWAP
        /* Get Extended Error Information */
        GetExtErr(&ErrInfo);
#endif

        /* suck up key(s) in buffer */
        while (_bios_keybrd(_KEYBRD_READY))
           _bios_keybrd(_KEYBRD_READ);

        /* your code goes here */
        application();

#ifdef DOS_SWAP
        RestoreDosSwap();
#else
        /* put back extended error information */
        SetExtErr(&ErrInfo);

        /* put back original DTA */
        regs.h.ah = SET_DTA;
        regs.x.dx = foreground_dta_off; 
        sregs.ds  = foreground_dta_seg;
        intdosx(&regs, &regs, &sregs); 

        /* put back original PSP */
        SetPSP(foreground_psp);
#endif

        /* put back original INTS */
       _dos_setvect(0x1b, old_int1b);
       _dos_setvect(0x23, old_int23);
       _dos_setvect(0x24, old_int24);
    }

    restore_stack();
}

// only restores OldInt if someone hasn't grabbed away Vect
int UnlinkVect(int Vect, INTVECT NewInt, INTVECT OldInt)
{
    if (NewInt == _dos_getvect(Vect))
    {
        _dos_setvect(Vect, OldInt);
        return 0;
    }
    return 1;
}

void tsr_exit(void)
{
    set_stack();
    /* put interrupts back the way they were, if possible */

    if (!(UnlinkVect(8, new_int8, old_int8)      |
          UnlinkVect(9, new_int9, old_int9)      |  // Do not use ||, we
          UnlinkVect(0x28, new_int28, old_int28) |  // don't want early out
          UnlinkVect(0x13, new_int13, old_int13) |
#ifdef DOS_SWAP
          UnlinkVect(0x2a, new_int2a, old_int2a) |
#endif
          UnlinkVect(0x2f, new_int2f, old_int2f) ))
    {
        // Set parent PSP, stored in our own PSP, to the current PSP
        *(int far *)(((long)_psp << 16) + PSP_PARENT_PSP) = GetPSP();

        // Set terminate address in our PSP
        *(long far *)(((long)_psp << 16) + PSP_TERMINATE) = TerminateAddr;

        /* set psp to be ours */
        SetPSP(_psp);

        /* exit program */
        bdos(DOS_EXIT, 0, 0);
    }
    restore_stack();
}

void usage(char *progname)
{
    fputs("Usage: ", stdout);
    puts(progname);
    puts(" [-d to deinstall] [-k keycode shift-status] [-f multiplex id]");
    puts(" Valid multiplex id");
    puts("    00 through 15 specifies a unique INT 2F ID");
    puts(" Valid shift-status is any combination of:");
    puts("    1 = Right Shift");
    puts("    2 = Left Shift");
    puts("    4 = CTRL");
    puts("    8 = ALT");
    exit(1);
}

void do_deinstall(char *progname)
{
    fputs(progname, stdout);
    switch (deinstall())
    {
        case 1:
            puts(" was not installed");
            break;
        case 2:
            puts(" deinstalled");
            break;
        default:
            puts(" deactivated but not removed");
            break;
    }
    exit(0);
}

int set_shift_key(unsigned sh)
{
    /* figure out, report on shift statuses */
    /* make sure shift key < 0x10 and non-zero */
    if (((shift_key = sh) < 0x10) && shift_key)
    {
        printf("Activation: %s%s%s%sSCAN=%d\n",
        shift_key & RIGHT_SHIFT ? "RIGHT " : "",
        shift_key & LEFT_SHIFT ? "LEFT " : "",
        shift_key & CTRL_KEY ? "CTRL " : "",
        shift_key & ALT_KEY ? "ALT " : "",
          hot_key);
        return 1;
    }
    else /* error, bad param */
    {
      puts("Invalid Shift-Status");
      return 0;
    }
}
                    
void parse_cmd_line(int argc, char *argv[])
{
    int i;
    int tmp;

    for (i = 1; i < argc; i++)   /* for each cmdline arg */
        if ((argv[i][0] == '-') || (argv[i][0] == '/'))
            switch(toupper(argv[i][1]))
            {
                case 'D':
                    do_deinstall(argv[0]);
                    break;
                case 'K':    /* set pop-up key sequence */          
                    user_key_set = 1;
                    i++;     /* bump to next argument */
                    if ((hot_key = atoi(argv[i])) != 0)
                    {
                        i++;   /* bump to next argument */
                        if (! set_shift_key(atoi(argv[i])))
                            usage(argv[0]);
                    }
                    else
                        usage(argv[0]);
                    break;
                case 'F':   /* set multiplex ID */
                    i++;    /* bump to next argument */
                    if ((tmp = atoi(argv[i])) < 0x10)                    
                        multiplex_id += tmp; /* range of C0-CF */
                    else
                        usage(argv[0]);
                    break;
                default:    /* invalid argument */
                    usage(argv[0]);
            }   /* end switch */
        else
            usage(argv[0]);
}

void main(int argc,char *argv[])
{
    union REGS  regs;
    struct SREGS sregs;     
    unsigned far *fp;
    unsigned memtop;
    unsigned dummy;

    InitInDos();

    parse_cmd_line(argc,argv);

    /* check if TSR already installed! */
    regs.h.ah = multiplex_id;
    regs.h.al = INSTALL_CHECK;
    int86(0x2f, &regs, &regs);
    if (regs.h.al == INSTALLED)
    {
        puts("TSR already installed");
        fputs(argv[0], stdout); puts(" -D de-installs");
        exit(1);
    }

    if (! user_key_set)
    {
        puts("Press ALT-D to activate TSR ");
        printf("Multiplex ID = %0x \n",multiplex_id);
        hot_key = HOT_KEY;
        shift_key = ALT_KEY;
    }
   
#ifdef DOS_SWAP
    if (InitDosSwap() != 0)
    {
        puts("Error initializing DOS Swappable Data Area");
        exit(1);
    }
#endif   

    /* MALLOC a stack for our TSR section */ 
    stack_ptr = malloc(STACK_SIZE);
    stack_ptr += STACK_SIZE;

    /* get interrupt vector */
    old_int8  = _dos_getvect(8);     /* timer interrupt */
    old_int9  = _dos_getvect(9);     /* keyboard interrupt */
    old_int13 = _dos_getvect(0x13);  /* disk intr, in TSRUTIL.ASM */
    old_int28 = _dos_getvect(0x28);  /* dos idle */
    old_int2f = _dos_getvect(0x2f);  /* multiplex int */

#ifdef DOS_SWAP
    old_int2a = _dos_getvect(0x2a);  /* dos internal int */
#endif

    init_intr();  /* initilize int routines in TSRUTIL.ASM */

    /* set interrupts to our routines */
    _dos_setvect(8, new_int8);
    _dos_setvect(9, new_int9);
    _dos_setvect(0x13, new_int13);      /* in TSRUTIL.ASM */
    _dos_setvect(0x28, new_int28);
    _dos_setvect(0x2f, new_int2f);

#ifdef DOS_SWAP
    _dos_setvect(0x2a, new_int2a);
#endif

    /* release environment back to MS-DOS */
    FP_SEG(fp) = _psp;
    FP_OFF(fp) = PSP_ENV_ADDR;
    _dos_freemem(*fp);

    /* release unused heap to MS-DOS */
    /* All MALLOCS for TSR section must be done in TSR_INIT() */
    /* calculate top of memory, shrink block, and go TSR */
    segread(&sregs);    
    memtop = sregs.ds + PARAGRAPHS(stack_ptr) - _psp;
    
    _dos_setblock(memtop, _psp, &dummy);
    _dos_keep(0, memtop);
}
