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