home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #27 / NN_1992_27.iso / spool / comp / lang / pascal / 6633 < prev    next >
Encoding:
Internet Message Format  |  1992-11-16  |  18.5 KB

  1. Path: sparky!uunet!munnari.oz.au!uniwa!DIALix!babel!del
  2. From: del@babel.DIALix.oz.au (D Elson)
  3. Newsgroups: comp.lang.pascal
  4. Subject: I need help writing a tsr 
  5. Distribution: world
  6. Message-ID: <721920656snx@babel.DIALix.oz.au>
  7. Date: Mon, 16 Nov 92 13:30:56 GMT
  8. Organization: 
  9. Lines: 737
  10.  
  11.  
  12. OK, since so many people have asked for it, here it is.
  13.  
  14. I haven't zipped/uuencoded it or nuthin, so you'll have to save
  15. the mail message and cut the bits out with a text editor.
  16.  
  17. Here goes:
  18.  
  19. {$V-,I-,R-,F+} {Do not change these directives. }
  20.  
  21. Unit TSR;
  22. {--------------------------------------------------------------------------
  23.  
  24.   Description:
  25.  
  26.     Part 1 of TSR implementation using Turbo Pascal.  To use this       
  27.     unit, you must have a modified version of TSRtest.PAS, including    
  28.     your TSR code in the appropriate procedure.
  29.  
  30.   References :
  31.  
  32.   Note :
  33.   
  34.     Avoid defining  local variables  inside an Interrupt routine, because
  35.     they will be allocated inside the interrupted program's stack.
  36.  
  37.   Author     :   D Elson
  38.                  del@dialix.oz.au
  39.   Version    :   3.0
  40.   Date       :   11/10/92
  41.  
  42. ---------------------------------------------------------------------------}
  43.  
  44.  
  45. {--------------------------------------------------------------------------}
  46. Interface
  47. {--------------------------------------------------------------------------}
  48.  
  49. Uses
  50.   Dos,
  51.   Crt,
  52.   Machine;
  53.  
  54. Const
  55.   OldStackSS    : Word = 0;    { SS save area of interrupted program }
  56.   OldStackSP    : Word = 0;    { SP save area of interrupted program }
  57.   OurStackSeg   : Word = 0;    { SS save area of this TSR program }
  58.   OurStackSP    : Word = 0;    { SP save area of this TSR program }
  59.  
  60.   StackSW       : integer = -1;
  61.  
  62. { StackSW indicates whether the Interrupt was issued from inside this
  63.   TSR program:
  64.               
  65.   -1 : Outside this TSR - Switch the stack        
  66.    0 : Inside this TSR - Do not switch the stack  }
  67.  
  68.  
  69.   TSROFF        : Boolean = FALSE;   { TRUE : Pop up has been disabled }
  70.  
  71.  
  72. Type
  73.  
  74.   TTSRProc = procedure;
  75.  
  76. procedure PopSetUp(pr:TTSRProc;
  77.                    SC,KM : Byte);
  78. { This sets up the popup procedure.  Pass in the procedure (which must
  79.   take no parameters to be compatible with type TTSRProc), the Scan
  80.   Code (SC), and the Key Mask (KM) }
  81.  
  82. procedure BeginPop;
  83. { Always call this at the beginning of your popup procedure. }
  84.  
  85. procedure EndPop;
  86. { Always call this at the end of your popup procedure. }
  87.  
  88. procedure install_int;
  89. { Call this to install an interrupt handler for your popup procedure. }
  90.  
  91. function  TSRExit : Boolean;
  92. { This exits the TSR program if it is possible, returns false if it is
  93.   not possible.  It is not possible to exit the TSR program in the case
  94.   that another TSR program has been loaded after this one. }
  95.  
  96. function  DupCheck(Var S: String; UserIntProc: Pointer) : Byte;
  97. { Checks whether the TSR procedure has been loaded.  If it hasn't, then
  98.   DupCheck returns 0.  If it has, it returns the interrupt vector that is
  99.   being used to communicate with the TSR.  This will be in the range
  100.   $60 .. $67. }
  101.  
  102. procedure BeginInt;
  103. InLine($ff/$06/stacksw/        { inc stacksw }
  104.        $75/$10/                { jne +16 }
  105.        $8c/$16/OldStackss/     { mov OldStackss,ss }
  106.        $89/$26/OldStacksp/     { mov OldStacksp,sp }
  107.        $8e/$16/OurStackseg/    { mov ss,OurStackseg }
  108.        $8b/$26/OurStacksp);    { mov sp,OurStacksp }
  109.  
  110. procedure EndInt;
  111. Inline($ff/$0e/stacksw/        { dec stacksw }
  112.        $7d/$08/                { jge +8 }
  113.        $8e/$16/OldStackSS/     { mov ss,OldStackss }
  114.        $8b/$26/OldStacksp);    { mov sp,OldStacksp }
  115.  
  116. {--------------------------------------------------------------------------}
  117. Implementation
  118. {--------------------------------------------------------------------------}
  119.  
  120. Const
  121.   TimerInt      = $1C;
  122.   KbdInt        = $09;
  123.   IdleInt       = $28;
  124.   CritInt       = $24;
  125.  
  126.   PopFlag       : Boolean = FALSE;      { TRUE : ok To Pop up }
  127.  
  128.   Running       : Boolean = FALSE;      { TRUE : Pop up is already Running }
  129.  
  130.   EndDos        : Word = 0;
  131.  
  132.   Scancode      : Byte = 0;
  133.   KeyMask       : Byte = 0;
  134. { These two constants define your hot key -- you can change them
  135.   at your convenience.  Note that the ScanCode is not the ASCII
  136.   value for the character but the code generated by the key-
  137.   board.                                                         }
  138.  
  139. Var
  140.   TimerVec,
  141.   KbdVec,
  142.   IdleVec,
  143.   CritVec: Pointer;                 { Pointers to save original interrupts }
  144.  
  145.   DosSeg : Word;
  146.  
  147.   DOSBusy : Word;                   { Save DOS busy flag address here }
  148.  
  149.   OldDTASeg,
  150.   OldDTAOfs,                        { Save interrupted program's DTA }
  151.  
  152.   OurDtaSeg,
  153.   OurDtaOfs : Word;                 { Address of the TSR's DTA }
  154.  
  155.   SaveBreak : Byte;
  156.  
  157.   PopRtn : TTSRProc;                { Address of the user's pop-up routine }
  158.  
  159.   TSR_Byte,
  160.   TSR_VEC       : Byte;        { TSR's communication Interrupt }
  161.   TSR_PSP       : Word;        { TSR's PSP }
  162.   INT_PSP       : Word;
  163.  
  164.   PSPArray      : array [1..2] of word;
  165.   PSP_counter   : byte;
  166.  
  167. {--------------------------------------------------------------------------}
  168.  
  169. { This replaces the existing critical error interrupt handler }
  170.  
  171. procedure NewCrit(Flags,CS,IP,AX,BX,CX,DX,SI,DI,DS,ES,Bp: Word);
  172.   Interrupt;
  173. { Set ax = 0 to tell DOS to ignore the error and continue }
  174. begin
  175.   ax:=0;
  176. end;
  177.  
  178. { This chains into the DOS idle interrupt }
  179.  
  180. procedure NewIdle (Flags,CS,IP,AX,BX,CX,DX,SI,DI,DS,ES,BP: Word);
  181.   Interrupt;
  182. { If DOS is idling, and if pop-up is pending, then do pop-up }
  183. begin
  184.   CLI;
  185.   BeginInt;
  186.   STI;
  187.   CallOldInt(IdleVec);     {Call old Interrupt 28 routine }
  188.   if ( (PopFlag) and (Mem[DosSeg:DOSBusy] <> 0) ) then
  189.     begin
  190.     PopFlag := False;
  191.     PopRtn;
  192.     End;
  193.   CLI;
  194.   EndInt;
  195.   STI;
  196. end;
  197.  
  198. { This chains into the current timer interrupt }
  199.  
  200. procedure NewClock (Flags,CS,IP,AX,BX,CX,DX,SI,DI,DS,ES,BP: Word);
  201.   Interrupt;
  202.  
  203. begin
  204.   CLI;
  205.   BeginInt;
  206.   STI;
  207.   CallOldInt (TimerVec);
  208.   If (PopFlag) and (Mem[DosSeg:DosBusy] = 0) Then
  209.     begin
  210.     i8259A_EOI;
  211.     PopFlag := False;
  212.     PopRtn;
  213.     end;
  214.   CLI;
  215.   EndInt;
  216.   STI;
  217. end;
  218.  
  219. { This replaces the Keyboard interrupt routine.  It checks to see whether
  220.   the hot key has been pressed, and sets PopFlag So that the NewClock and
  221.   NewIdle routines can detect it. }
  222.  
  223. procedure NewKbd(Flags,CS,IP,AX,BX,CX,DX,SI,DI,DS,ES,BP: Word);
  224.   Interrupt;
  225.  
  226. label
  227.   TSRDown,
  228.   KeyExit;
  229.  
  230. begin
  231.   CLI;
  232.   BeginInt;
  233.   STI;
  234. { If the TSR is suspended, do not check }
  235.   If TSROff then
  236.     Goto TSRDown;
  237.  
  238.   If (Port[$60] = ScanCode) then
  239.     if ((mem[0000:$0417] and keymask) = keymask) then
  240.       begin
  241. {     Reset the Keyboard }
  242.       TSR_byte  := Port[$61];
  243.       Port[$61] := TSR_Byte OR $80;
  244.       Port[$61] := TSR_Byte;
  245. {     Signal end of interrupt }
  246.       CLI;
  247.       i8259A_EOI;
  248.       STI;
  249.       If Not Running then
  250.         PopFlag := True;
  251.       Goto KeyExit;
  252.       end;
  253.  
  254. TSRdown:
  255.   CallOldInt(KbdVec);
  256.  
  257. KeyExit:
  258.   CLI;
  259.   EndInt;
  260.   STI;
  261. end;
  262.  
  263. {--------------------------------------------------------------------------}
  264.  
  265.  
  266. { Install the TSR'S Interrupts }
  267.  
  268. procedure Install_int;
  269. begin
  270.   SetIntVec(TimerInt, @NewClock);
  271.   SetIntVec(IdleInt,  @NewIdle);
  272.   SetIntVec(KbdInt,   @NewKbd);
  273.   If Stacksw = -1 then
  274.     SetIntVec($1B,SaveInt1B);
  275. end;
  276.  
  277. { Release any memory used by this TSR }
  278.  
  279. procedure rel_mem;
  280. var
  281.   r : registers;
  282. begin
  283.   While (Mem[EndDos:0] = $4d) do
  284.     begin
  285.     If MemW[EndDos:1] = TSR_PSP then
  286.       begin
  287.       R.AH := $49;
  288.       R.ES := EndDos+1;
  289.       MsDos(R);
  290.       end;
  291.     EndDos := EndDos + MemW[EndDos:3] + 1;
  292.     end;
  293. end;
  294.  
  295. { Restore interrupts when terminating TSR }
  296.  
  297. function TSRExit : boolean;
  298. var
  299.   p,q,r,s,t : pointer;
  300. begin
  301. { If the current interrupt vectors which the TSR steals are the same as
  302.   the TSR's original ones, then there are no more programs in memory after
  303.   this TSR, so it is safe to unload. }
  304.   GetIntVec(TimerInt,p);
  305.   GetIntVec(KbdInt,q);
  306.   GetIntVec(IdleInt,r);
  307.   If (p = @NewClock) and (q = @NewKbd) and (r = @NewIdle) then
  308.     begin
  309. {   Restore old vectors }
  310.     SetIntVec(TimerInt,TimerVec);
  311.     SetIntVec(KbdInt,KbdVec);
  312.     SetIntVec(IdleInt,IdleVec);
  313.     t := nil;
  314.     SetIntVec(TSR_Vec,t);
  315. {   Free all allocated memory }
  316.     rel_mem;
  317.     TSRExit := True;
  318.     end
  319.   else
  320.     begin
  321.     TSRExit := False;
  322.     TSROFF  := True;
  323.     end;
  324. end;
  325.  
  326. { Set up for pop-up routines -- Get the DOS busy flag, interrupts, etc. }
  327.  
  328. procedure PopSetup(pr:TTSRProc; SC,KM:byte);
  329. var
  330.   R   : Registers;
  331.   adr : word;
  332.   i   : integer;
  333. begin
  334.   ScanCode:=SC;
  335.   KeyMask :=KM;
  336.   Poprtn  :=pr;
  337.   CheckBreak:=False;
  338.  
  339. { Save Stack }
  340.   OurStackSeg:=SSeg;
  341.   Inline($89/$26/OurStackSP);
  342.  
  343. { Save address of DOS busy flag }
  344.   R.AH:=$34;
  345.   MsDos(R);
  346.   DosSeg:=R.ES;
  347.   DosBusy:=R.BX;
  348.  
  349. { Save address of Data Transfer Area }
  350.   R.AH:=$2F;
  351.   MsDos(R);
  352.   OurDtaSeg:=R.ES;
  353.   OurDtaOfs:=R.BX;
  354.  
  355. { Save address of Program segment prefix }
  356.   R.AH:=$51;
  357.   MsDos(R);
  358.   TSR_PSP:=R.BX;
  359.  
  360.   R.AH:=$52;
  361.   MsDos(R);
  362.   EndDos:=MemW[R.ES:R.BX-2];
  363.  
  364.   PSP_Counter :=0;
  365.   adr:=0;
  366.   While (PSP_Counter<2) and
  367.         (((DosSeg shl 4) + adr) < (EndDos shl 4)) do
  368.     begin
  369.     If MemW[DosSeg:adr] = TSR_PSP then
  370.       begin
  371.       R.AH:=$50;
  372.       R.BX:=TSR_PSP + 1;
  373.       MsDos(R);
  374.       if MemW[DosSeg:adr] = TSR_PSP+1 then
  375.         PSP_counter := PSP_counter + 1;
  376.       PSPArray[PSP_counter] := adr;
  377.       R.AH:=$50;
  378.       R.BX:=TSR_PSP;
  379.       MsDos(R);
  380.       end;
  381.     adr:=adr+1;
  382.     end;
  383.  
  384. { Get interrupt vectors }
  385.  
  386.   GetIntVec(TimerInt,TimerVec);
  387.   GetIntVec(KbdInt,KbdVec);
  388.   GetIntVec(IdleInt,IdleVec);
  389.   end;
  390.  
  391.  
  392. procedure ResidentPSP;
  393. var i:byte;
  394. begin
  395.   INT_PSP := MemW[DosSeg:PSParray[1]];
  396.   for i:=1 to PSP_counter do
  397.     MemW[DosSeg:PspArray[i]]:=tsr_psp;
  398. end;
  399.  
  400. procedure RestorePSP;
  401. var i:byte;
  402. begin
  403.   For i:=1 to PSP_counter do
  404.     MemW[DosSeg:PSPArray[i]]:=INT_PSP;
  405. end;
  406.  
  407. { BeginPop.  Call this before the pop up routine and it will replace the
  408.   DTA and Critical error handler }
  409.  
  410. procedure BeginPop;
  411. var
  412.   R : Registers;
  413. begin
  414.   Running := true;
  415.   R.AH := $2F;
  416.   MsDos(R);
  417.   OldDTAseg:=R.ES;
  418.   OldDTAofs:=R.BX;
  419.   R.AH:=$1A;
  420.   R.DS:=OurDtaSeg;
  421.   R.DX:=OurDTAOfs;
  422.   MsDos(R);
  423.  
  424. { Get control-break setting }
  425.   R.AX:=$3300;
  426.   MsDos(r);
  427.   SaveBreak:=R.DL;
  428.  
  429. { Set Control-break off }
  430.   R.AX:=$3301;
  431.   R.DL:=0;
  432.   MsDos(R);
  433.  
  434. { Get current critical error interrupt address }
  435.   GetIntVec(CritInt,CritVec);
  436.   SetIntVec(CritInt,@NewCrit);
  437.   ResidentPSP;
  438.   end;
  439.  
  440. { EndPOP -- does the reverse of the above }
  441.  
  442. procedure EndPop;
  443. var
  444.   R:Registers;
  445. begin
  446.   running:=false;
  447.  
  448.   R.AH:=$1A;                  { Restore DTA }
  449.   R.DS:=OldDTASeg;
  450.   R.DX:=OldDTAOfs;
  451.   MsDos(R);
  452.  
  453.   R.AX:=$3301;                { Restore ctrl-break setting }
  454.   R.DL:=SaveBreak;
  455.   MsDos(R);
  456.  
  457.   SetIntVec(CritInt,CritVec);
  458.   RestorePSP;
  459.   end;
  460.  
  461. { Check for duplicate copies of this TSR }
  462.  
  463. function DupCheck(var s:string; UserIntProc:pointer) : byte;
  464. var
  465.   vec:word;
  466.   dif:word;
  467.   ptr:pointer;
  468.   PtrArr : array[1..2] of integer absolute ptr;
  469.   RtnArr : array[1..2] of integer absolute UserIntProc;
  470.   i      : byte;
  471.   st     : string;
  472.  
  473. begin
  474. { Calculate the difference between the interrupt service routine and the
  475.   data segment in the transient program. }
  476.   dif := Dseg-rtnarr[2];
  477.   For vec:=$60 to $67 do
  478.     begin
  479.     GetIntVec(vec,ptr);
  480.     if ptr=nil then
  481.       begin
  482.       setintvec(vec,UserIntProc);
  483.       TSR_VEC:=vec;
  484.       DupCheck := 0;
  485.       exit;
  486.       end;
  487. { Find the signature, store it in a string, and compare with the
  488.   signature passed in.  }
  489.     Move(Mem[Ptrarr[2]+dif:ofs(s)],st,length(s)+1);
  490.     if st=s then
  491.       begin
  492.       DupCheck:=vec;   { return the communication vector of the TSR }
  493.       exit;
  494.       end;
  495.     end;
  496. end;
  497.  
  498. begin
  499.   stacksw := -1;
  500. End.
  501.  
  502. {$V-,I-,R-,F+} {Do not change these directives. }
  503. Unit Machine;
  504. {--------------------------------------------------------------------------
  505.  
  506.   Description:
  507.  
  508.     Library of machine dependant and machine coded instructions.
  509.  
  510.   References :
  511.  
  512.   Author     :   D Elson
  513.                  del@dialix.oz.au
  514.   Version    :   2.0
  515.   Date       :   11/11/92
  516.  
  517. ---------------------------------------------------------------------------}
  518.  
  519. {--------------------------------------------------------------------------}
  520. Interface
  521. {--------------------------------------------------------------------------}
  522.  
  523. Procedure CLI; InLine($FA);             { Disable interrupts }
  524.  
  525. Procedure STI; InLine($FB);             { Enable interrupts  }
  526.  
  527. Procedure i8259A_EOI;                   { 8259A Generic end of interrupt }
  528.   InLine($B0/$20/        { MOV AL,20 }
  529.          $E6/$20);       { OUT 20,AL }
  530.  
  531. Procedure POPF; Inline($9D);            { Pop Flags after Intr 25/26 }
  532.  
  533. Function  Flags : byte;
  534.   InLine($98/            { LAHF }
  535.          $88/$E0);       { MOV AL,AH }
  536.  
  537. Procedure CallOldInt (Sub:pointer);
  538. { Use this procedure to call an interrupt routine }
  539.  
  540. {--------------------------------------------------------------------------}
  541. Implementation
  542. {--------------------------------------------------------------------------}
  543.  
  544. Procedure CallOldInt (Sub:pointer);
  545. begin
  546. InLine ($9C/                { PUSHF    ; push status flags  }
  547.         $FF/$5E/$06);       { CALL DWORD PTR [BP+6] }
  548. end;
  549.  
  550. end.
  551.  
  552. {$M 6000,600,600 }      (* These have to be the actual memory requirements
  553.                            of this TSR.  The values are stack size, low heap
  554.                            size, and maximum heap size.                   *)
  555. {$V-,I-,R-,F+}          (* Do not change these directives *)
  556.  
  557. Program TSRTest;
  558. {--------------------------------------------------------------------------
  559.  
  560.   Description:
  561.  
  562.     Test driver for TSR.PAS.  This program can be modified, to change:
  563.  
  564.       1) The TSR signature (TSR_Tmark)                                  
  565.       2) The ScanCode and Mask                                          
  566.       3) Procedure Do_Pop (the TSR body itself.
  567.  
  568.   References :
  569.  
  570.     Unit TSR;
  571.  
  572.   Author     :   D Elson
  573.                  del@dialix.oz.au
  574.   Version    :   3.0
  575.   Date       :   11/11/92
  576.  
  577. ---------------------------------------------------------------------------}
  578.  
  579. uses  Crt,
  580.       Dos,
  581.       Machine,
  582.       TSR;
  583.  
  584. Const
  585.   TSR_tmark : string[20] = 'DELSON001';
  586.   ScanCode               = 78;      { See WATZITDO to find these }
  587.   Mask                   = 08;      { = <ALT>                    }
  588.  
  589. Var
  590.   TSR_int : byte;      { used for communication with the TSR }
  591.   TSR_AX  : word;      { " }
  592.  
  593. Procedure Do_Pop;
  594. { This is the pop-up "Main program".  Replace this routine with the pop
  595.   up routine you want to install. }
  596. begin
  597.   GotoXY(1,1);
  598.   write('Hello, world');
  599. end;
  600.  
  601. Procedure Pop_Call;
  602. begin
  603.   BeginPop;
  604.   Do_Pop;
  605.   EndPop;
  606. end;
  607.  
  608. Function Upper (S : string) : string;
  609. Var
  610.   I : Integer;
  611.   lcase : set of Char;
  612. begin
  613.   lcase := ['a'..'z'];
  614.  
  615.   for I := 1 to Length(S) do
  616.     if S[I] in lcase then
  617.       S[I] := Char(Ord(S[I]) - 32);
  618.   Upper := S;
  619. end;
  620.  
  621. Procedure Stop_TSR;
  622. begin
  623.   if TSRExit then
  624.     writeln('... Pop up routine removed from memory')
  625.   else
  626.     writeln('Cannot de-install Pop-up -- other TSRs have been loaded');
  627. end;
  628.  
  629. { This interrupt is installed at start up.  Its purpose is to provide a
  630.   way to communicate with the resident TSR through command line parameters.
  631.   It also serves to prevent any subsequent installation of the same pop
  632.   up program.  }
  633.  
  634. Procedure TSR_IntRtn(Flags,CS,IP,AX,BX,CX,DX,SI,DI,DS,ES,BP: Word);
  635.   Interrupt;
  636. begin
  637.   TSR_AX := AX;
  638.   CLI;
  639.   BeginInt;
  640.   STI;
  641.  
  642.   case TSR_AX of
  643.   1: begin
  644.      Stop_TSR;            { Terminate TSR and free memory }
  645.      end;
  646.   2: begin
  647.      TSROFF := true;      { Suspend execution of TSR }
  648.      writeln('Pop up routine suspended');
  649.      end;
  650.   3: begin
  651.      TSROFF := false;
  652.      writeln('Pop up routine restarted');
  653.      end;
  654.   end {case};
  655.  
  656.   CLI;
  657.   EndInt;
  658.   STI;
  659.   end;
  660.  
  661.  
  662. {--------------------------------------------------------------------------}
  663. (*        Main program -- installation procedure                          *)
  664. {--------------------------------------------------------------------------}
  665.  
  666. var
  667.   r     : Registers;
  668.   st    : String;
  669.  
  670. begin
  671.   { Installation routine.  Checks to see if TSR already installed, if it
  672.     is it returns the communication vector (INT 60 - 67) in TSR_int      }
  673.   TSR_Int  := DupCheck(TSR_tmark,@TSR_intrtn);
  674.  
  675.   if TSR_int > 0 then
  676.     begin
  677.     If paramcount > 0 then
  678.       begin
  679.       st := Upper(ParamStr(1));
  680.       if st = 'STOP'        then r.AX := 1
  681.       else if st = 'HOLD'   then r.AX := 2
  682.       else if st = 'GO'     then r.AX := 3
  683.       else begin
  684.            writeln('Invalid parameter -- parameters are:');
  685.            writeln('  STOP -- unload the TSR');
  686.            writeln('  HOLD -- suspend execution of the TSR');
  687.            writeln('  GO   -- restart after a HOLD');
  688.            exit;
  689.            end;
  690.       Intr(TSR_int,r);  { pass the parameter to the resident TSR }
  691.       exit;
  692.       end
  693.     else
  694.       begin
  695.       Writeln('TSR already installed');
  696.       exit;
  697.       end;
  698.     end;
  699.  
  700.   { Complete the installation of the TSR if sucessful so far }
  701.  
  702.   PopSetup(Pop_Call,ScanCode,Mask);    { Parameters are:  TSR dispatcher,
  703.                                                     keyboard scan code,
  704.                                                     keyboard mask.      }
  705.  
  706.   Writeln('TSR installed -- press <ALT> and ',
  707.           'the numeric keypad <+> ',             { Put the real key in here }
  708.           'to activate');
  709.   install_int;
  710.   Keep(0);
  711. end.
  712.  
  713. ------------------
  714. Have fun, y'all.
  715.  
  716. UnStandard Disclaimer:  I make no claims as to the suitability of these
  717. units, and if you stuff your system running them them don't bleed
  718. on me.  TSRs are dangerous things -- I suggest you get TSRTEST.EXE
  719. compiled and linked, then change it bit by bit so you can hunt
  720. down your mistakes easier.
  721.  
  722. I will have a good stress tested version of this unit ready for the
  723. people who are working with me on the PD pascal group, probably in
  724. about a month or two.  I don't consider this unit highly stable at
  725. the moment, and the documentation is lacking a little.
  726.  
  727. Sorry to all the people I didn't have time to reply to -- I had about
  728. 120 messages asking about these units, and I only got through about
  729. 15.  Please, have a good look at these units and attempt to solve any
  730. problems you have with them using a good book on DOS (I suggest something
  731. by Ray Duncan), before you all contact me.  It's not that I don't
  732. appreciate the interest, it's just that I'd like some room in my mailbox
  733. for other stuff.
  734.  
  735. Perhaps this shows that sometimes you should consider posting a request
  736. to a group, rather than using private e-mail, so you can see if someone
  737. has already made the request.  Sorry to rant and rave about it, but
  738. it took quite a bit of my time just sorting through mail messages.
  739.  
  740. And since this appears to be popular -- can someone with FTP access
  741. (I don't have it) please upload it to somewhere like garbo?  Timo?
  742.  
  743. Del
  744. --
  745. -----------------------------------+-------------------------------------
  746. D Elson                            |  del@DIALix.oz.au del@adied.oz.au
  747. -----------------------------------+-------------------------------------
  748.