home *** CD-ROM | disk | FTP | other *** search
/ CD Actual 14 / CDACTUAL.iso / cdactual / demobin / share / program / Pascal / TSRSRC30.ZIP / RELEASE.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1991-10-15  |  21.7 KB  |  692 lines

  1. {**************************************************************************
  2. *   RELEASE - Releases memory above the last MARK call made.              *
  3. *   Copyright (c) 1986,1991 Kim Kokkonen, TurboPower Software.            *
  4. *   May be freely distributed and used but not sold except by permission. *
  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. *   :                                                                     *
  10. *   long intervening history                                              *
  11. *   :                                                                     *
  12. *   Version 3.0 9/24/91                                                   *
  13. *     make compatible with DOS 5                                          *
  14. *     add Quiet option                                                    *
  15. *     close open file handles of released blocks                          *
  16. *     update for new WATCH behavior                                       *
  17. *     increase number of supported memory blocks to 256                   *
  18. *     add support for upper memory blocks                                 *
  19. ***************************************************************************
  20. *   telephone: 719-260-6641, CompuServe: 76004,2611.                      *
  21. *   requires Turbo version 6 to compile.                                  *
  22. ***************************************************************************}
  23.  
  24. {$R-,S-,I-,V-,B-,F-,A-,E-,N-,G-,X-}
  25. {$M 16384,0,655360}
  26.  
  27. program ReleaseTSR;
  28.   {-Restore system to state it had when a MARK was placed}
  29.  
  30. uses
  31.   Dos,
  32.   MemU,
  33.   Ems;
  34.  
  35. const
  36.   Version = '3.0';
  37.   MarkID = 'M3.0 PARAMETER BLOCK FOLLOWS'; {Marking string for TSR MARK}
  38.   FmarkID = 'FM3.0 TSR';          {Marking string for TSR file mark}
  39.   NmarkID = 'MN3.0 TSR';          {Marking string for TSR NET file mark}
  40.  
  41.   ProtectChar = '!';          {Marks whose name begins with this will be
  42.                               released ONLY if an exact name match occurs}
  43.  
  44.   {Offsets into resident copy of MARK.COM for data storage}
  45.   MarkOffset = $103;          {Where markID is found in MARK TSR}
  46.   FmarkOffset = $60;          {Where FmarkID is found in FMARK TSR}
  47.   NmarkOffset = $60;          {Where NmarkID is found in MARKNET TSR}
  48.   VectorOffset = $120;        {Where vector table is stored}
  49.   EGAsavOffset = $520;        {Where the EGA save save is stored}
  50.   IntComOffset = $528;        {Where the interapps comm area is stored}
  51.   ParentOffset = $538;        {(TER) Where parent's PSP segment is stored}
  52.   EMScntOffset = $53A;        {Where count of EMS active pages is stored}
  53.   EMSmapOffset = $53C;        {Where the page map is stored}
  54.  
  55. var
  56.   Blocks : BlockArray;
  57.   markBlock, BlockMax : BlockType;
  58.   CommandSeg : Word;
  59.   StartMcb : Word;
  60.  
  61.   markName : String[127];
  62.  
  63.   FilMarkHandles, ReturnCode : Word;
  64.   ShowHiMem, DealWithEMS, KeepMark, MemMark, FilMark, Quiet : Boolean;
  65.   UmbLinkStatus : Boolean;
  66.   Keys : string[16];
  67.   SaveExit : pointer;
  68.  
  69.   TrappedBytes : LongInt;
  70.  
  71.   MarkEHandles : Word;
  72.   CurrEHandles : Word;
  73.   MarkEmsHandles : PageArrayPtr;
  74.   CurrEmsHandles : PageArrayPtr;
  75.  
  76.   {Save areas read in from file mark}
  77.   Vectors : array[0..1023] of Byte;
  78.   EGAsavTable : array[0..7] of Byte;
  79.   IntComTable : array[0..15] of Byte;
  80.   ParentTable : array[0..1] of Byte;
  81.   McbP : ^McbGroup;
  82.  
  83.   procedure SafeExit; far;
  84.   var
  85.     Status : Word;
  86.   begin
  87.     ExitProc := SaveExit;
  88.     if HiMemAvailable(DosV) then
  89.       Status := SetUmbLinkStatus(UmbLinkStatus);
  90.   end;
  91.  
  92.   procedure Abort(msg : String);
  93.     {-Halt in case of error}
  94.   begin
  95.     WriteLn(msg);
  96.     Halt(1);
  97.   end;
  98.  
  99.   procedure NoRestoreHalt(ReturnCode : Word);
  100.     {-Replace Turbo halt with one that doesn't restore any interrupts}
  101.   begin
  102.     SafeExit;
  103.     Close(Output);
  104.     asm
  105.       mov ah,$4C
  106.       mov al, byte(ReturnCode)
  107.       int $21
  108.     end;
  109.   end;
  110.  
  111.   function FindMark(markName, MarkID : String;
  112.                     MarkOffset : Word;
  113.                     var MemMark, FilMark : Boolean;
  114.                     var b : BlockType) : Boolean;
  115.     {-Find the last memory block matching idstring at offset idoffset}
  116.   var
  117.     BPsp : Word;
  118.     PassedFileMark : Boolean;
  119.  
  120.     function HasIDstring(segment : Word;
  121.                          idString : String;
  122.                          idOffset : Word) : Boolean;
  123.       {-Return true if idstring is found at segment:idoffset}
  124.     var
  125.       len : Byte;
  126.       tString : String;
  127.     begin
  128.       len := Length(idString);
  129.       tString[0] := Chr(len);
  130.       Move(Mem[segment:idOffset], tString[1], len);
  131.       HasIDstring := (tString = idString);
  132.     end;
  133.  
  134.     function GetMarkName(segment : Word) : String;
  135.       {-Return a cleaned up mark name from the segment's PSP}
  136.     var
  137.       tString : String;
  138.       tlen : Byte absolute tString;
  139.     begin
  140.       Move(Mem[segment:$80], tString[0], 128);
  141.       while (tlen > 0) and ((tString[1] = ' ') or (tString[1] = ^I)) do
  142.         Delete(tString, 1, 1);
  143.       while (tlen > 0) and ((tString[tlen] = ' ') or (tString[tlen] = ^I)) do
  144.         dec(tlen);
  145.       GetMarkName := StUpcase(tString);
  146.     end;
  147.  
  148.     function MatchMemMark(segment : Word;
  149.                           markName : String;
  150.                           var b : BlockType) : Boolean;
  151.       {-Return true if MemMark is unnamed or matches current name}
  152.     var
  153.       FoundIt : Boolean;
  154.       tString : String;
  155.     begin
  156.       tString := GetMarkName(segment);
  157.       if markName <> '' then begin
  158.         FoundIt := (tString = markName);
  159.         if not FoundIt and not ShowHiMem then
  160.           if (tString <> '') and (tString[1] = ProtectChar) then
  161.             {Current mark is protected, stop searching}
  162.             b := 1;
  163.       end else if (tString <> '') and (tString[1] = ProtectChar) then begin
  164.         {Stored mark name is protected}
  165.         FoundIt := False;
  166.         {Stop checking}
  167.         b := 1;
  168.       end else if tString = '' then
  169.         {Unnamed release and unnamed mark}
  170.         FoundIt := True
  171.       else begin
  172.         {Unnamed release and named mark, match only if didn't pass file mark}
  173.         FoundIt := not PassedFileMark;
  174.         {Stop searching if no match}
  175.         if not FoundIt then
  176.           B := 1;
  177.       end;
  178.       if not FoundIt then
  179.         dec(b);
  180.       MatchMemMark := FoundIt;
  181.     end;
  182.  
  183.     function MatchFilMark(segment : Word;
  184.                           markName : String;
  185.                           var b : BlockType) : Boolean;
  186.       {-Return true if FilMark is unnamed or matches current name}
  187.     var
  188.       FoundIt : Boolean;
  189.     begin
  190.       if markName <> '' then begin
  191.         FoundIt := (GetMarkName(segment) = markName);
  192.         if FoundIt then
  193.           {Assure named file exists}
  194.           FoundIt := ExistFile(markName);
  195.       end else begin
  196.         {File marks must be named on RELEASE command line}
  197.         FoundIt := False;
  198.         PassedFileMark := True;
  199.       end;
  200.       if not FoundIt then
  201.         dec(B);
  202.       MatchFilMark := FoundIt;
  203.     end;
  204.  
  205.   begin
  206.     {Scan from the last block down to find the last MARK TSR}
  207.     b := BlockMax;
  208.     MemMark := False;
  209.     FilMark := False;
  210.     PassedFileMark := False;
  211.     repeat
  212.       BPsp := Blocks[B].Psp;
  213.       if (Blocks[B].Mcb+1 <> BPsp) or (BPsp = PrefixSeg) then
  214.         {Don't match any non-program block or this program}
  215.         dec(b)
  216.       else if HasIDstring(BPsp, NmarkID, NmarkOffset) then begin
  217.         {A net mark, can't release it here}
  218.         if ShowHiMem then
  219.           {Keep looking}
  220.           dec(b)
  221.         else
  222.           {Stop looking}
  223.           b := 0;
  224.       end else if HasIDstring(BPsp, MarkID, MarkOffset) then
  225.         {An in-memory mark}
  226.         MemMark := MatchMemMark(BPsp, markName, b)
  227.       else if HasIDstring(BPsp, FmarkID, FmarkOffset) then
  228.         {A file mark}
  229.         FilMark := MatchFilMark(BPsp, markName, b)
  230.       else
  231.         {Not a mark}
  232.         dec(b);
  233.     until (b < 1) or MemMark or FilMark;
  234.     FindMark := MemMark or FilMark;
  235.   end;
  236.  
  237.   procedure ReadMarkFile(markName : String);
  238.     {-Read the mark file info into memory}
  239.   var
  240.     McbCount : Word;
  241.     f : file;
  242.   begin
  243.     Assign(f, markName);
  244.     Reset(f, 1);
  245.     if IoResult <> 0 then
  246.       Abort('Error opening mark file');
  247.  
  248.     {Read the vector table from the mark file, into a temporary memory area}
  249.     BlockRead(f, Vectors, 1024);
  250.  
  251.     {Read the BIOS miscellaneous save areas into temporary tables}
  252.     BlockRead(f, EGAsavTable, 8);
  253.     BlockRead(f, IntComTable, 16);
  254.     BlockRead(f, ParentTable, 2);
  255.  
  256.     {Read the stored EMS handles, if any}
  257.     BlockRead(f, MarkEHandles, SizeOf(Word));
  258.     GetMem(MarkEmsHandles, SizeOf(HandlePageRecord)*MarkEHandles);
  259.     BlockRead(f, MarkEmsHandles^, SizeOf(HandlePageRecord)*MarkEHandles);
  260.  
  261.     {Read the stored Mcb table}
  262.     BlockRead(f, McbCount, SizeOf(Word));
  263.     GetMem(McbP, SizeOf(Word)+2*SizeOf(Word)*McbCount);
  264.     BlockRead(f, McbP^.Mcbs, 2*SizeOf(Word)*McbCount);
  265.     McbP^.Count := McbCount;
  266.  
  267.     if IoResult <> 0 then
  268.       Abort('Error reading mark file');
  269.     Close(f);
  270.  
  271.     if not KeepMark then
  272.       {Delete the mark file so it causes no mischief later}
  273.       Erase(f);
  274.   end;
  275.  
  276.   procedure InitMarkInfo;
  277.     {-Set up information from mark in memory}
  278.   var
  279.     markPsp : Word;
  280.   begin
  281.     markPsp := Blocks[markBlock].psp;
  282.     MarkEHandles := MemW[markPsp:EMScntOffset];
  283.     MarkEmsHandles := Ptr(markPsp, EMSmapOffset);
  284.     McbP := Ptr(markPsp, EMSmapOffset+4*MarkEHandles);
  285.   end;
  286.  
  287.   procedure CopyVectors(markBlock : BlockType);
  288.     {-Put interrupt vectors back into table}
  289.   var
  290.     markPsp : Word;
  291.   begin
  292.     {Interrupts off}
  293.     inline($FA);
  294.  
  295.     {Restore the main interrupt vector table and the misc save areas}
  296.     if FilMark then begin
  297.       Move(Vectors, Mem[0:0], 1024);
  298.       Move(EGAsavTable, Mem[$40:$A8], 8);
  299.       Move(IntComTable, Mem[$40:$F0], 16);
  300.       Move(ParentTable, Mem[PrefixSeg:$16], 2);
  301.     end else begin
  302.       markPsp := Blocks[markBlock].psp;
  303.       Move(Mem[markPsp:VectorOffset], Mem[0:0], 1024);
  304.       Move(Mem[markPsp:EGAsavOffset], Mem[$40:$A8], 8);
  305.       Move(Mem[markPsp:IntComOffset], Mem[$40:$F0], 16);
  306.       Move(Mem[markPsp:ParentOffset], Mem[PrefixSeg:$16], 2);
  307.     end;
  308.  
  309.     {Interrupts on}
  310.     inline($FB);
  311.  
  312.     {Move the old termination/break/error addresses into this program}
  313.     Move(Mem[0:$88], Mem[PrefixSeg:$0A], 12);
  314.   end;
  315.  
  316.   procedure MarkBlocks(markBlock : BlockType);
  317.     {-Mark those blocks to be released}
  318.  
  319.     procedure BatchWarning(b : BlockType);
  320.       {-Warn about the trapping effect of batch files}
  321.     var
  322.       t : BlockType;
  323.     begin
  324.       WriteLn('Memory space for TSRs installed prior to batch file');
  325.       WriteLn('will not be released until batch file completes.');
  326.       WriteLn;
  327.       ReturnCode := 1;
  328.       {Accumulate number of bytes temporarily trapped}
  329.       for t := 1 to b do
  330.         if Blocks[t].releaseIt then
  331.           inc(TrappedBytes, LongInt(MemW[Blocks[t].mcb:3]) shl 4);
  332.     end;
  333.  
  334.     procedure MarkBlocksAbove;
  335.       {-Mark blocks above the mark}
  336.     var
  337.       markPsp : Word;
  338.       b : BlockType;
  339.     begin
  340.       markPsp := Blocks[markBlock].psp;
  341.       for b := 1 to BlockMax do
  342.         with Blocks[b] do
  343.           if (b >= markBlock) and (psp = CommandSeg) then begin
  344.             {Don't release blocks owned by master COMMAND.COM}
  345.             releaseIt := False;
  346.             BatchWarning(b);
  347.           end else if KeepMark then
  348.             {Release all but RELEASE and the mark}
  349.             releaseIt := (psp <> PrefixSeg) and (psp > markPsp)
  350.           else
  351.             releaseIt := (psp <> PrefixSeg) and (psp >= markPsp);
  352.     end;
  353.  
  354.     procedure MarkUnallocatedBlocks;
  355.       {-Mark blocks that weren't allocated at time of mark}
  356.     var
  357.       markPsp : Word;
  358.       TopSeg : Word;
  359.       b : BlockType;
  360.       m : BlockType;
  361.       Found : Boolean;
  362.     begin
  363.       markPsp := Blocks[markBlock].psp;
  364.  
  365.       {Find last low memory mcb}
  366.       TopSeg := TopOfMemSeg-1;
  367.       m := 1;
  368.       Found := False;
  369.       while (not Found) and (m <= McbP^.Count) do
  370.         if McbP^.Mcbs[m].mcb >= TopSeg then
  371.           Found := True
  372.         else
  373.           inc(m);
  374.  
  375.       {Mark out all mcbs associated with psp of last low memory mcb}
  376.       TopSeg := McbP^.Mcbs[m-1].psp;
  377.       if TopSeg <> markPsp then
  378.         for m := 1 to McbP^.Count do
  379.           with McbP^.Mcbs[m] do
  380.             if psp = TopSeg then
  381.               psp := 0;
  382.  
  383.       for b := 1 to BlockMax do
  384.         with Blocks[b] do begin
  385.           Found := False;
  386.           m := 1;
  387.           while (not Found) and (m <= McbP^.Count) do begin
  388.             Found := (McbP^.Mcbs[m].psp <> 0) and (McbP^.Mcbs[m].mcb = mcb);
  389.             inc(m);
  390.           end;
  391.           if Found then
  392.             {was allocated at time of mark, keep it now unless a mark to be released}
  393.             releaseIt := not KeepMark and (psp = markPsp)
  394.           else if psp = CommandSeg then
  395.             {Don't release blocks owned by master COMMAND.COM}
  396.             releaseIt := False
  397.           else
  398.             {not allocated at time of mark}
  399.             releaseIt := (psp <> 0) and (psp <> PrefixSeg);
  400.         end;
  401.     end;
  402.  
  403.   begin
  404.     if ShowHiMem then
  405.       MarkUnallocatedBlocks
  406.     else
  407.       MarkBlocksAbove;
  408.  
  409.     {$IFDEF Debug}
  410.     for b := 1 to BlockMax do
  411.       with Blocks[b] do
  412.         WriteLn(b:3, ' ', HexW(psp), ' ', HexW(mcb), ' ', releaseIt);
  413.     {$ENDIF}
  414.   end;
  415.  
  416.   function ReleaseBlock(Segm : Word) : Word; assembler;
  417.     {-Use DOS services to release memory block}
  418.   asm
  419.     mov ah,$49
  420.     mov es,Segm
  421.     int $21
  422.     jc  @Done
  423.     xor ax,ax
  424. @Done:
  425.   end;
  426.  
  427.   procedure ReleaseMem;
  428.     {-Release DOS memory marked for release}
  429.   var
  430.     Status : Word;
  431.     B : BlockType;
  432.   begin
  433.     for B := 1 to BlockMax do
  434.       with Blocks[B] do
  435.         if releaseIt then
  436.           if ReleaseBlock(mcb+1) <> 0 then begin
  437.             WriteLn('Could not release block at segment ', HexW(mcb+1));
  438.             Abort('Memory may be a mess... Please reboot');
  439.           end;
  440.   end;
  441.  
  442.   procedure SetPSP(PSP : Word); assembler;
  443.     {-Sets current PSP}
  444.   asm
  445.     mov bx,psp
  446.     mov ax,$5000
  447.     int $21
  448.   end;
  449.  
  450.   procedure CloseHandles;
  451.     {-Close any handles of blocks marked for release}
  452.   type
  453.     HandleTable = array[0..65520] of Byte;
  454.   var
  455.     O : Word;
  456.     FileMax : Word;
  457.     TablePtr : ^HandleTable;
  458.     b : BlockType;
  459.     H : Byte;
  460.   begin
  461.     for b := 1 to BlockMax do
  462.       with Blocks[b] do
  463.         if releaseIt and (psp = mcb+1) and (memw[psp:0] = $20CD) then begin
  464.           {A released block with a program segment prefix}
  465.           {set psp to this block}
  466.           setpsp(psp);
  467.  
  468.           {Deal with expanded handle tables in DOS 3.0 and later}
  469.           if DosV >= 3 then begin
  470.             FileMax := MemW[Psp:$32];
  471.             TablePtr := Pointer(MemL[Psp:$34]);
  472.           end else begin
  473.             FileMax := 20;
  474.             TablePtr := Ptr(Psp, $18);
  475.           end;
  476.  
  477.           for O := 0 to FileMax-1 do begin
  478.             H := TablePtr^[O];
  479.             case H of
  480.               0, 1, 2, $FF : {standard handle or not open} ;
  481.             else
  482.               asm
  483.                 mov ah,$3E
  484.                 mov bx,O
  485.                 int $21      {ignore errors}
  486.               end;
  487.             end;
  488.           end;
  489.         end;
  490.  
  491.     {reset psp}
  492.     setpsp(prefixseg);
  493.   end;
  494.  
  495.   procedure RestoreEMSmap;
  496.     {-Restore EMS to state at time of mark}
  497.   var
  498.     O, N, NHandle : Word;
  499.  
  500.     procedure EmsError;
  501.     begin
  502.       WriteLn('Program error or EMS device not responding');
  503.       Abort('EMS memory may be a mess... Please reboot');
  504.     end;
  505.  
  506.   begin
  507.     {Get the existing EMS page map}
  508.     GetMem(CurrEmsHandles, MaxHandles*SizeOf(HandlePageRecord));
  509.     CurrEHandles := EmsHandles(CurrEmsHandles^);
  510.  
  511.     if CurrEHandles > MaxHandles then
  512.       WriteLn('EMS handle count exceeds capacity of RELEASE -- no action taken')
  513.  
  514.     else if CurrEHandles <> 0 then begin
  515.       {Compare the two maps and deallocate pages not in the stored map}
  516.       for N := 1 to CurrEHandles do begin
  517.         {Scan all current handles}
  518.         NHandle := CurrEmsHandles^[N].Handle;
  519.         if MarkEHandles > 0 then begin
  520.           {See if current handle matches one stored by MARK}
  521.           O := 1;
  522.           while (MarkEmsHandles^[O].Handle <> NHandle) and (O <= MarkEHandles) do
  523.             Inc(O);
  524.           {If not, deallocate the current handle}
  525.           if (O > MarkEHandles) then
  526.             if not FreeEms(NHandle) then
  527.               EmsError;
  528.         end else
  529.           {No handles stored by MARK, deallocate all current handles}
  530.           if not FreeEms(NHandle) then
  531.             EmsError;
  532.       end;
  533.     end;
  534.   end;
  535.  
  536.   procedure GetOptions;
  537.     {-Analyze command line for options}
  538.   var
  539.     i : Word;
  540.     Status : Word;
  541.     arg : String[127];
  542.  
  543.     procedure WriteCopyright;
  544.     begin
  545.       WriteLn('RELEASE ', Version, ', Copyright 1991 TurboPower Software');
  546.     end;
  547.  
  548.     procedure WriteHelp;
  549.       {-Show the options}
  550.     begin
  551.       WriteCopyright;
  552.       WriteLn;
  553.       WriteLn('RELEASE removes memory-resident programs from memory and restores the');
  554.       WriteLn('interrupt vectors to their state as found prior to the installation of a MARK.');
  555.       WriteLn('RELEASE manages both normal DOS memory and also Lotus/Intel Expanded Memory.');
  556.       WriteLn('If WATCH has been installed, RELEASE will update the WATCH data area for the');
  557.       WriteLn('TSRs released.');
  558.       WriteLn;
  559.       WriteLn('RELEASE accepts the following command line syntax:');
  560.       WriteLn;
  561.       WriteLn('  RELEASE [MarkName] [Options]');
  562.       WriteLn;
  563.       WriteLn('Options may be preceded by either / or -. Valid options are as follows:');
  564.       WriteLn;
  565.       WriteLn('  /E         do NOT access EMS memory.');
  566.       WriteLn('  /K         release memory, but keep the mark in place.');
  567.       WriteLn('  /Q         write no screen output.');
  568.       WriteLn('  /S chars   stuff string (<16 chars) into keyboard buffer on exit.');
  569.       WriteLn('  /U         consider upper memory blocks for release (DOS 5).');
  570.       WriteLn('  /?         write this help screen.');
  571.       WriteLn;
  572.       WriteLn('When /U is requested, a MarkName must always be specified.');
  573.       Halt(1);
  574.     end;
  575.  
  576.   begin
  577.     {Initialize defaults}
  578.     markName := '';
  579.     Keys := '';
  580.     ReturnCode := 0;
  581.     TrappedBytes := 00;
  582.  
  583.     KeepMark := False;
  584.     Quiet := False;
  585.     DealWithEMS := True;
  586.     ShowHiMem := False;
  587.  
  588.     i := 1;
  589.     while i <= ParamCount do begin
  590.       arg := ParamStr(i);
  591.       if (arg[1] = '?') then
  592.         WriteHelp
  593.       else if (arg[1] = '-') or (arg[1] = '/') then
  594.         case Length(Arg) of
  595.           1 : Abort('Missing command option following '+arg);
  596.           2 : case UpCase(arg[2]) of
  597.                 '?' : WriteHelp;
  598.                 'E' : DealWithEMS := False;
  599.                 'K' : KeepMark := True;
  600.                 'Q' : Quiet := True;
  601.                 'S' : begin
  602.                         if I >= ParamCount then
  603.                           Abort('Key string missing');
  604.                         inc(I);
  605.                         Arg := ParamStr(I);
  606.                         if Length(Arg) > 15 then
  607.                           Abort('No more than 15 keys may be stuffed');
  608.                         Keys := Arg+^M;
  609.                       end;
  610.                 'U' : ShowHiMem := True;
  611.               else
  612.                 Abort('Unknown command option: '+arg);
  613.               end;
  614.         else
  615.           Abort('Unknown command option: '+arg);
  616.         end
  617.       else
  618.         {Named mark}
  619.         markName := stupcase(arg);
  620.       inc(i);
  621.     end;
  622.  
  623.     if not Quiet then
  624.       WriteCopyright;
  625.  
  626.     {Initialize for high memory access}
  627.     if HiMemAvailable(DosV) then begin
  628.       UmbLinkStatus := GetUmbLinkStatus;
  629.       Status := SetUmbLinkStatus(ShowHiMem);
  630.       if ShowHiMem and (Status = 1) then
  631.         Abort('To access upper memory you must have DOS=[HIGH,]UMB in CONFIG.SYS');
  632.     end else
  633.       ShowHiMem := False;
  634.     SaveExit := ExitProc;
  635.     ExitProc := @SafeExit;
  636.  
  637.     if ShowHiMem then
  638.       if MarkName = '' then
  639.         Abort('Upper memory releases must refer to named marks');
  640.   end;
  641.  
  642. begin
  643.   {Analyze command line for options}
  644.   GetOptions;
  645.  
  646.   {Get all allocated memory blocks in normal memory}
  647.   FindTheBlocks(Blocks, BlockMax, StartMcb, CommandSeg);
  648.  
  649.   {Find the last one marked with the MARK idstring, and MarkName if specified}
  650.   if not FindMark(markName, MarkID, MarkOffset, MemMark, FilMark, markBlock) then
  651.     Abort('No matching marker found, or protected marker encountered.');
  652.  
  653.   {Get file mark information into memory}
  654.   if FilMark then
  655.     ReadMarkFile(markName)
  656.   else
  657.     InitMarkInfo;
  658.  
  659.   {Mark those blocks to be released}
  660.   MarkBlocks(markBlock);
  661.  
  662.   {Copy the vector table from the MARK copy}
  663.   CopyVectors(markBlock);
  664.  
  665.   {Close open file handles}
  666.   CloseHandles;
  667.  
  668.   {Release normal memory marked for release}
  669.   ReleaseMem;
  670.  
  671.   {Deal with expanded memory}
  672.   if DealWithEMS then
  673.     if EMSpresent then
  674.       RestoreEMSmap;
  675.  
  676.   {Write success message}
  677.   if not Quiet then begin
  678.     Write('Memory released after MARK');
  679.     if markName <> '' then
  680.       Write(' (', markName, ')');
  681.     WriteLn;
  682.     if ReturnCode <> 0 then
  683.       WriteLn(TrappedBytes, ' bytes temporarily trapped until batch file completes');
  684.   end;
  685.  
  686.   {Stuff keyboard buffer if requested}
  687.   if Length(Keys) > 0 then
  688.     StuffKeys(Keys, True);
  689.  
  690.   NoRestoreHalt(ReturnCode);
  691. end.
  692.