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