home *** CD-ROM | disk | FTP | other *** search
/ World of Shareware - Software Farm 2 / wosw_2.zip / wosw_2 / PASCAL / TSRUTILS.ZIP / DISABLE.PAS < prev    next >
Pascal/Delphi Source File  |  1989-05-04  |  25KB  |  782 lines

  1. {**************************************************************************
  2. *   Activates or deactivates TSRs, while leaving them in memory.          *
  3. *   Copyright (c) 1987 Kim Kokkonen, TurboPower Software.                 *
  4. *   Released to the public domain for personal, non-commercial use only.  *
  5. ***************************************************************************
  6. *   version 2.3 5/4/87                                                    *
  7. *     first release. version number matches other TSR Utilities           *
  8. *   version 2.4 5/17/87                                                   *
  9. *     fix a bug during reactivate with more than one TSR deactivated      *
  10. *     turn off interrupts during disable and restore                      *
  11. *   version 2.5 6/2/87                                                    *
  12. *     make warning messages a little more useful                          *
  13. *   version 2.6 1/15/89                                                   *
  14. *     convert to Turbo Pascal 5.0                                         *
  15. *   version 2.7                                                           *
  16. *     skipped                                                             *
  17. *   version 2.8 3/10/89                                                   *
  18. *     add option just to check for a TSR                                  *
  19. *   version 2.9 3/18/89                                                   *
  20. *     fix bug in countvecs                                                *
  21. *     check for overlapping patches before disabling                      *
  22. *       (takes care of problem with SK+)                                  *
  23. *     add /O override option to disable TSR even if overlaps detected     *
  24. ***************************************************************************
  25. *   telephone: 408-438-8608, CompuServe: 72457,2131.                      *
  26. *   requires Turbo version 5 to compile.                                  *
  27. ***************************************************************************}
  28.  
  29. {$R-,S-}
  30.  
  31. program DisableTSR;
  32.   {-Deactivate and reactivate memory resident programs}
  33.   {-Leaving them in memory all the while}
  34.  
  35. uses
  36.   Dos;
  37.  
  38. const
  39.   Version = '2.9';
  40.   MaxBlocks = 128;            {Max number of DOS allocation blocks supported}
  41.  
  42.   WatchID = 'TSR WATCHER';    {Marking string for WATCH}
  43.  
  44.   {Offsets into resident copy of WATCH.COM for data storage}
  45.   WatchOffset = $81;
  46.   NextChange = $104;
  47.   ChangeVectors = $220;
  48.   OrigVectors = $620;
  49.   CurrVectors = $A20;
  50.   MaxChanges = 128;           {Maximum number of vector changes stored in WATCH}
  51.  
  52. type
  53.   {.F-}
  54.   Block =
  55.   record                      {Store info about each memory block}
  56.     mcb : Word;
  57.     psp : Word;
  58.   end;
  59.  
  60.   BlockType = 0..MaxBlocks;
  61.   BlockArray = array[BlockType] of Block;
  62.  
  63.   ChangeBlock =
  64.   record                      {Store info about each vector takeover}
  65.     VecNum : byte;
  66.     case ID : byte of
  67.       0, 1 : (VecOfs, VecSeg : Word);
  68.       2    : (SaveCode : array[1..6] of byte);
  69.       $FF  : (PspAdd : Word);
  70.   end;
  71.   {
  72.   ID is interpreted as follows:
  73.     00 = ChangeBlock holds the new pointer for vector vecnum
  74.     01 = ChangeBlock holds pointer for vecnum but the block is disabled
  75.     02 = ChangeBlock holds the code underneath the vector patch
  76.     FF = ChangeBlock holds the segment of a new PSP
  77.   }
  78.   ChangeArray = array[0..maxchanges] of changeblock;
  79.  
  80.   HexString = string[4];
  81.   Pathname = string[79];
  82.   {.F+}
  83.  
  84. var
  85.   Blocks : BlockArray;
  86.   WatchBlock, BlockNum : BlockType;
  87.   Regs : Registers;
  88.   Changes : ChangeArray;
  89.   ChangeMax, ActualMax, WatchSeg, PspHex, StartMCB : Word;
  90.   Action : (aDeactivate, aActivate, aCheckFor);
  91.   Override : Boolean;
  92.   TsrName : Pathname;
  93.  
  94.   procedure Abort(msg : String; ErrorLevel : Byte);
  95.     {-Halt in case of error}
  96.   begin
  97.     WriteLn(msg);
  98.     Halt(ErrorLevel);
  99.   end;
  100.  
  101.   function StUpcase(s : String) : String;
  102.     {-Return the uppercase string}
  103.   var
  104.     i : Byte;
  105.   begin
  106.     for i := 1 to Length(s) do
  107.       s[i] := UpCase(s[i]);
  108.     StUpcase := s;
  109.   end;
  110.  
  111.   function HexW(i : Word) : HexString;
  112.     {-Return HexW representation of Word}
  113.   const
  114.     hc : array[0..15] of Char = '0123456789ABCDEF';
  115.   var
  116.     l, h : Byte;
  117.   begin
  118.     l := Lo(i);
  119.     h := Hi(i);
  120.     HexW[0] := #4;
  121.     HexW[1] := hc[h shr 4];
  122.     HexW[2] := hc[h and $F];
  123.     HexW[3] := hc[l shr 4];
  124.     HexW[4] := hc[l and $F];
  125.   end;
  126.  
  127.   procedure FindTheBlocks;
  128.     {-Scan memory for the allocated memory blocks}
  129.   const
  130.     MidBlockID = $4D;         {Byte DOS uses to identify part of MCB chain}
  131.     EndBlockID = $5A;         {Byte DOS uses to identify last block of MCB chain}
  132.   var
  133.     mcbSeg : Word;         {Segment address of current MCB}
  134.     nextSeg : Word;        {Computed segment address for the next MCB}
  135.     gotFirst : Boolean;       {True after first MCB is found}
  136.     gotLast : Boolean;        {True after last MCB is found}
  137.     idbyte : Byte;            {Byte that DOS uses to identify an MCB}
  138.  
  139.     function GetStartMCB : Word;
  140.       {-Return the first MCB segment}
  141.     begin
  142.       Regs.ah := $52;
  143.       MsDos(Regs);
  144.       GetStartMCB := MemW[Regs.es:(Regs.bx-2)];
  145.     end;
  146.  
  147.     procedure StoreTheBlock(var mcbSeg, nextSeg : Word;
  148.                             var gotFirst, gotLast : Boolean);
  149.       {-Store information regarding the memory block}
  150.     var
  151.       nextID : Byte;
  152.       PspAdd : Word;       {Segment address of the current PSP}
  153.       mcbLen : Word;       {Size of the current memory block in paragraphs}
  154.  
  155.     begin
  156.  
  157.       PspAdd := MemW[mcbSeg:1]; {Address of program segment prefix for MCB}
  158.       mcbLen := MemW[mcbSeg:3]; {Size of the MCB in paragraphs}
  159.       nextSeg := Succ(mcbSeg+mcbLen); {Where the next MCB should be}
  160.       nextID := Mem[nextSeg:0];
  161.  
  162.       if gotLast or (nextID = EndBlockID) or (nextID = MidBlockID) then begin
  163.         inc(BlockNum);
  164.         gotFirst := True;
  165.         with Blocks[BlockNum] do begin
  166.           mcb := mcbSeg;
  167.           psp := PspAdd;
  168.         end;
  169.       end;
  170.  
  171.     end;
  172.  
  173.   begin
  174.  
  175.     {Initialize}
  176.     StartMCB := GetStartMCB;
  177.     mcbSeg := StartMCB;
  178.     gotFirst := False;
  179.     gotLast := False;
  180.     BlockNum := 0;
  181.  
  182.     {Scan all memory until the last block is found}
  183.     repeat
  184.       idbyte := Mem[mcbSeg:0];
  185.       if idbyte = MidBlockID then begin
  186.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  187.         if gotFirst then
  188.           mcbSeg := nextSeg
  189.         else
  190.           inc(mcbSeg);
  191.       end else if gotFirst and (idbyte = EndBlockID) then begin
  192.         gotLast := True;
  193.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  194.       end else
  195.         {Start block was invalid}
  196.         Abort('Corrupted allocation chain or program error....', 255);
  197.     until gotLast;
  198.  
  199.   end;
  200.  
  201.   function FindMark(markId : String;
  202.                     markoffset : Word;
  203.                     var b : BlockType) : Boolean;
  204.     {-Find the last memory block matching idstring at offset idoffset}
  205.   var
  206.     found : Boolean;
  207.  
  208.     function HasIDstring(segment : Word;
  209.                          idString : String;
  210.                          idOffset : Word) : Boolean;
  211.       {-Return true if idstring is found at segment:idoffset}
  212.     var
  213.       tString : String;
  214.       len : Byte;
  215.     begin
  216.       len := Length(idString);
  217.       tString[0] := Chr(len);
  218.       Move(Mem[segment:idOffset], tString[1], len);
  219.       HasIDstring := (tString = idString);
  220.     end;
  221.  
  222.   begin
  223.     {Scan from the last block down}
  224.     b := BlockNum;
  225.     found := False;
  226.     repeat
  227.       if Blocks[b].psp = PrefixSeg then
  228.         {Assure this program's command line is not matched}
  229.         b := Pred(b)
  230.       else if HasIDstring(Blocks[b].psp, markId, markoffset) then
  231.         {mark found}
  232.         found := True
  233.       else
  234.         {Not a mark}
  235.         b := Pred(b);
  236.     until (b < 1) or found;
  237.     FindMark := found;
  238.   end;
  239.  
  240.   function ExecutableBlock(PspHex : Word) : Boolean;
  241.     {-Return true if psphex corresponds to an executable code block}
  242.   var
  243.     b : BlockType;
  244.   begin
  245.     for b := BlockNum downto 1 do
  246.       {Search back to find executable rather than environment block}
  247.       if Blocks[b].psp = PspHex then begin
  248.         ExecutableBlock := True;
  249.         Exit;
  250.       end;
  251.     ExecutableBlock := False;
  252.   end;
  253.  
  254.   procedure InitChangeArray(WatchBlock : BlockType);
  255.     {-Initialize information regarding the WATCH data block}
  256.   var
  257.     watchindex : Word;
  258.     p : ^ChangeBlock;
  259.   begin
  260.     {Store the segment of the WATCH data area}
  261.     WatchSeg := Blocks[WatchBlock].psp;
  262.  
  263.     {Maximum offset in WATCH data area}
  264.     ActualMax := MemW[WatchSeg:NextChange];
  265.  
  266.     {Transfer changes from WATCH into a buffer array}
  267.     watchindex := 0;
  268.     ChangeMax := 0;
  269.     while watchindex < ActualMax do begin
  270.       p := Ptr(WatchSeg, ChangeVectors+watchindex);
  271.       Move(p^, Changes[ChangeMax], SizeOf(ChangeBlock));
  272.       watchindex := watchindex+SizeOf(ChangeBlock);
  273.       if watchindex < ActualMax then
  274.         inc(ChangeMax);
  275.     end;
  276.   end;
  277.  
  278.   procedure PutWatch(chg : ChangeBlock; var watchindex : Word);
  279.     {-Put a change block back into WATCH}
  280.   var
  281.     p : ^ChangeBlock;
  282.   begin
  283.     p := Ptr(WatchSeg, ChangeVectors+watchindex);
  284.     Move(chg, p^, SizeOf(ChangeBlock));
  285.     watchindex := watchindex+SizeOf(ChangeBlock);
  286.   end;
  287.  
  288.   procedure ActivateTSR(PspHex : Word);
  289.     {-Patch out the active interrupt vectors of a specified TSR}
  290.   var
  291.     nextchg, chg, watchindex : Word;
  292.     checking, didsomething : Boolean;
  293.   begin
  294.     didsomething := False;
  295.     watchindex := 0;
  296.     chg := 0;
  297.  
  298.     {Scan looking for the specified PSP}
  299.     while chg <= ChangeMax do begin
  300.       with Changes[chg] do
  301.         case ID of
  302.  
  303.           $FF :               {This record starts a new PSP}
  304.             begin
  305.               checking := (PspAdd = PspHex);
  306.               nextchg := Succ(chg);
  307.               if checking then
  308.                 {Turn off interrupts}
  309.                 inline($FA)
  310.               else
  311.                 {Turn on interrupts}
  312.                 inline($FB);
  313.             end;
  314.  
  315.           $01 :               {This record has an inactive vector redefinition}
  316.             if checking then begin
  317.               {We're in the proper PSP}
  318.               didsomething := True;
  319.               {Change the ID to indicate that vector is active}
  320.               ID := 0;
  321.               {Put the original vector code back in place}
  322.               nextchg := Succ(chg);
  323.               if (Changes[nextchg].ID <> 2) or (Changes[nextchg].VecNum <> VecNum) then
  324.                 Abort('Program error in Activate, patch record not found', 255);
  325.               {Restore the patched over code}
  326.               Move(Changes[nextchg].SaveCode, Mem[VecSeg:VecOfs], 6);
  327.               {Don't output the following patch record}
  328.               inc(nextchg);
  329.             end else
  330.               nextchg := Succ(chg);
  331.  
  332.         else
  333.           nextchg := Succ(chg);
  334.         end;
  335.  
  336.       {Put the change block back into WATCH}
  337.       PutWatch(Changes[chg], watchindex);
  338.       {Advance to the next change record}
  339.       chg := nextchg;
  340.     end;
  341.  
  342.     {Store the count back into WATCH}
  343.     MemW[WatchSeg:NextChange] := watchindex;
  344.  
  345.     if not(didsomething) then
  346.       Abort('No changes were needed to activate '+HexW(PspHex), 1);
  347.  
  348.   end;
  349.  
  350.   procedure DeactivateTSR(PspHex : Word);
  351.     {-Patch out the active interrupt vectors of a specified TSR}
  352.   var
  353.     newchange : ChangeBlock;
  354.     chg, watchindex, curpsp : Word;
  355.     putrec, checking, didsomething : Boolean;
  356.     name : pathname;
  357.  
  358.     procedure PutPatch(vecn : Byte; vecs, veco, curpsp : Word);
  359.       {-Patch vector entry point with JMP to previous controlling vector}
  360.     label
  361.       90;
  362.     var
  363.       vec : ^Word;
  364.       chg : Word;
  365.     begin
  366.       {Get the original vector from WATCH}
  367.       Move(Mem[WatchSeg:(OrigVectors+(vecn shl 2))], vec, 4);
  368.  
  369.       {Scan the Changes array to look for redefinition of this vector}
  370.       for chg := 0 to ChangeMax do begin
  371.         with Changes[chg] do
  372.           case ID of
  373.             0, 1 :            {This is or was a redefined vector}
  374.               if vecn = VecNum then
  375.                 {It's the vector we're interested in}
  376.                 {Store the latest value of the vector}
  377.                 Move(VecOfs, vec, 4);
  378.             $FF :             {This record starts a new PSP}
  379.               if PspAdd = curpsp then
  380.                 {Stop when we get to the PSP that is being disabled}
  381.                 goto 90;
  382.           end;
  383.       end;
  384. 90:
  385.       {Patch the vector entry point into a JMP FAR vec}
  386.       Mem[vecs:veco] := $EA;
  387.       Move(vec, Mem[vecs:Succ(veco)], 4);
  388.     end;
  389.  
  390.     function CountVecs(chg : Word) : Word;
  391.       {-Return count of vectors taken over by the PSP starting at changeblock chg}
  392.     var
  393.       count : Word;
  394.       ID : Byte;
  395.     begin
  396.       count := 0;
  397.       repeat
  398.         {Skip over the first one, which defines the current PSP}
  399.         inc(chg);
  400.         ID := Changes[chg].ID;
  401.         if (ID = 0) and (chg <= ChangeMax) then {!!}
  402.           inc(count);
  403.       until (ID = $FF) or (chg >= ChangeMax); {!!}
  404.       CountVecs := count;
  405.     end;
  406.  
  407.     function ValidToPatch(chg : Word) : Boolean;
  408.       {-Assure that there is space to place 6-byte patches}
  409.     var
  410.       First : Word;
  411.       Next : Word;
  412.       I : Word;
  413.       J : Word;
  414.       IAddr : LongInt;
  415.       JAddr : LongInt;
  416.     begin
  417.       ValidToPatch := True;
  418.       if Override then
  419.         Exit;
  420.  
  421.       {First vector to patch}
  422.       First := chg+1;
  423.  
  424.       {Last vector to patch}
  425.       Next := First;
  426.       while (Next <= ChangeMax) and (Changes[Next].ID <> $FF) do
  427.         inc(Next);
  428.  
  429.       {Any to patch?}
  430.       if Next = First then
  431.         Exit;
  432.  
  433.       {Compare each pair to assure enough space for patch}
  434.       for I := First to Next-1 do begin
  435.         with Changes[I] do
  436.           IAddr := (LongInt(VecSeg) shl 4)+VecOfs;
  437.         for J := First to Next-1 do
  438.           if I <> J then begin
  439.             with Changes[J] do
  440.               JAddr := (LongInt(VecSeg) shl 4)+VecOfs;
  441.             if Abs(IAddr-JAddr) < 6 then begin
  442.               ValidToPatch := False;
  443.               Exit;
  444.             end;
  445.           end;
  446.       end;
  447.     end;
  448.  
  449.   begin
  450.  
  451.     {Scan looking for the specified PSP}
  452.     didsomething := False;
  453.     watchindex := 0;
  454.  
  455.     for chg := 0 to ChangeMax do begin
  456.       putrec := True;
  457.       with Changes[chg] do
  458.         case ID of
  459.  
  460.           $FF :               {This record starts a new PSP}
  461.             begin
  462.               checking := (PspAdd = PspHex);
  463.               if checking then begin
  464.                 {Store the current PSP}
  465.                 curpsp := PspAdd;
  466.                 {Make sure WATCH has room for the extra changes}
  467.                 if watchindex+(CountVecs(chg)*SizeOf(ChangeBlock)) >
  468.                 MaxChanges*SizeOf(ChangeBlock) then
  469.                   Abort('Insufficient space in WATCH data area', 255);
  470.                 {Make sure the patches will be valid}
  471.                 if not ValidToPatch(chg) then
  472.                   Abort('Insufficient space between vectors to patch TSR', 255);
  473.                 {Turn off interrupts}
  474.                 inline($FA);
  475.               end else
  476.                 {Turn on interrupts}
  477.                 inline($FB);
  478.             end;
  479.  
  480.           $00 :               {This record has an active vector redefinition}
  481.             if checking then begin
  482.               {We're in the proper PSP}
  483.               didsomething := True;
  484.  
  485.               {Change the ID to indicate that vector is inactive}
  486.               ID := 1;
  487.               {Output the record now so that the new record can immediately follow}
  488.               PutWatch(Changes[chg], watchindex);
  489.               putrec := False;
  490.  
  491.               {Output a new change record so we can reactivate later}
  492.               {Indicate this is a patch record}
  493.               newchange.ID := 2;
  494.               {Save which vector it goes with}
  495.               newchange.VecNum := VecNum;
  496.               {Save the code we'll patch over}
  497.               Move(Mem[VecSeg:VecOfs], newchange.SaveCode, 6);
  498.               {Output the record to the WATCH area}
  499.               PutWatch(newchange, watchindex);
  500.               {Patch in a JMP to the previous vector}
  501.               PutPatch(VecNum, VecSeg, VecOfs, curpsp);
  502.             end;
  503.  
  504.         end;
  505.       if putrec then
  506.         {Put the change block back into WATCH}
  507.         PutWatch(Changes[chg], watchindex);
  508.     end;
  509.  
  510.     {Store the count back into WATCH}
  511.     MemW[WatchSeg:NextChange] := watchindex;
  512.  
  513.     if not(didsomething) then
  514.       Abort('No changes were needed to deactivate '+tsrname, 1);
  515.  
  516.   end;
  517.  
  518.   procedure GetOptions;
  519.     {-Analyze command line for options}
  520.   var
  521.     arg : String;
  522.     arglen : Byte absolute arg;
  523.     i, code : Word;
  524.  
  525.     procedure WriteHelp;
  526.       {-Show the options}
  527.     begin
  528.       WriteLn;
  529.       WriteLn('DISABLE allows you to selectively disable and reenable a TSR while leaving it');
  530.       WriteLn('in memory. To run DISABLE, you must have previously installed the TSR utility');
  531.       WriteLn('WATCH.');
  532.       WriteLn;
  533.       WriteLn('DISABLE is command-line driven. You specify a single TSR by its name (if you');
  534.       WriteLn('are running DOS 3.0 or later) or by its address as determined from a MAPMEM');
  535.       WriteLn('report. Addresses must be preceded by a dollar sign "$" and specified in hex.');
  536.       WriteLn;
  537.       WriteLn('DISABLE accepts the following command line syntax:');
  538.       WriteLn;
  539.       WriteLn('  DISABLE TSRname|$PSPaddress [Options]');
  540.       WriteLn;
  541.       WriteLn('Options may be preceded by either / or -. Valid options are as follows:');
  542.       WriteLn;
  543.       WriteLn('     /A     reActivate the specified TSR.');
  544.       WriteLn('     /C     Check whether TSR is installed.');
  545.       WriteLn('     /O     Disable the TSR even if dangerous (Override).');
  546.       WriteLn('     /?     Write this help screen.');
  547.       Halt(1);
  548.     end;
  549.  
  550.     function DOSversion : Byte;
  551.       {-return the major version number of DOS}
  552.     var
  553.       reg : Registers;
  554.     begin
  555.       reg.ah := $30;
  556.       MsDos(reg);
  557.       DOSversion := reg.al;
  558.     end;
  559.  
  560.     function Owner(envseg : Word) : Pathname;
  561.       {-return the name of the owner program of an MCB}
  562.     type
  563.       chararray = array[0..32767] of Char;
  564.     var
  565.       e : ^chararray;
  566.       i : Word;
  567.       t : Pathname;
  568.  
  569.       function LongPos(m : Pathname; var s : chararray) : Word;
  570.         {-return the position number of m in s, or 0 if not found}
  571.       var
  572.         mlen : Byte absolute m;
  573.         mc : Char;
  574.         ss : Pathname;
  575.         i, maxindex : Word;
  576.         found : Boolean;
  577.       begin
  578.         i := 0;
  579.         maxindex := SizeOf(s)-mlen;
  580.         ss[0] := m[0];
  581.         if mlen > 0 then begin
  582.           mc := m[1];
  583.           repeat
  584.             while (s[i] <> mc) and (i <= maxindex) do
  585.               inc(i);
  586.             if s[i] = mc then begin
  587.               Move(s[i], ss[1], Length(m));
  588.               found := (ss = m);
  589.               if not(found) then
  590.                 inc(i);
  591.             end;
  592.           until found or (i > maxindex);
  593.           if not(found) then
  594.             i := 0;
  595.         end;
  596.         LongPos := i;
  597.       end;
  598.  
  599.       procedure StripNonAscii(var t : Pathname);
  600.         {-return an empty string if t contains any non-printable characters}
  601.       var
  602.         ipos : Byte;
  603.         goodname : Boolean;
  604.       begin
  605.         goodname := True;
  606.         for ipos := 1 to Length(t) do
  607.           if (t[ipos] <> #0) and ((t[ipos] < ' ') or (t[ipos] > '}')) then
  608.             goodname := False;
  609.         if not(goodname) then
  610.           t := '';
  611.       end;
  612.  
  613.       procedure StripPathname(var pname : Pathname);
  614.         {-remove leading drive or path name from the input}
  615.       var
  616.         spos, cpos, rpos : Byte;
  617.       begin
  618.         spos := Pos('\', pname);
  619.         cpos := Pos(':', pname);
  620.         if spos+cpos = 0 then
  621.           Exit;
  622.         if spos <> 0 then begin
  623.           {find the last slash in the pathname}
  624.           rpos := Length(pname);
  625.           while (rpos > 0) and (pname[rpos] <> '\') do
  626.             rpos := Pred(rpos);
  627.         end else
  628.           rpos := cpos;
  629.         Delete(pname, 1, rpos);
  630.       end;
  631.  
  632.       procedure StripExtension(var pname : Pathname);
  633.         {-remove the file extension}
  634.       var
  635.         dotpos : Byte;
  636.       begin
  637.         dotpos := Pos('.', pname);
  638.         if dotpos <> 0 then
  639.           Delete(pname, dotpos, 64);
  640.       end;
  641.  
  642.     begin
  643.       {point to the environment string}
  644.       e := Ptr(envseg, 0);
  645.  
  646.       {find end of the standard environment}
  647.       i := LongPos(#0#0, e^);
  648.       if i = 0 then begin
  649.         {something's wrong, exit gracefully}
  650.         Owner := '';
  651.         Exit;
  652.       end;
  653.  
  654.       {end of environment found, get the program name that follows it}
  655.       t := '';
  656.       i := i+4;               {skip over #0#0#args}
  657.       repeat
  658.         t := t+e^[i];
  659.         inc(i);
  660.       until (Length(t) > 64) or (e^[i] = #0);
  661.  
  662.       StripNonAscii(t);
  663.       if t = '' then
  664.         Owner := 'N/A'
  665.       else begin
  666.         StripPathname(t);
  667.         StripExtension(t);
  668.         if t = '' then
  669.           t := 'N/A';
  670.         Owner := StUpcase(t);
  671.       end;
  672.  
  673.     end;
  674.  
  675.     function FindOwner(name : String) : Word;
  676.       {-Return segment of executable block with specified name}
  677.     var
  678.       b : BlockType;
  679.     begin
  680.       name := StUpcase(name);
  681.       {Scan the blocks in reverse order}
  682.       for b := BlockNum downto 1 do
  683.         with Blocks[b] do
  684.           if Succ(mcb) = psp then
  685.             {This block is an executable block}
  686.             if Owner(MemW[psp:$2C]) = name then begin
  687.               {Found it}
  688.               FindOwner := psp;
  689.               Exit;
  690.             end;
  691.       FindOwner := $FFFF;
  692.     end;
  693.  
  694.   begin
  695.     {Initialize defaults}
  696.     PspHex := 0;
  697.     Action := aDeactivate;
  698.     Override := False;
  699.  
  700.     i := 1;
  701.     while i <= ParamCount do begin
  702.       arg := ParamStr(i);
  703.       if (arg[1] = '?') then
  704.         WriteHelp
  705.       else if (arg[1] = '-') or (arg[1] = '/') then
  706.         case arglen of
  707.           1 : Abort('Missing command option following '+arg, 254);
  708.           2 : case UpCase(arg[2]) of
  709.                 '?' : WriteHelp;
  710.                 'A' : Action := aActivate;
  711.                 'C' : Action := aCheckFor;
  712.                 'E' : Action := aActivate;
  713.                 'O' : Override := True;
  714.               else
  715.                 Abort('Unknown command option: '+arg, 254);
  716.               end;
  717.         else
  718.           Abort('Unknown command option: '+arg, 254);
  719.         end
  720.       else begin
  721.         {TSR to change}
  722.         if arg[1] = '$' then begin
  723.           {Treat as hex address}
  724.           Val(arg, PspHex, code);
  725.           if code <> 0 then
  726.             Abort('Invalid hex address specification: '+arg, 254);
  727.         end else if DOSversion >= 3 then
  728.           {Treat as PSP owner name - scan to find proper PSP}
  729.           PspHex := FindOwner(arg)
  730.         else
  731.           Abort('Must have DOS 3.0+ to find TSRs by name', 254);
  732.         TsrName := StUpcase(arg);
  733.       end;
  734.       inc(i);
  735.     end;
  736.  
  737.     if PspHex = 0 then
  738.       Abort('No TSR name or address specified', 254)
  739.     else if PspHex = $FFFF then
  740.       Abort('No such TSR found', 2);
  741.   end;
  742.  
  743. begin
  744.   WriteLn('DISABLE ', Version, ', by TurboPower Software');
  745.  
  746.   {Get all allocated memory blocks in normal memory}
  747.   {Must do first to support TSRs by name in GetOptions}
  748.   FindTheBlocks;
  749.  
  750.   {Analyze command line for options}
  751.   GetOptions;
  752.  
  753.   {Find the watch block}
  754.   if not FindMark(WatchID, WatchOffset, WatchBlock) then
  755.     Abort('WATCH must be installed in order to use DISABLE', 255);
  756.  
  757.   {Assure PspHex corresponds to an executable block}
  758.   if not ExecutableBlock(PspHex) then
  759.     Abort('No such TSR found', 2);
  760.  
  761.   {Initialize information regarding the WATCH data block}
  762.   InitChangeArray(WatchBlock);
  763.  
  764.   {Activate or deactivate the TSR}
  765.   case Action of
  766.     aDeactivate:DeactivateTSR(PspHex);
  767.     aActivate:ActivateTSR(PspHex);
  768.   end;
  769.  
  770.   {Write success message}
  771.   case Action of
  772.     aDeactivate:Write('Deactivated');
  773.     aActivate:Write('Activated');
  774.     aCheckFor:Write('Found');
  775.   end;
  776.   Write(' ');
  777.   if TsrName[1] = '$' then
  778.     Write('TSR at ');
  779.   WriteLn(TsrName);
  780.  
  781. end.
  782.