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

  1. {**************************************************************************
  2. *   Releases memory above the last MARK call made.                        *
  3. *   Copyright (c) 1986 Kim Kokkonen, TurboPower Software.                 *
  4. *   Released to the public domain for personal, non-commercial use only.  *
  5. ***************************************************************************
  6. *   Version 1.0 2/8/86                                                    *
  7. *     original public release                                             *
  8. *     (thanks to Neil Rubenking for an outline of the method used)        *
  9. *   Version 1.1 2/11/86                                                   *
  10. *     fixed problem with processes which deallocate their environment     *
  11. *   Version 1.2 2/13/86                                                   *
  12. *     fixed another problem with processes which deallocate environment   *
  13. *   Version 1.3 2/15/86                                                   *
  14. *     add support for "named" marks                                       *
  15. *   Version 1.4 2/23/86                                                   *
  16. *     add support for releasing programs which use Expanded Memory        *
  17. *   Version 1.5 2/28/86                                                   *
  18. *     add more bulletproof method of finding first allocation block       *
  19. *   Version 1.6 3/20/86                                                   *
  20. *     restore all FF interrupts.                                          *
  21. *     restore the termination address to the local process                *
  22. *     reduce number of EMS blocks to 32.                                  *
  23. *     fix bug in number of EMS handles in EMS release step                *
  24. *     restore an undocumented address in the PSP which allows RELEASE of  *
  25. *       a COMMAND shell (emulates the EXIT command)                       *
  26. *   Version 1.7 (date not recorded)                                       *
  27. *     add "protected" marks                                               *
  28. *   Version 1.8 4/21/86                                                   *
  29. *     fix problem when mark is installed as 'MARK '                       *
  30. *   Version 1.9 5/22/86                                                   *
  31. *     release the environment of MARK when it is not contiguous with      *
  32. *       the MARK itself                                                   *
  33. *     capture RELEASE calls from within batch files and don't release the *
  34. *       batch control block                                               *
  35. *     fiddle with different methods of restoring interrupt vectors in     *
  36. *       an attempt to successfully remove DoubleDos. No success, not      *
  37. *       implemented. Note, after more effort: DDos apparently             *
  38. *       reprograms the 8259 as well as patching the operating system      *
  39. *   Version 2.0 6/17/86                                                   *
  40. *     support "file" marks placed by the new program FMARK                *
  41. *   Version 2.1 7/18/86                                                   *
  42. *     fix bug in restoring "parent" address in RELEASE PSP                *
  43. *   Version 2.2 3/3/87                                                    *
  44. *     add option to revector 8259 interrupt controller                    *
  45. *       (thanks to Steve Glynn for this code)                             *
  46. *     add option to leave mark in place when RELEASE is run               *
  47. *     restore save areas for EGA and interapplication communications      *
  48. *   Version 2.3 5/2/87                                                    *
  49. *     update watch area, if any, when releasing                           *
  50. *   Version 2.4 5/17/87                                                   *
  51. *     avoids use of EMS call $4B, which doesn't work in many EMS          *
  52. *       implementations                                                   *
  53. *     adds switch to ignore EMS altogether                                *
  54. *   Version 2.5 6/2/87                                                    *
  55. *     check version number of mark to avoid incompatibilities             *
  56. *   Version 2.6 1/15/89                                                   *
  57. *     fix problem occurring when command processor is EXE file            *
  58. *       (thanks to Tom Rawson for this code)                              *
  59. *     convert to Turbo Pascal 5.0                                         *
  60. *   Version 2.7                                                           *
  61. *     skipped                                                             *
  62. *   Version 2.8                                                           *
  63. *     treat file and net marks as protected marks                         *
  64. *     add key stuffing routine                                            *
  65. *     remove 8259 revector routine                                        *
  66. *   Version 2.9                                                           *
  67. *     don't treat file marks as protected marks                           *
  68. ***************************************************************************
  69. *   telephone: 408-438-8608, CompuServe: 72457,2131.                      *
  70. *   requires Turbo version 5 to compile.                                  *
  71. *   Compile with mAx dynamic memory = FFFF.                               *
  72. ***************************************************************************}
  73.  
  74. {$R-,S-}
  75.  
  76. program ReleaseTSR;
  77.   {-Release system memory above the last mark call}
  78.   {-Release expanded memory blocks allocated since the last mark call}
  79. uses
  80.   Dos;
  81.  
  82. const
  83.   Version = '2.9';
  84.   MarkID = 'M2.9 PARAMETER BLOCK FOLLOWS'; {Marking string for TSR MARK}
  85.   FmarkID = 'FM2.9 TSR';          {Marking string for TSR file mark}
  86.   NmarkID = 'MN2.9 TSR';          {Marking string for TSR NET file mark}
  87.  
  88.   ProtectChar = '!';          {Marks whose name begins with this will be
  89.                               released ONLY if an exact name match occurs}
  90.   MaxBlocks = 128;            {Max number of DOS allocation blocks supported}
  91.   MaxHandles = 32;            {Max number of EMS allocation blocks supported}
  92.   EMSinterrupt = $67;         {The vector used by the expanded memory manager}
  93.  
  94.   {Offsets into resident copy of MARK.COM for data storage}
  95.   MarkOffset = $103;          {Where markID is found in MARK TSR}
  96.   FmarkOffset = $60;          {Where FmarkID is found in FMARK TSR}
  97.   NmarkOffset = $60;          {Where NmarkID is found in MARKNET TSR}
  98.   VectorOffset = $120;        {Where vector table is stored}
  99.   EGAsavOffset = $520;        {Where the EGA save save is stored}
  100.   IntComOffset = $528;        {Where the interapps comm area is stored}
  101.   ParentOffset = $538;        {(TER) Where parent's PSP segment is stored}
  102.   EMScntOffset = $53A;        {Where count of EMS active pages is stored}
  103.   EMSmapOffset = $53C;        {Where the page map is stored}
  104.  
  105.   WatchID = 'TSR WATCHER';    {Marking string for WATCH}
  106.  
  107.   {Offsets into resident copy of WATCH.COM for data storage}
  108.   WatchOffset = $81;
  109.   NextChange = $104;
  110.   ChangeVectors = $220;
  111.   OrigVectors = $620;
  112.   CurrVectors = $A20;
  113.   MaxChanges = 128;           {Maximum number of vector changes stored in WATCH}
  114.  
  115.   Debug = False;
  116.  
  117. type
  118.   HandlePageRecord =
  119.   record
  120.     handle : Word;
  121.     numpages : Word;
  122.   end;
  123.  
  124.   PageArray = array[1..MaxHandles] of HandlePageRecord;
  125.   PageArrayPtr = ^PageArray;
  126.  
  127.   Block =
  128.   record                      {Store info about each memory block}
  129.     mcb : Word;
  130.     psp : Word;
  131.     releaseIt : Boolean;
  132.   end;
  133.  
  134.   BlockType = 0..MaxBlocks;
  135.   BlockArray = array[BlockType] of Block;
  136.  
  137.   HexString = string[4];
  138.   Pathname = string[79];
  139.  
  140. var
  141.   Blocks : BlockArray;
  142.   watchBlock, bottomBlock, blockNum : BlockType;
  143.  
  144.   markName : String;
  145.   Regs : Registers;
  146.  
  147.   FilMarkHandles, ReturnCode, StartMCB, StoredHandles, EMShandles : Word;
  148.   UseWatch, DealWithEMS,
  149.   KeepMark, MemMark, FilMark, Junk : Boolean;
  150.   Keys : string[16];
  151.  
  152.   FilMarkPageMap, Map, StoredMap : PageArrayPtr;
  153.   TrappedBytes : LongInt;
  154.  
  155.   {Save areas read in from file mark}
  156.   Vectors : array[0..1023] of Byte;
  157.   EGAsavTable : array[0..7] of Byte;
  158.   IntComTable : array[0..15] of Byte;
  159.   ParentTable : array[0..1] of Byte;
  160.  
  161.   procedure Abort(msg : String);
  162.     {-Halt in case of error}
  163.   begin
  164.     WriteLn(msg);
  165.     Halt(1);
  166.   end;
  167.  
  168.   procedure Halt(ReturnCode : Word);
  169.     {-Replace Turbo halt with one that doesn't restore any interrupts}
  170.   begin
  171.     Close(Output);
  172.     with Regs do begin
  173.       ah := $4C;
  174.       al := Lo(ReturnCode);
  175.       MsDos(Regs);
  176.     end;
  177.   end;
  178.  
  179.   procedure FindTheBlocks;
  180.     {-Scan memory for the allocated memory blocks}
  181.   const
  182.     MidBlockID = $4D;         {Byte DOS uses to identify part of MCB chain}
  183.     EndBlockID = $5A;         {Byte DOS uses to identify last block of MCB chain}
  184.   var
  185.     mcbSeg : Word;            {Segment address of current MCB}
  186.     nextSeg : Word;           {Computed segment address for the next MCB}
  187.     gotFirst : Boolean;       {True after first MCB is found}
  188.     gotLast : Boolean;        {True after last MCB is found}
  189.     idbyte : Byte;            {Byte that DOS uses to identify an MCB}
  190.  
  191.     function GetStartMCB : Word;
  192.       {-Return the first MCB segment}
  193.     begin
  194.       Regs.ah := $52;
  195.       MsDos(Regs);
  196.       GetStartMCB := MemW[Regs.es:(Regs.bx-2)];
  197.     end;
  198.  
  199.     procedure StoreTheBlock(var mcbSeg, nextSeg : Word;
  200.                             var gotFirst, gotLast : Boolean);
  201.       {-Store information regarding the memory block}
  202.     var
  203.       nextID : Byte;
  204.       pspAdd : Word;          {Segment address of the current PSP}
  205.       mcbLen : Word;          {Size of the current memory block in paragraphs}
  206.  
  207.     begin
  208.  
  209.       mcbLen := MemW[mcbSeg:3]; {Size of the MCB in paragraphs}
  210.       nextSeg := Succ(mcbSeg+mcbLen); {Where the next MCB should be}
  211.       pspAdd := MemW[mcbSeg:1]; {Address of program segment prefix for MCB}
  212.       nextID := Mem[nextSeg:0];
  213.  
  214.       if gotLast or (nextID = EndBlockID) or (nextID = MidBlockID) then begin
  215.         inc(blockNum);
  216.         gotFirst := True;
  217.         with Blocks[blockNum] do begin
  218.           mcb := mcbSeg;
  219.           psp := pspAdd;
  220.         end;
  221.       end;
  222.  
  223.     end;
  224.  
  225.   begin
  226.  
  227.     {Initialize}
  228.     StartMCB := GetStartMCB;
  229.     mcbSeg := StartMCB;
  230.     gotFirst := False;
  231.     gotLast := False;
  232.     blockNum := 0;
  233.  
  234.     {Scan all memory until the last block is found}
  235.     repeat
  236.       idbyte := Mem[mcbSeg:0];
  237.       if idbyte = MidBlockID then begin
  238.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  239.         if gotFirst then
  240.           mcbSeg := nextSeg
  241.         else
  242.           inc(mcbSeg);
  243.       end else if gotFirst and (idbyte = EndBlockID) then begin
  244.         gotLast := True;
  245.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  246.       end else
  247.         {Start block was invalid}
  248.         Abort('Corrupted allocation chain or program error....');
  249.     until gotLast;
  250.  
  251.   end;
  252.  
  253.   function StUpcase(s : String) : String;
  254.     {-Return the uppercase string}
  255.   var
  256.     i : Byte;
  257.  
  258.   begin
  259.     for i := 1 to Length(s) do
  260.       s[i] := UpCase(s[i]);
  261.     StUpcase := s;
  262.   end;
  263.  
  264.   function FindMark(markName, MarkID : String;
  265.                     MarkOffset : Word;
  266.                     var MemMark, FilMark : Boolean;
  267.                     var b : BlockType) : Boolean;
  268.     {-Find the last memory block matching idstring at offset idoffset}
  269.  
  270.     function HasIDstring(segment : Word;
  271.                          idString : String;
  272.                          idOffset : Word) : Boolean;
  273.       {-Return true if idstring is found at segment:idoffset}
  274.     var
  275.       tString : String;
  276.       len : Byte;
  277.     begin
  278.       len := Length(idString);
  279.       tString[0] := Chr(len);
  280.       Move(Mem[segment:idOffset], tString[1], len);
  281.       HasIDstring := (tString = idString);
  282.     end;
  283.  
  284.     function GetMarkName(segment : Word) : String;
  285.       {-Return a cleaned up mark name from the segment's PSP}
  286.     var
  287.       tString : String;
  288.       tlen : Byte absolute tString;
  289.     begin
  290.       Move(Mem[segment:$80], tString[0], 128);
  291.       while (tlen > 0) and ((tString[1] = ' ') or (tString[1] = ^I)) do
  292.         Delete(tString, 1, 1);
  293.       while (tlen > 0) and ((tString[tlen] = ' ') or (tString[tlen] = ^I)) do
  294.         dec(tlen);
  295.       GetMarkName := StUpcase(tString);
  296.     end;                      {GetMarkName}
  297.  
  298.     function MatchMemMark(segment : Word;
  299.                           markName : String;
  300.                           var b : BlockType) : Boolean;
  301.       {-Return true if MemMark is unnamed or matches current name}
  302.     var
  303.       tString : String;
  304.       FoundIt : Boolean;
  305.     begin
  306.       {Check the mark name stored in the PSP of the mark block}
  307.       tString := GetMarkName(segment);
  308.       if (markName <> '') then begin
  309.         FoundIt := (tString = StUpcase(markName));
  310.         if not(FoundIt) then
  311.           if (tString <> '') and (tString[1] = ProtectChar) then
  312.             {Current mark is protected, stop searching}
  313.             b := 1;
  314.       end else if (tString <> '') and (tString[1] = ProtectChar) then begin
  315.         {Stored mark name is protected}
  316.         FoundIt := False;
  317.         {Stop checking}
  318.         b := 1;
  319.       end else
  320.         {Match any mark}
  321.         FoundIt := True;
  322.       if not(FoundIt) then
  323.         dec(b);
  324.       MatchMemMark := FoundIt;
  325.     end;
  326.  
  327.     function MatchFilMark(segment : Word;
  328.                           markName : String;
  329.                           var b : BlockType) : Boolean;
  330.       {-Return true if FilMark is unnamed or matches current name}
  331.     var
  332.       tString : String;
  333.       FoundIt : Boolean;
  334.  
  335.       function ExistFile(path : String) : Boolean;
  336.         {-Return true if file exists}
  337.       var
  338.         f : file;
  339.       begin
  340.         Assign(f, path);
  341.         {$I-}
  342.         Reset(f);
  343.         {$I+}
  344.         if IoResult = 0 then begin
  345.           ExistFile := True;
  346.           Close(f);
  347.         end else
  348.           ExistFile := False;
  349.       end;
  350.  
  351.     begin
  352.       {Check the mark name stored in the PSP of the mark block}
  353.       tString := GetMarkName(segment);
  354.       if (markName <> '') then begin
  355.         markName := StUpcase(markName);
  356.         FoundIt := (tString = markName);
  357.         if FoundIt then begin
  358.           {Assure named file exists}
  359.           (*
  360.           WriteLn('Finding mark file ', markName);
  361.           *)
  362.           FoundIt := ExistFile(markName);
  363.         end;
  364.       end else
  365.         {File marks must be named on RELEASE command line}
  366.         FoundIt := False;
  367.       if not(FoundIt) then
  368.         {File marks are like protected marks}
  369.         {Stop checking if a non-matching file mark is found}
  370.         dec(B); {!!!!!!!}
  371. (*
  372.         b := 0;
  373. *)
  374.       MatchFilMark := FoundIt;
  375.     end;
  376.  
  377.   begin
  378.     {Scan from the last block down to find the last MARK TSR}
  379.     b := blockNum;
  380.     MemMark := False;
  381.     FilMark := False;
  382.     repeat
  383.       if Blocks[b].psp = PrefixSeg then
  384.         {Assure this program's command line is not matched}
  385.         dec(b)
  386.       else if HasIDstring(Blocks[b].psp, NmarkID, NmarkOffset) then
  387.         {A net mark, can't release it here. Stop looking}
  388.         b := 0
  389.       else if HasIDstring(Blocks[b].psp, MarkID, MarkOffset) then
  390.         {An in-memory mark}
  391.         MemMark := MatchMemMark(Blocks[b].psp, markName, b)
  392.       else if HasIDstring(Blocks[b].psp, FmarkID, FmarkOffset) then
  393.         {A file mark}
  394.         FilMark := MatchFilMark(Blocks[b].psp, markName, b)
  395.       else
  396.         {Not a mark}
  397.         dec(b);
  398.     until (b < 1) or MemMark or FilMark;
  399.     FindMark := MemMark or FilMark;
  400.   end;
  401.  
  402.   function Hex(i : Word) : HexString;
  403.     {-Return hex representation of Word}
  404.   const
  405.     hc : array[0..15] of Char = '0123456789ABCDEF';
  406.   var
  407.     l, h : Byte;
  408.   begin
  409.     l := Lo(i);
  410.     h := Hi(i);
  411.     Hex[0] := #4;
  412.     Hex[1] := hc[h shr 4];
  413.     Hex[2] := hc[h and $F];
  414.     Hex[3] := hc[l shr 4];
  415.     Hex[4] := hc[l and $F];
  416.   end;
  417.  
  418.   procedure ReadMarkFile(markName : String);
  419.     {-Read the mark file info into memory}
  420.   var
  421.     f : file;
  422.   begin
  423.     Assign(f, markName);
  424.     Reset(f, 1);
  425.  
  426.     {Read the vector table from the mark file, into a temporary memory area}
  427.     BlockRead(f, Vectors, 1024);
  428.  
  429.     {Read the BIOS miscellaneous save areas into temporary tables}
  430.     BlockRead(f, EGAsavTable, 8);
  431.     BlockRead(f, IntComTable, 16);
  432.     BlockRead(f, ParentTable, 2);
  433.  
  434.     {Read the number of EMS handles stored}
  435.     BlockRead(f, FilMarkHandles, 2);
  436.  
  437.     {Get a page map area and read the page map into it}
  438.     GetMem(FilMarkPageMap, 4*FilMarkHandles);
  439.     BlockRead(f, FilMarkPageMap^, 4*FilMarkHandles);
  440.     Close(f);
  441.  
  442.     if not(KeepMark) then
  443.       {Delete the mark file so it causes no mischief later}
  444.       Erase(f);
  445.   end;
  446.  
  447.   procedure CopyVectors(bottomBlock : BlockType);
  448.     {-Put interrupt vectors back into table}
  449.   var
  450.     bottompsp : Word;
  451.  
  452.   begin
  453.  
  454.     {Interrupts off}
  455.     inline($FA);
  456.  
  457.     {Restore the main interrupt vector table and the misc save areas}
  458.     if FilMark then begin
  459.       Move(Vectors, Mem[0:0], 1024);
  460.       Move(EGAsavTable, Mem[$40:$A8], 8);
  461.       Move(IntComTable, Mem[$40:$F0], 16);
  462.       Move(ParentTable, Mem[PrefixSeg:$16], 2);
  463.     end else begin
  464.       bottompsp := Blocks[bottomBlock].psp;
  465.       Move(Mem[bottompsp:VectorOffset], Mem[0:0], 1024);
  466.       Move(Mem[bottompsp:EGAsavOffset], Mem[$40:$A8], 8);
  467.       Move(Mem[bottompsp:IntComOffset], Mem[$40:$F0], 16);
  468.       Move(Mem[bottompsp:ParentOffset], Mem[PrefixSeg:$16], 2);
  469.     end;
  470.  
  471.     {Interrupts on}
  472.     inline($FB);
  473.  
  474.     {Move the old termination/break/error addresses into this program}
  475.     Move(Mem[0:$88], Mem[PrefixSeg:$0A], 12);
  476.  
  477.   end;
  478.  
  479.   procedure MarkBlocks(bottomBlock : BlockType);
  480.     {-Mark those blocks to be released}
  481.   var
  482.     b : BlockType;
  483.     commandPsp, markPsp : Word;
  484.     ch : Char;
  485.  
  486.     procedure BatchWarning(b : BlockType);
  487.       {-Warn about the trapping effect of batch files}
  488.     var
  489.       t : BlockType;
  490.  
  491.     begin
  492.       WriteLn('Memory space for TSRs installed prior to batch file');
  493.       WriteLn('will not be released until batch file completes.');
  494.       WriteLn;
  495.       ReturnCode := 1;
  496.       {Accumulate number of bytes temporarily trapped}
  497.       for t := 1 to b do
  498.         if Blocks[t].releaseIt then
  499.           inc(TrappedBytes, LongInt(MemW[Blocks[t].mcb:3]) shl 4);
  500.     end;
  501.  
  502.   begin
  503.  
  504.     commandPsp := Blocks[2].psp;
  505.     markPsp := Blocks[bottomBlock].psp;
  506.  
  507.     for b := 1 to blockNum do
  508.       with Blocks[b] do
  509.         if (b < bottomBlock) then begin
  510.           {Release any trapped environment block}
  511.           if KeepMark then
  512.             releaseIt := (psp <> PrefixSeg) and (psp > markPsp)
  513.           else
  514.             releaseIt := (psp <> PrefixSeg) and (psp >= markPsp);
  515.         end else if (psp = commandPsp) then begin
  516.           {Don't release blocks owned by COMMAND.COM}
  517.           releaseIt := False;
  518.           BatchWarning(b);
  519.         end else if KeepMark then
  520.           {Release all but RELEASE and the mark}
  521.           releaseIt := (psp <> PrefixSeg) and (psp <> markPsp)
  522.         else
  523.           {Release all but RELEASE itself}
  524.           releaseIt := (psp <> PrefixSeg);
  525.  
  526.     if Debug then begin
  527.       for b := 1 to blockNum do with Blocks[b] do
  528.         WriteLn(b:3, ' ', Hex(psp), ' ', Hex(mcb), ' ', releaseIt);
  529.       ReadLn;
  530.     end;
  531.  
  532.   end;
  533.  
  534.   procedure ReleaseMem;
  535.     {-Release DOS memory marked for release}
  536.   var
  537.     b : BlockType;
  538.   begin
  539.     with Regs do
  540.       for b := 1 to blockNum do
  541.         with Blocks[b] do
  542.           if releaseIt then begin
  543.             ah := $49;
  544.             {The block is always 1 paragraph above the MCB}
  545.             es := Succ(mcb);
  546.             MsDos(Regs);
  547.             if Odd(flags) then begin
  548.               WriteLn('Could not release block at segment ', Hex(es));
  549.               Abort('Memory may be a mess... Please reboot');
  550.             end;
  551.           end;
  552.   end;
  553.  
  554.   procedure UpdateWatch(watchBlock : BlockType);
  555.     {-Write a new watch data area based on the release and the original watch}
  556.   type
  557.     ChangeBlock =
  558.     record
  559.       VecID : Word;
  560.       VecOfs : Word;
  561.       VecSeg : Word;
  562.       PatchWord : Word;
  563.     end;
  564.   var
  565.     changes : array[0..MaxChanges] of ChangeBlock;
  566.     p : ^ChangeBlock;
  567.     watchseg, c, o, i, actualmax : Word;
  568.     KeepPSP : Boolean;
  569.  
  570.     function WillKeepPSP(pspAdd : Word) : Boolean;
  571.       {-Return true if this psp address will be kept}
  572.     var
  573.       b : BlockType;
  574.     begin
  575.       for b := 1 to blockNum do
  576.         with Blocks[b] do
  577.           if psp = pspAdd then begin
  578.             WillKeepPSP := not(releaseIt);
  579.             Exit;
  580.           end;
  581.     end;
  582.  
  583.   begin
  584.  
  585.     {Initialize}
  586.     watchseg := Blocks[watchBlock].psp;
  587.     actualmax := MemW[watchseg:NextChange];
  588.  
  589.     {Transfer changes from WATCH into a buffer array}
  590.     i := 0;
  591.     o := 0;
  592.     while i < actualmax do begin
  593.       p := Ptr(watchseg, ChangeVectors+i);
  594.       Move(p^, changes[o], SizeOf(ChangeBlock));
  595.       inc(i, SizeOf(ChangeBlock));
  596.       inc(o);
  597.     end;
  598.  
  599.     {Determine which change records to keep and transfer them back to WATCH}
  600.     KeepPSP := True;
  601.     i := 0;
  602.     for c := 0 to Pred(o) do begin
  603.       with changes[c] do
  604.         if VecID = $FFFF then
  605.           {This record starts a new PSP. See if PSP is kept in memory}
  606.           KeepPSP := WillKeepPSP(VecOfs);
  607.       if KeepPSP then begin
  608.         p := Ptr(watchseg, ChangeVectors+i);
  609.         Move(changes[c], p^, SizeOf(ChangeBlock));
  610.         i := i+SizeOf(ChangeBlock);
  611.       end;
  612.     end;
  613.     MemW[watchseg:NextChange] := i;
  614.  
  615.     {Update the WATCH image of the vector table to whatever's current}
  616.     Move(Mem[0:0], Mem[watchseg:CurrVectors], 1024);
  617.  
  618.   end;
  619.  
  620.   function EMSpresent : Boolean;
  621.     {-Return true if EMS memory manager is present}
  622.   var
  623.     f : file;
  624.   begin
  625.     {"file handle" defined by the expanded memory manager at installation}
  626.     Assign(f, 'EMMXXXX0');
  627.     {$I-}
  628.     Reset(f);
  629.     {$I+}
  630.     if IOResult = 0 then begin
  631.       EMSpresent := True;
  632.       Close(f);
  633.     end else
  634.       EMSpresent := False;
  635.   end;
  636.  
  637.   procedure RestoreEMSmap;
  638.     {-Restore EMS to state at time of mark}
  639.  
  640.     function GetHandles(bottomBlock : BlockType; EMScntOffset : Word) : Word;
  641.       {-Return the number of handles stored by mark}
  642.     begin
  643.       if FilMark then
  644.         GetHandles := FilMarkHandles
  645.       else
  646.         GetHandles := MemW[Blocks[bottomBlock].psp:EMScntOffset];
  647.     end;
  648.  
  649.     function GetStoredMap(bottomBlock : BlockType; EMSmapOffset : Word) : PageArrayPtr;
  650.       {-Returns a pointer to the stored page array}
  651.     begin
  652.       if FilMark then
  653.         GetStoredMap := FilMarkPageMap
  654.       else
  655.         GetStoredMap := Ptr(Blocks[bottomBlock].psp, EMSmapOffset);
  656.     end;
  657.  
  658.     procedure EMSpageMap(var PageMap : PageArray; var EMShandles:Word);
  659.       {-return an array of the allocated memory blocks}
  660.     begin
  661.       regs.ah := $4D;
  662.       regs.es := Seg(PageMap);
  663.       regs.di := Ofs(PageMap);
  664.       regs.bx := 0;
  665.       Intr(EMSinterrupt, regs);
  666.       if regs.ah <> 0 then begin
  667.         WriteLn('EMS device not responding');
  668.         emshandles:=0;
  669.       end else
  670.         emshandles:=regs.bx;
  671.     end;
  672.  
  673.     procedure ReleaseEMSblocks(var oldmap, newmap : PageArray);
  674.       {-Release those EMS blocks allocated since MARK was installed}
  675.     var
  676.       o, n, nhandle : Word;
  677.  
  678.       procedure EMSdeallocate(EMShandle : Word);
  679.         {-Release the allocated expanded memory}
  680.       begin
  681.         Regs.ah := $45;
  682.         Regs.dx := EMShandle;
  683.         Intr(EMSinterrupt, Regs);
  684.         if Regs.ah <> 0 then begin
  685.           WriteLn('Program error or EMS device not responding');
  686.           Abort('EMS memory may be a mess... Please reboot');
  687.         end;
  688.       end;
  689.  
  690.     begin
  691.       for n := 1 to EMShandles do begin
  692.         {Scan all current handles}
  693.         nhandle := newmap[n].handle;
  694.         if StoredHandles > 0 then begin
  695.           {See if current handle matches one stored by MARK}
  696.           o := 1;
  697.           while (oldmap[o].handle <> nhandle) and (o <= StoredHandles) do
  698.             inc(o);
  699.           {If not, deallocate the current handle}
  700.           if (o > StoredHandles) then
  701.             EMSdeallocate(nhandle);
  702.         end else
  703.           {No handles stored by MARK, deallocate all current handles}
  704.           EMSdeallocate(nhandle);
  705.       end;
  706.     end;
  707.  
  708.   begin
  709.     {Get the existing EMS page map}
  710.     GetMem(Map, 2048);
  711.     EMSpageMap(Map^, EMShandles);
  712.     if EMShandles > MaxHandles then
  713.       WriteLn('EMS process count exceeds capacity of RELEASE - no action taken')
  714.     else if EMShandles <> 0 then begin
  715.       {See how many handles were active when MARK was installed}
  716.       StoredHandles := GetHandles(bottomBlock, EMScntOffset);
  717.       {Get the stored page map}
  718.       StoredMap := GetStoredMap(bottomBlock, EMSmapOffset);
  719.       {Compare the two maps and deallocate pages not in the stored map}
  720.       ReleaseEMSblocks(StoredMap^, Map^);
  721.     end;
  722.   end;
  723.  
  724.   procedure GetOptions;
  725.     {-Analyze command line for options}
  726.   var
  727.     arg : String;
  728.     arglen : Byte absolute arg;
  729.     i : Word;
  730.  
  731.     procedure WriteHelp;
  732.       {-Show the options}
  733.     begin
  734.       WriteLn;
  735.       WriteLn('RELEASE removes memory-resident programs from memory and restores the');
  736.       WriteLn('interrupt vectors to their state as found prior to the installation of a MARK.');
  737.       WriteLn('RELEASE manages both normal DOS memory and also Lotus/Intel Expanded Memory.');
  738.       WriteLn('If WATCH has been installed, RELEASE will update the WATCH data area for the');
  739.       WriteLn('TSRs released.');
  740.       WriteLn;
  741.       WriteLn('RELEASE accepts the following command line syntax:');
  742.       WriteLn;
  743.       WriteLn('  RELEASE [MarkName] [Options]');
  744.       WriteLn;
  745.       WriteLn('Options may be preceded by either / or -. Valid options are as follows:');
  746.       WriteLn;
  747.       WriteLn('  /E         do NOT access EMS memory.');
  748.       WriteLn('  /K         release memory, but keep the mark in place.');
  749.       WriteLn('  /S chars   stuff string (<16 chars) into keyboard buffer on exit.');
  750.       WriteLn('  /?         write this help screen.');
  751.       Halt(1);
  752.     end;
  753.  
  754.   begin
  755.     {Initialize defaults}
  756.     markName := '';
  757.     Keys := '';
  758.     KeepMark := False;
  759.     DealWithEMS := True;
  760.     ReturnCode := 0;
  761.     TrappedBytes := 00;
  762.  
  763.     i := 1;
  764.     while i <= ParamCount do begin
  765.       arg := ParamStr(i);
  766.       if (arg[1] = '?') then
  767.         WriteHelp
  768.       else if (arg[1] = '-') or (arg[1] = '/') then
  769.         case arglen of
  770.           1 : Abort('Missing command option following '+arg);
  771.           2 : case UpCase(arg[2]) of
  772.                 '?' : WriteHelp;
  773.                 'E' : DealWithEMS := False;
  774.                 'K' : KeepMark := True;
  775.                 'S' : begin
  776.                         if I >= ParamCount then
  777.                           Abort('Key string missing');
  778.                         inc(I);
  779.                         Arg := ParamStr(I);
  780.                         if ArgLen > 15 then
  781.                           Abort('No more than 15 keys may be stuffed');
  782.                         Keys := Arg+^M;
  783.                       end;
  784.               else
  785.                 Abort('Unknown command option: '+arg);
  786.               end;
  787.         else
  788.           Abort('Unknown command option: '+arg);
  789.         end
  790.       else
  791.         {Named mark}
  792.         markName := arg;
  793.       inc(i);
  794.     end;
  795.  
  796.   end;
  797.  
  798.   const
  799.     KbdStart = $1E;
  800.     KbdEnd = $3C;
  801.   var
  802.     KbdHead : Word absolute $40 : $1A;
  803.     KbdTail : Word absolute $40 : $1C;
  804.  
  805.   procedure StuffKey(W : Word);
  806.     {-Stuff one key into the keyboard buffer}
  807.   var
  808.     SaveKbdTail : Word;
  809.   begin
  810.     SaveKbdTail := KbdTail;
  811.     if KbdTail = KbdEnd then
  812.       KbdTail := KbdStart
  813.     else
  814.       Inc(KbdTail, 2);
  815.     if KbdTail = KbdHead then
  816.       KbdTail := SaveKbdTail
  817.     else
  818.       MemW[$40:SaveKbdTail] := W;
  819.   end;
  820.  
  821.   procedure StuffKeys(Keys : string; ClearFirst : Boolean);
  822.     {-Stuff up to 16 keys into keyboard buffer}
  823.   var
  824.     Len : Byte;
  825.     I : Byte;
  826.   begin
  827.     if ClearFirst then
  828.       KbdTail := KbdHead;
  829.     Len := Length(Keys);
  830.     if Len > 16 then
  831.       Len := 16;
  832.     for I := 1 to Length(Keys) do
  833.       StuffKey(Ord(Keys[I]));
  834.   end;
  835.  
  836. begin
  837.   WriteLn('RELEASE ', Version, ', by TurboPower Software');
  838.  
  839.   {Analyze command line for options}
  840.   GetOptions;
  841.  
  842.   {Get all allocated memory blocks in normal memory}
  843.   FindTheBlocks;
  844.  
  845.   {Find the last one marked with the MARK idstring, and MarkName if specified}
  846.   if not(FindMark(markName, MarkID, MarkOffset, MemMark, FilMark, bottomBlock)) then
  847.     Abort('No matching marker found, or protected marker encountered.');
  848.  
  849.   {Find the watch block, if any}
  850.   UseWatch := FindMark('', WatchID, WatchOffset, Junk, Junk, watchBlock);
  851.  
  852.   {Mark those blocks to be released}
  853.   MarkBlocks(bottomBlock);
  854.  
  855.   {Get file mark information into memory}
  856.   if FilMark then
  857.     ReadMarkFile(markName);
  858.  
  859.   {Copy the vector table from the MARK copy}
  860.   CopyVectors(bottomBlock);
  861.  
  862.   {Update the watch block if requested}
  863.   if UseWatch then
  864.     {The WATCH ID was found in memory}
  865.     if not(Blocks[watchBlock].releaseIt) then
  866.       {Watch itself won't be released}
  867.       UpdateWatch(watchBlock);
  868.  
  869.   {Release normal memory marked for release}
  870.   ReleaseMem;
  871.  
  872.   {Deal with expanded memory}
  873.   if DealWithEMS then
  874.     if EMSpresent then
  875.       RestoreEMSmap;
  876.  
  877.   {Write success message}
  878.   Write('Memory released above last MARK');
  879.   if markName <> '' then
  880.     Write(' (', StUpcase(markName), ')');
  881.   WriteLn;
  882.  
  883.   if ReturnCode <> 0 then
  884.     WriteLn(TrappedBytes, ' bytes temporarily trapped until batch file completes');
  885.  
  886.   {Stuff keyboard buffer if requested}
  887.   if Length(Keys) > 0 then
  888.     StuffKeys(Keys, True);
  889.  
  890.   Halt(ReturnCode);
  891. end.
  892.