home *** CD-ROM | disk | FTP | other *** search
/ Media Share 9 / MEDIASHARE_09.ISO / tsr / tsrsrc33.zip / RELNET.PAS < prev    next >
Pascal/Delphi Source File  |  1992-01-08  |  44KB  |  1,360 lines

  1. {**************************************************************************
  2. *   RELNET - releases memory above the last MARKNET 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 2.7 3/4/89                                                    *
  7. *     first public release                                                *
  8. *     (based on RELEASE 2.6)                                              *
  9. *   Version 2.8 3/10/89                                                   *
  10. *     restore the DOS environment                                         *
  11. *     restore the async ports                                             *
  12. *   Version 2.9 5/4/89                                                    *
  13. *     ignore file marks                                                   *
  14. *   Version 3.0 9/25/91                                                   *
  15. *     make compatible with DOS 5                                          *
  16. *     handle NetWare IPX better, allowing release of NETBIOS TSR          *
  17. *     add Quiet option                                                    *
  18. *     update for new WATCH behavior                                       *
  19. *     restore BIOS LPT port data areas                                    *
  20. *     restore XMS allocation                                              *
  21. *     add code for tracking high memory                                   *
  22. *   Version 3.1 11/4/91                                                   *
  23. *     restore less of DOS variables table (more deactivates high memory   *
  24. *       after a release)                                                  *
  25. *     add option to disable IPX socket shutdown                           *
  26. *   Version 3.2 11/22/91                                                  *
  27. *     version 3.1 crashed under DOS 3.3 (RestoreDosTable)                 *
  28. *     change method of accessing high memory                              *
  29. *     reverse order in which memory blocks are released to work           *
  30. *       correctly with the 386MAX high memory manager                     *
  31. *     merge blocks in high memory after release (QEMM doesn't)            *
  32. *   Version 3.3 1/8/92                                                    *
  33. *     add /H to use high memory optionally                                *
  34. *     new features for parsing and getting command line options           *
  35. ***************************************************************************
  36. *   Telephone: 719-260-6641, CompuServe: 76004,2611.                      *
  37. *   Requires Turbo Pascal 6 to compile.                                   *
  38. ***************************************************************************}
  39.  
  40. {$R-,S-,I-,V-,B-,F-,A-,E-,N-,G-,X-}
  41. {$M 16384,0,655360}
  42. {.$DEFINE Debug}
  43.  
  44. program RelNet;
  45.  
  46. uses
  47.   Dos,
  48.   MemU,
  49.   Ipx,
  50.   Xms,
  51.   Ems;
  52.  
  53. const
  54.   MarkFOpen : Boolean = False;       {True while mark file is open}
  55.   VectorsRestored : Boolean = False; {True after old vector table restored}
  56.  
  57. var
  58.   Blocks : BlockArray;
  59.   markBlock : BlockType;
  60.   BlockMax : BlockType;
  61.   markPsp : Word;
  62.  
  63.   MarkName : PathStr;
  64.  
  65.   ReturnCode : Word;
  66.   StartMCB : Word;
  67.   HiMemSeg : Word;
  68.  
  69.   Revector8259 : Boolean;
  70.   DealWithIpx : Boolean;
  71.   DealWithEMS : Boolean;
  72.   DealWithXMS : Boolean;
  73.   KeepMark : Boolean;
  74.   RestoreEnvir : Boolean;
  75.   ResetTimer : Boolean;
  76.   RestoreComm : Boolean;
  77.   MemMark : Boolean;
  78.   FilMark : Boolean;
  79.   Verbose : Boolean;
  80.   Quiet : Boolean;
  81.   OptUseHiMem, UseHiMem : Boolean;
  82.  
  83.   Keys : string[16];
  84.  
  85.   MarkEHandles : Word;
  86.   CurrEHandles : Word;
  87.   MarkEmsHandles : PageArrayPtr;
  88.   CurrEmsHandles : PageArrayPtr;
  89.  
  90.   TrappedBytes : LongInt;
  91.  
  92.   MarkXHandles : Word;
  93.   CurrXHandles : Word;
  94.   MarkXmsHandles : XmsHandlesPtr;
  95.   CurrXmsHandles : XmsHandlesPtr;
  96.  
  97.   {Save areas read in from file mark}
  98.   Vectors : array[0..1023] of Byte;
  99.   EGAsavTable : array[0..7] of Byte;
  100.   IntComTable : array[0..15] of Byte;
  101.   ParentSeg : Word;
  102.   ParentLen : Word;
  103.   BiosPrintTable : array[0..9] of Byte;
  104.   DevA : DeviceArray;             {Temporary array of device headers}
  105.   DevCnt : Word;                  {Number of device headers}
  106.   CommandPsp : array[1..$100] of Byte; {Buffer for COMMAND.COM PSP}
  107.   DosData : array[1..$200] of Byte; {Buffer for DOS data area}
  108.   DosTableSize : Word;
  109.   DosTable : Pointer;             {Dos internal variables}
  110.   FileTableA : array[1..5] of SftRecPtr; {Points to system file table buffers}
  111.   FileTableCnt : Word;            {Number of system file table blocks}
  112.   FileRecSize : Word;             {Bytes in internal DOS file record}
  113.   PatchOfst : Word;               {Address of COMMAND.COM patch}
  114.   PatchSegm : Word;
  115.   EnvLen : Word;                  {Bytes in DOS environment}
  116.   EnvPtr : Pointer;               {Pointer to copy of DOS environment}
  117.   PicMask : Byte;                 {8259 interrupt mask}
  118.   ComData : ComArray;             {Communications data array}
  119.   McbG : McbGroup;                {Allocated Mcbs}
  120.  
  121.   TestPtr : DeviceHeaderPtr;      {Test pointer while getting started on chain}
  122.   DevicePtr : DeviceHeaderPtr;    {Pointer to the next device header}
  123.   DeviceSegment : Word;           {Current device segment}
  124.   DeviceOffset : Word;            {Current device offset}
  125.   MarkF : file;                   {Saved system information file}
  126.   DosPtr : ^DosRec;               {Pointer to internal DOS variable table}
  127.   CommandSeg : Word;              {Segment of primary COMMAND.COM}
  128.  
  129.   procedure NoRestoreHalt(ReturnCode : Word);
  130.     {-Replace Turbo halt with one that doesn't restore any interrupts}
  131.   begin
  132.     if VectorsRestored then begin
  133.       Close(Output);
  134.       asm
  135.         mov ah,$4C
  136.         mov al,byte(ReturnCode)
  137.         int $21
  138.       end;
  139.     end else
  140.       System.Halt(ReturnCode);
  141.   end;
  142.  
  143.   procedure RemoveMarkFile;
  144.     {-Close and remove the mark file}
  145.   begin
  146.     Close(MarkF);
  147.     if IoResult = 0 then
  148.       if not KeepMark then begin
  149.         Erase(MarkF);
  150.         if IoResult = 0 then ;
  151.       end;
  152.     MarkFOpen := False;
  153.   end;
  154.  
  155.   procedure Abort(Msg : String);
  156.     {-Halt in case of error}
  157.   begin
  158.     if MarkFOpen then
  159.       RemoveMarkFile;
  160.     WriteLn(Msg);
  161.     Halt(255);
  162.   end;
  163.  
  164.   function FindMark(MarkName, MarkID : String;
  165.                     MarkOffset : Word;
  166.                     var MemMark, FilMark : Boolean;
  167.                     var B : BlockType) : Boolean;
  168.     {-Find the last memory block matching idstring at offset idoffset}
  169.   var
  170.     BPsp : Word;
  171.  
  172.     function HasIDstring(Segment : Word;
  173.                          IdString : String;
  174.                          IdOffset : Word) : Boolean;
  175.       {-Return true if idstring is found at segment:idoffset}
  176.     var
  177.       Tstring : String;
  178.       Len : Byte;
  179.     begin
  180.       Len := Length(IdString);
  181.       Tstring[0] := Chr(Len);
  182.       Move(Mem[Segment:IdOffset], Tstring[1], Len);
  183.       HasIDstring := (Tstring = IdString);
  184.     end;
  185.  
  186.     function GetMarkName(Segment : Word) : String;
  187.       {-Return a cleaned up mark name from the segment's PSP}
  188.     var
  189.       Tstring : String;
  190.       Tlen : Byte absolute Tstring;
  191.     begin
  192.       Move(Mem[Segment:$80], Tstring[0], 128);
  193.       while (Tlen > 0) and ((Tstring[1] = ' ') or (Tstring[1] = ^I)) do
  194.         Delete(Tstring, 1, 1);
  195.       while (Tlen > 0) and ((Tstring[Tlen] = ' ') or (Tstring[Tlen] = ^I)) do
  196.         Dec(Tlen);
  197.       GetMarkName := StUpcase(Tstring);
  198.     end;
  199.  
  200.     function MatchMemMark(Segment : Word;
  201.                           MarkName : String;
  202.                           var B : BlockType) : Boolean;
  203.       {-Return true if MemMark is unnamed or matches current name}
  204.     var
  205.       FoundIt : Boolean;
  206.       Tstring : String;
  207.     begin
  208.       {Check the mark name stored in the PSP of the mark block}
  209.       Tstring := GetMarkName(Segment);
  210.       FoundIt := (Tstring = MarkName);
  211.       if not FoundIt then begin
  212.         if (Tstring <> '') and (Tstring[1] = ProtectChar) then
  213.           {Current mark is protected, stop searching}
  214.           B := 1;
  215.         Dec(B);
  216.       end;
  217.       MatchMemMark := FoundIt;
  218.     end;
  219.  
  220.     function MatchFilMark(Segment : Word;
  221.                           MarkName : String;
  222.                           var B : BlockType) : Boolean;
  223.       {-Return true if FilMark is unnamed or matches current name}
  224.     var
  225.       FoundIt : Boolean;
  226.     begin
  227.       {Check the mark name stored in the PSP of the mark block}
  228.       FoundIt := (GetMarkName(Segment) = MarkName);
  229.       if FoundIt then begin
  230.         {Assure named file exists}
  231.         if Verbose then
  232.           WriteLn('Finding mark file ', MarkName);
  233.         FoundIt := ExistFile(MarkName);
  234.       end;
  235.       if not FoundIt then
  236.         {Net marks are protected marks; stop checking if non-match found}
  237.         B := 0;
  238.       MatchFilMark := FoundIt;
  239.     end;
  240.  
  241.     function MatchExactFilMark(Segment : Word;
  242.                                MarkName : String;
  243.                                var B : BlockType) : Boolean;
  244.       {-Return true if FilMark matches current name}
  245.     var
  246.       FoundIt : Boolean;
  247.     begin
  248.       {Check the mark name stored in the PSP of the mark block}
  249.       FoundIt := (GetMarkName(Segment) = MarkName);
  250.       if FoundIt then begin
  251.         {Assure named file exists}
  252.         if Verbose then
  253.           WriteLn('Finding mark file ', MarkName);
  254.         FoundIt := ExistFile(MarkName);
  255.       end;
  256.       if not FoundIt then
  257.         dec(B);
  258.       MatchExactFilMark := FoundIt;
  259.     end;
  260.  
  261.   begin
  262.     B := BlockMax;
  263.     MemMark := False;
  264.     FilMark := False;
  265.     if UseHiMem then begin
  266.       {Scan for an exact match to the specified net mark}
  267.       repeat
  268.         BPsp := Blocks[B].Psp;
  269.         if (Blocks[B].Mcb+1 <> BPsp) or (BPsp = PrefixSeg) then
  270.           {Don't match any non-program block or this program}
  271.           Dec(B)
  272.         else if HasIDstring(BPsp, NmarkID, NmarkOffset) then
  273.           {A net mark}
  274.           FilMark := MatchExactFilMark(BPsp, MarkName, B)
  275.         else
  276.           {Not a net mark}
  277.           Dec(B);
  278.       until (B < 1) or FilMark;
  279.  
  280.     end else begin
  281.       {Scan from the last block down to find the last MARK TSR}
  282.       repeat
  283.         BPsp := Blocks[B].Psp;
  284.         if (Blocks[B].Mcb+1 <> BPsp) or (BPsp = PrefixSeg) then
  285.           {Don't match any non-program block or this program}
  286.           Dec(B)
  287.         else if HasIDstring(BPsp, MarkID, MarkOffset) then
  288.           {An in-memory mark}
  289.           MemMark := MatchMemMark(BPsp, MarkName, B)
  290.         else if HasIDstring(BPsp, NmarkID, NmarkOffset) then
  291.           {A net mark}
  292.           FilMark := MatchFilMark(BPsp, MarkName, B)
  293.         else
  294.           {Ignore normal file marks}
  295.           {Not a mark}
  296.           Dec(B);
  297.       until (B < 1) or MemMark or FilMark;
  298.     end;
  299.     FindMark := MemMark or FilMark;
  300.   end;
  301.  
  302.   procedure CheckReadError;
  303.     {-Check previous I/O operation}
  304.   begin
  305.     if IoResult = 0 then
  306.       Exit;
  307.     Abort('Error reading '+MarkName);
  308.   end;
  309.  
  310.   function PhysicalAddress(P : Pointer) : LongInt;
  311.   begin
  312.     PhysicalAddress := LongInt(OS(P).S) shl 4+OS(P).O;
  313.   end;
  314.  
  315.   procedure ValidateMarkFile;
  316.     {-Open mark file and assure it's valid}
  317.   type
  318.     IDArray = array[1..4] of Char;
  319.   var
  320.     ID : IDArray;
  321.     ExpectedID : IDArray;
  322.   begin
  323.     Assign(MarkF, MarkName);
  324.     Reset(MarkF, 1);
  325.     if IoResult <> 0 then
  326.       Abort('Mark file '+MarkName+' not found');
  327.     MarkFOpen := True;
  328.  
  329.     {Check the ID at the start of the file}
  330.     ExpectedID := NetMarkID;
  331.     BlockRead(MarkF, ID, SizeOf(IDArray));
  332.     CheckReadError;
  333.     if ID <> ExpectedID then
  334.       Abort(MarkName+' is not a valid net mark file');
  335.  
  336.     {Read the NUL device address}
  337.     BlockRead(MarkF, TestPtr, SizeOf(Pointer));
  338.     CheckReadError;
  339.     if PhysicalAddress(TestPtr) <> PhysicalAddress(DevicePtr) then begin
  340.       if Verbose then
  341.         WriteLn('Old NUL addr:', HexPtr(TestPtr),
  342.                 '   Current NUL addr:', HexPtr(DevicePtr));
  343.       Abort('Unexpected error. NUL device moved');
  344.     end;
  345.   end;
  346.  
  347.   procedure BufferFileTable;
  348.     {-Read the file table from the mark file into memory}
  349.   type
  350.     SftRecStub =
  351.       record
  352.         Next : SftRecPtr;
  353.         Count : Word;
  354.       end;
  355.   var
  356.     I : Word;
  357.     Size : Word;
  358.     P : Pointer;
  359.     S : SftRecStub;
  360.   begin
  361.     BlockRead(MarkF, FileTableCnt, SizeOf(Word));
  362.     for I := 1 to FileTableCnt do begin
  363.       BlockRead(MarkF, S, SizeOf(SftRecStub));
  364.       Size := 6+S.Count*FileRecSize;
  365.       GetMem(FileTableA[I], Size);
  366.       P := FileTableA[I];
  367.       Move(S, P^, SizeOf(SftRecStub));
  368.       Inc(OS(P).O, SizeOf(SftRecStub));
  369.       BlockRead(MarkF, P^, Size-SizeOf(SftRecStub));
  370.     end;
  371.     CheckReadError;
  372.   end;
  373.  
  374.   procedure ReadReg(var B : Byte);
  375.     {-Read a communications register from the mark file}
  376.   begin
  377.     BlockRead(MarkF, B, SizeOf(Byte));
  378.     CheckReadError;
  379.   end;
  380.  
  381.   procedure ReadMarkFile;
  382.     {-Read the mark file info into memory}
  383.   var
  384.     DevPtr : DeviceHeaderPtr;
  385.     Com : Byte;
  386.   begin
  387.     {Read the vector table from the mark file, into a temporary memory area}
  388.     BlockRead(MarkF, Vectors, 1024);
  389.     CheckReadError;
  390.  
  391.     {Read the BIOS miscellaneous save areas into temporary tables}
  392.     BlockRead(MarkF, EGAsavTable, 8);
  393.     BlockRead(MarkF, IntComTable, 16);
  394.     BlockRead(MarkF, ParentSeg, 2);
  395.     BlockRead(MarkF, ParentLen, 2);
  396.     BlockRead(MarkF, BiosPrintTable, 10);
  397.     CheckReadError;
  398.  
  399.     {Read the stored EMS handles, if any}
  400.     BlockRead(MarkF, MarkEHandles, SizeOf(Word));
  401.     GetMem(MarkEmsHandles, SizeOf(HandlePageRecord)*MarkEHandles);
  402.     BlockRead(MarkF, MarkEmsHandles^, SizeOf(HandlePageRecord)*MarkEHandles);
  403.     CheckReadError;
  404.  
  405.     {Read the stored XMS handles, if any}
  406.     BlockRead(MarkF, MarkXHandles, SizeOf(Word));
  407.     GetMem(MarkXmsHandles, SizeOf(XmsHandleRecord)*MarkXHandles);
  408.     BlockRead(MarkF, MarkXmsHandles^, SizeOf(XmsHandleRecord)*MarkXHandles);
  409.     CheckReadError;
  410.  
  411.     {Read the device driver chain}
  412.     DevPtr := DevicePtr;
  413.     DevCnt := 0;
  414.     while OS(DevPtr).O <> $FFFF do begin
  415.       Inc(DevCnt);
  416.       GetMem(DevA[DevCnt], SizeOf(DeviceHeader));
  417.       BlockRead(MarkF, DevA[DevCnt]^, SizeOf(DeviceHeader));
  418.       CheckReadError;
  419.       with DevA[DevCnt]^ do
  420.         DevPtr := Ptr(NextHeaderSegment, NextHeaderOffset);
  421.     end;
  422.  
  423.     {Read the DOS data area table}
  424.     BlockRead(MarkF, DosData, $200);
  425.     CheckReadError;
  426.  
  427.     {Read the DOS internal variables table}
  428.     BlockRead(MarkF, DosTableSize, SizeOf(Word));
  429.     if DosTableSize <> 0 then begin
  430.       GetMem(DosTable, DosTableSize);
  431.       BlockRead(MarkF, DosTable^, DosTableSize);
  432.     end;
  433.     CheckReadError;
  434.  
  435.     {Read the internal file table}
  436.     BufferFileTable;
  437.  
  438.     {Read in the copy of COMMAND.COM's PSP}
  439.     BlockRead(MarkF, CommandPsp, $100);
  440.     CheckReadError;
  441.  
  442.     {Read in the address used for COMMAND.COM patching by NetWare}
  443.     BlockRead(MarkF, PatchOfst, SizeOf(Word));
  444.     BlockRead(MarkF, PatchSegm, SizeOf(Word));
  445.     CheckReadError;
  446.  
  447.     {Read in the DOS master environment}
  448.     BlockRead(MarkF, EnvLen, SizeOf(Word));
  449.     GetMem(EnvPtr, EnvLen);
  450.     BlockRead(MarkF, EnvPtr^, EnvLen);
  451.     CheckReadError;
  452.  
  453.     {Read in the communications data area}
  454.     BlockRead(MarkF, PicMask, SizeOf(Byte));
  455.     CheckReadError;
  456.     for Com := 1 to 2 do
  457.       with ComData[Com] do begin
  458.         BlockRead(MarkF, Base, SizeOf(Word));
  459.         CheckReadError;
  460.         if Base <> 0 then begin
  461.           ReadReg(IERReg);
  462.           ReadReg(LCRReg);
  463.           ReadReg(MCRReg);
  464.           ReadReg(BRLReg);
  465.           ReadReg(BRHreg);
  466.         end;
  467.       end;
  468.  
  469.     {Read in the allocated Mcb chain}
  470.     BlockRead(MarkF, McbG.Count, SizeOf(Word));
  471.     BlockRead(MarkF, McbG.Mcbs, 2*SizeOf(Word)*McbG.Count);
  472.     CheckReadError;
  473.  
  474.     {Close and possibly erase mark file}
  475.     RemoveMarkFile;
  476.   end;
  477.  
  478.   procedure RestoreCommState;
  479.     {-Restore the communications chips to their previous state}
  480.   var
  481.     Com : Byte;
  482.   begin
  483.     for Com := 1 to 2 do
  484.       with ComData[Com] do
  485.         if Base <> 0 then begin
  486.           Port[Base+IER] := IERReg; {Interrupt enable register}
  487.           NullJump;
  488.           Port[Base+MCR] := MCRReg; {Modem control register}
  489.           NullJump;
  490.           Port[Base+LCR] := LCRReg or $80; {Enable baud rate divisor registers}
  491.           NullJump;
  492.           Port[Base+BRL] := BRLReg; {Baud rate low}
  493.           NullJump;
  494.           Port[Base+BRH] := BRHReg; {Baud rate high}
  495.           NullJump;
  496.           Port[Base+LCR] := LCRReg; {Line control register}
  497.           NullJump;
  498.         end;
  499.     {Restore the interrupt mask}
  500.     Port[$21] := PicMask;
  501.   end;
  502.  
  503.   procedure CopyVectors;
  504.     {-Put interrupt vectors back into table}
  505.  
  506.     procedure Reset8259;
  507.       {-Reset the 8259 interrupt controller to its powerup state}
  508.       {-Interrupts assumed OFF prior to calling this routine}
  509.  
  510.       function ATmachine : Boolean;
  511.         {-Return true if machine is AT class}
  512.       var
  513.         MachType : Byte absolute $FFFF : $000E;
  514.       begin
  515.         case MachType of
  516.           $F8, $FC : ATmachine := True;
  517.         else
  518.           ATmachine := False;
  519.         end;
  520.       end;
  521.  
  522.       procedure Reset8259PC;
  523.         {-Reset the 8259 on a PC class machine}
  524.       begin
  525.         inline(
  526.           $E4/$21/                { in      al,$21}
  527.           $88/$C4/                { mov     ah,al}
  528.           $B0/$13/                { mov     al,$13}
  529.           $E6/$20/                { out     $20,al}
  530.           $B0/$08/                { mov     al,8}
  531.           $E6/$21/                { out     $21,al}
  532.           $B0/$09/                { mov     al,9}
  533.           $E6/$21/                { out     $21,al}
  534.           $88/$E0/                { mov     al,ah}
  535.           $E6/$21                 { out     $21,al}
  536.           );
  537.       end;
  538.  
  539.       procedure Reset8259AT;
  540.         {-Reset the 8259 interrupt controllers on an AT machine}
  541.       begin
  542.         inline(
  543.           $32/$C0/                { xor       al,al }
  544.           $E6/$F1/                { out       0f1h,al         ; Switch off an 80287 if necessary}
  545.           {Set up master 8259 }
  546.           $E4/$21/                { in        al,21h          ; Get current interrupt mask }
  547.           $8A/$E0/                { mov       ah,al           ; save it }
  548.           $B0/$11/                { mov       al,11h }
  549.           $E6/$20/                { out       20h,al }
  550.           $EB/$00/                { jmp       short $+2 }
  551.           $B0/$08/                { mov       al,8            ; Set up main interrupt vector number}
  552.           $E6/$21/                { out       21h,al }
  553.           $EB/$00/                { jmp       short $+2 }
  554.           $B0/$04/                { mov       al,4 }
  555.           $E6/$21/                { out       21h,al }
  556.           $EB/$00/                { jmp       short $+2 }
  557.           $B0/$01/                { mov       al,1 }
  558.           $E6/$21/                { out       21h,al }
  559.           $EB/$00/                { jmp       short $+2 }
  560.           $8A/$C4/                { mov       al,ah }
  561.           $E6/$21/                { out       21h,al }
  562.           {Set up slave 8259 }
  563.           $E4/$A1/                { in        al,0a1h         ; Get current interrupt mask }
  564.           $8A/$E0/                { mov       ah,al           ; save it }
  565.           $B0/$11/                { mov       al,11h }
  566.           $E6/$A0/                { out       0a0h,al }
  567.           $EB/$00/                { jmp       short $+2 }
  568.           $B0/$70/                { mov       al,70h }
  569.           $E6/$A1/                { out       0a1h,al }
  570.           $B0/$02/                { mov       al,2 }
  571.           $EB/$00/                { jmp       short $+2 }
  572.           $E6/$A1/                { out       0a1h,al }
  573.           $EB/$00/                { jmp       short $+2 }
  574.           $B0/$01/                { mov       al,1 }
  575.           $E6/$A1/                { out       0a1h,al }
  576.           $EB/$00/                { jmp       short $+2 }
  577.           $8A/$C4/                { mov       al,ah           ; Reset previous interrupt state }
  578.           $E6/$A1                 { out       0a1h,al }
  579.           );
  580.       end;
  581.  
  582.     begin
  583.       if ATmachine then
  584.         Reset8259AT
  585.       else
  586.         Reset8259PC;
  587.     end;
  588.  
  589.   begin
  590.     {Interrupts off}
  591.     IntsOff;
  592.  
  593.     {Reset 8259 if requested}
  594.     if Revector8259 then
  595.       Reset8259;
  596.  
  597.     {Reset the communications state if requested}
  598.     if RestoreComm then
  599.       RestoreCommState;
  600.  
  601.     {Restore the main interrupt vector table}
  602.     Move(Vectors, Mem[0:0], 1024);
  603.  
  604.     {Interrupts on}
  605.     IntsOn;
  606.  
  607.     {Flag that we don't want system restoring vectors for us}
  608.     VectorsRestored := True;
  609.  
  610.     Move(EGAsavTable, Mem[$40:$A8], 8); {EGA table}
  611.     Move(IntComTable, Mem[$40:$F0], 16); {Interapplications communication area}
  612.     {$IFDEF Debug}
  613.     writeln('Parent address: ', HexW(ParentSeg), ' Length: ', ParentLen);
  614.     {$ENDIF}
  615.     if ValidPsp(HiMemSeg, ParentSeg, ParentLen) then
  616.       {Don't restore parent if it no longer exists (applies to QEMM LOADHI)}
  617.       MemW[PrefixSeg:$16] := ParentSeg;
  618.     Move(BiosPrintTable, Mem[$40:$08], 10); {BIOS Printer Table}
  619.     if not UseHiMem then
  620.       {Programs loaded into high memory have strange termination addresses}
  621.       Move(Mem[0:$88], Mem[PrefixSeg:$0A], 12); {Int 22,23,24 addresses}
  622.   end;
  623.  
  624.   procedure MarkBlocks(markBlock : BlockType);
  625.     {-Mark those blocks to be released}
  626.  
  627.     procedure BatchWarning(B : BlockType);
  628.       {-Warn about the trapping effect of batch files}
  629.     var
  630.       T : BlockType;
  631.     begin
  632.       ReturnCode := 1;
  633.       {Accumulate number of bytes temporarily trapped}
  634.       for T := 1 to B do
  635.         if Blocks[T].ReleaseIt then
  636.           Inc(TrappedBytes, LongInt(MemW[Blocks[T].Mcb:3]) shl 4);
  637.     end;
  638.  
  639.     procedure MarkBlocksAbove;
  640.       {-Mark blocks above the mark}
  641.     var
  642.       b : BlockType;
  643.     begin
  644.       for b := 1 to BlockMax do
  645.         with Blocks[b] do
  646.           if (b >= markBlock) and (psp = CommandSeg) then begin
  647.             {Don't release blocks owned by master COMMAND.COM}
  648.             releaseIt := False;
  649.             BatchWarning(b);
  650.           end else if KeepMark then
  651.             {Release all but RELEASE and the mark}
  652.             releaseIt := (psp <> PrefixSeg) and (psp > markPsp)
  653.           else
  654.             releaseIt := (psp <> PrefixSeg) and (psp >= markPsp);
  655.     end;
  656.  
  657.     procedure MarkUnallocatedBlocks;
  658.       {-Mark blocks that weren't allocated at time of mark}
  659.     var
  660.       TopSeg : Word;
  661.       b : BlockType;
  662.       m : BlockType;
  663.       Found : Boolean;
  664.     begin
  665.       {Find last low memory mcb}
  666.       TopSeg := TopOfMemSeg-1;
  667.       m := 1;
  668.       Found := False;
  669.       while (not Found) and (m <= McbG.Count) do
  670.         if McbG.Mcbs[m].mcb >= TopSeg then
  671.           Found := True
  672.         else
  673.           inc(m);
  674.  
  675.       {Mark out all mcbs associated with psp of last low memory mcb}
  676.       TopSeg := McbG.Mcbs[m-1].psp;
  677.       if TopSeg <> markPsp then
  678.         for m := 1 to McbG.Count do
  679.           with McbG.Mcbs[m] do
  680.             if psp = TopSeg then
  681.               psp := 0;
  682.  
  683.       for b := 1 to BlockMax do
  684.         with Blocks[b] do begin
  685.           Found := False;
  686.           m := 1;
  687.           while (not Found) and (m <= McbG.Count) do begin
  688.             Found := (McbG.Mcbs[m].psp <> 0) and (McbG.Mcbs[m].mcb = mcb);
  689.             inc(m);
  690.           end;
  691.           if Found then
  692.             {was allocated at time of mark, keep it now unless a mark to be released}
  693.             releaseIt := not KeepMark and (psp = markPsp)
  694.           else if psp = CommandSeg then
  695.             {Don't release blocks owned by master COMMAND.COM}
  696.             releaseIt := False
  697.           else
  698.             {not allocated at time of mark}
  699.             releaseIt := (psp <> 0) and (psp <> PrefixSeg);
  700.         end;
  701.     end;
  702.  
  703.   begin
  704.     if UseHiMem then
  705.       MarkUnallocatedBlocks
  706.     else
  707.       MarkBlocksAbove;
  708.  
  709.     {$IFDEF Debug}
  710.     for b := 1 to BlockMax do
  711.       with Blocks[b] do
  712.         WriteLn(b:3, ' ', HexW(psp), ' ', HexW(mcb), ' ', releaseIt);
  713.     {$ENDIF}
  714.   end;
  715.  
  716.   function ReleaseBlock(Segm : Word) : Word; assembler;
  717.     {-Use DOS services to release memory block}
  718.   asm
  719.     mov ah,$49
  720.     mov es,Segm
  721.     int $21
  722.     jc  @Done
  723.     xor ax,ax
  724. @Done:
  725.   end;
  726.  
  727.   procedure ReleaseMem;
  728.     {-Release DOS memory marked for release}
  729.   var
  730.     b : BlockType;
  731.   begin
  732.     if Verbose then begin
  733.       WriteLn('Releasing DOS memory');
  734.       {$IFDEF Debug}
  735.       ReadLn;
  736.       {$ENDIF}
  737.     end;
  738.     for b := BlockMax downto 1 do
  739.       with blocks[b] do
  740.         if releaseIt then
  741.           if ReleaseBlock(mcb+1) <> 0 then begin
  742.             WriteLn('Could not release block at segment ', HexW(mcb+1));
  743.             Abort('Memory may be a mess... Please reboot');
  744.           end;
  745.     if Verbose then begin
  746.       WriteLn('Merging free blocks in high memory');
  747.       {$IFDEF Debug}
  748.       ReadLn;
  749.       {$ENDIF}
  750.     end;
  751.     MergeHiMemBlocks(HiMemSeg);
  752.   end;
  753.  
  754.   procedure RestoreEMSmap;
  755.     {-Restore EMS to state at time of mark}
  756.   var
  757.     O, N, NHandle : Word;
  758.  
  759.     procedure EmsError;
  760.     begin
  761.       WriteLn('Program error or EMS device not responding');
  762.       Abort('EMS memory may be a mess... Please reboot');
  763.     end;
  764.  
  765.   begin
  766.     {Get the existing EMS page map}
  767.     GetMem(CurrEmsHandles, MaxHandles*SizeOf(HandlePageRecord));
  768.     CurrEHandles := EmsHandles(CurrEmsHandles^);
  769.     if CurrEHandles > MaxHandles then
  770.       WriteLn('EMS handle count exceeds capacity of RELNET -- no action taken')
  771.     else if CurrEHandles <> 0 then begin
  772.       {See how many handles were active when MARK was installed}
  773.       if Verbose then begin
  774.         WriteLn('Releasing EMS memory allocated since MARK');
  775.         {$IFDEF Debug}
  776.         ReadLn;
  777.         {$ENDIF}
  778.       end;
  779.       {Compare the two maps and deallocate pages not in the stored map}
  780.       for N := 1 to CurrEHandles do begin
  781.         {Scan all current handles}
  782.         NHandle := CurrEmsHandles^[N].Handle;
  783.         if MarkEHandles > 0 then begin
  784.           {See if current handle matches one stored by MARK}
  785.           O := 1;
  786.           while (MarkEmsHandles^[O].Handle <> NHandle) and (O <= MarkEHandles) do
  787.             Inc(O);
  788.           {If not, deallocate the current handle}
  789.           if (O > MarkEHandles) then
  790.             if not FreeEms(NHandle) then
  791.               EmsError;
  792.         end else
  793.           {No handles stored by MARK, deallocate all current handles}
  794.           if not FreeEms(NHandle) then
  795.             EmsError;
  796.       end;
  797.     end;
  798.   end;
  799.  
  800.   procedure RestoreXmsmap;
  801.     {-Restore Xms to state at time of mark}
  802.   var
  803.     O, N, NHandle : Word;
  804.  
  805.     procedure XmsError;
  806.     begin
  807.       WriteLn('Program error or XMS device not responding');
  808.       Abort('XMS memory may be a mess... Please reboot');
  809.     end;
  810.  
  811.   begin
  812.     CurrXHandles := GetXmsHandles(CurrXmsHandles);
  813.     if CurrXHandles <> 0 then begin
  814.       {See how many handles were active when MARK was installed}
  815.       if Verbose then begin
  816.         WriteLn('Releasing XMS memory allocated since MARK');
  817.         {$IFDEF Debug}
  818.         ReadLn;
  819.         {$ENDIF}
  820.       end;
  821.       if MarkXHandles = 0 then begin
  822.         {Release all current XMS Handles}
  823.         for N := 1 to CurrXHandles do
  824.           if FreeExtMem(CurrXmsHandles^[N].Handle) <> 0 then
  825.             XmsError;
  826.       end else begin
  827.         {Compare the two maps and deallocate pages not in the stored map}
  828.         for N := 1 to CurrXHandles do begin
  829.           {Scan all current handles}
  830.           NHandle := CurrXmsHandles^[N].Handle;
  831.           {See if current handle matches one stored by MARK}
  832.           O := 1;
  833.           while (MarkXmsHandles^[O].Handle <> NHandle) and (O <= MarkXHandles) do
  834.             Inc(O);
  835.           {If not, deallocate the current handle}
  836.           if (O > MarkXHandles) then
  837.             if FreeExtMem(NHandle) <> 0 then
  838.               XmsError;
  839.         end;
  840.       end;
  841.     end;
  842.   end;
  843.  
  844.   procedure GetOptions;
  845.     {-Analyze command line for options}
  846.  
  847.     procedure WriteCopyright;
  848.     begin
  849.       WriteLn('RELNET ', Version, ', Copyright 1991 TurboPower Software');
  850.     end;
  851.  
  852.     procedure WriteHelp;
  853.       {-Show the options}
  854.     begin
  855.       WriteCopyright;
  856.       WriteLn;
  857.       WriteLn('RELNET removes memory-resident programs from memory, particularly network');
  858.       WriteLn('shells like Novell''s NetWare, although it will also release normal memory');
  859.       WriteLn('resident programs. In combination with MARKNET it thoroughly restores the');
  860.       WriteLn('system to its state at the time MARKNET was called.');
  861.       WriteLn;
  862.       WriteLn('RELNET accepts the following command line syntax:');
  863.       WriteLn;
  864.       WriteLn('  RELNET NetMarkFile [Options]');
  865.       WriteLn;
  866.       WriteLn('Options may be preceded by either / or -. Valid options are:');
  867.       WriteLn;
  868.       WriteLn('  /C         do NOT restore communications state.');
  869.       WriteLn('  /E         do NOT access EMS memory.');
  870.       WriteLn('  /H         work with upper memory if available.');
  871.       WriteLn('  /I         do NOT shut down IPX events and sockets.');
  872.       WriteLn('  /K         release memory, but keep the mark in place.');
  873.       WriteLn('  /P         do NOT restore DOS environment.');
  874.       WriteLn('  /Q         write no screen output.');
  875.       WriteLn('  /R         revector 8259 interrupt controller to powerup state.');
  876.       WriteLn('  /S chars   stuff string (<16 chars) into keyboard buffer on exit.');
  877.       WriteLn('  /T         do NOT reset system timer chip to default rate.');
  878.       WriteLn('  /U         work with upper memory, but halt if none found.');
  879.       WriteLn('  /V         verbose: show each step of the restore.');
  880.       WriteLn('  /X         do NOT access XMS memory.');
  881.       WriteLn('  /?         write this help screen.');
  882.       Halt(1);
  883.     end;
  884.  
  885.     procedure GetArgs(S : String);
  886.     var
  887.       SPos : Word;
  888.       Arg : String[127];
  889.     begin
  890.       SPos := 1;
  891.       repeat
  892.         Arg := NextArg(S, SPos);
  893.         if Arg = '' then
  894.           Exit;
  895.         if Arg[1] = '?' then
  896.           WriteHelp
  897.         else if (Arg[1] = '-') or (Arg[1] = '/') then
  898.           case Length(Arg) of
  899.             1 : Abort('Missing command option following '+Arg);
  900.             2 : case Upcase(Arg[2]) of
  901.                   'C' : RestoreComm := False;
  902.                   'E' : DealWithEMS := False;
  903.                   'H' : OptUseHiMem := True;
  904.                   'I' : DealWithIPX := False;
  905.                   'K' : KeepMark := True;
  906.                   'P' : RestoreEnvir := False;
  907.                   'Q' : Quiet := True;
  908.                   'R' : Revector8259 := True;
  909.                   'S' : begin
  910.                           Arg := NextArg(S, SPos);
  911.                           if Length(Arg) = 0 then
  912.                             Abort('Key string missing');
  913.                           if Length(Arg) > 15 then
  914.                             Abort('No more than 15 keys may be stuffed');
  915.                           Keys := Arg+^M;
  916.                         end;
  917.                   'T' : ResetTimer := False;
  918.                   'U' : UseHiMem := True;
  919.                   'V' : Verbose := True;
  920.                   'X' : DealWithXMS := False;
  921.                   '?' : WriteHelp;
  922.                 else
  923.                   Abort('Unknown command option: '+Arg);
  924.                 end;
  925.           else
  926.             Abort('Unknown command option: '+Arg);
  927.           end
  928.         else if Length(MarkName) = 0 then
  929.           {Mark file}
  930.           MarkName := StUpcase(Arg)
  931.         else
  932.           Abort('Too many mark files specified');
  933.       until False;
  934.     end;
  935.  
  936.   begin
  937.     {Initialize defaults}
  938.     MarkName := '';
  939.     Keys := '';
  940.  
  941.     Revector8259 := False;
  942.     KeepMark := False;
  943.     DealWithIPX := True;
  944.     DealWithEMS := True;
  945.     DealWithXMS := True;
  946.     ResetTimer := True;
  947.     Verbose := False;
  948.     Quiet := False;
  949.     RestoreEnvir := True;
  950.     RestoreComm := True;
  951.     UseHiMem := False;
  952.     OptUseHiMem := False;
  953.  
  954.     ReturnCode := 0;
  955.     TrappedBytes := 00;
  956.  
  957.     {Get arguments from the command line and the environment}
  958.     GetArgs(StringPtr(Ptr(PrefixSeg, $80))^);
  959.     GetArgs(GetEnv('RELNET'));
  960.  
  961.     if Length(MarkName) = 0 then begin
  962.       WriteLn('No mark file specified');
  963.       WriteHelp;
  964.     end;
  965.     if Verbose then
  966.       Quiet := False;
  967.     if not Quiet then
  968.       WriteCopyright;
  969.  
  970.     {Initialize for high memory access}
  971.     if OptUseHiMem or UseHiMem then begin
  972.       HiMemSeg := FindHiMemStart;
  973.       if HiMemSeg = 0 then begin
  974.         if UseHiMem then
  975.           Abort('No upper memory blocks found');
  976.       end else
  977.         UseHiMem := True;
  978.     end else
  979.       HiMemSeg := 0;
  980.   end;
  981.  
  982.   function MemoryRelease(P : Pointer) : Boolean;
  983.     {-Return True if address P is in a block to be released}
  984.   var
  985.     B : BlockType;
  986.     PL : LongInt;
  987.     PSPL : LongInt;
  988.   begin
  989.     PL := PhysicalAddress(P);
  990.     for B := 1 to BlockMax do
  991.       with Blocks[B] do
  992.         if ReleaseIt then begin
  993.           PSPL := LongInt(Psp) shl 4;
  994.           if (PL >= PSPL) and (PL < PSPL+LongInt(MemW[Mcb:3]) shl 4) then begin
  995.             MemoryRelease := True;
  996.             Exit;
  997.           end;
  998.         end;
  999.     MemoryRelease := False;
  1000.   end;
  1001.  
  1002.   procedure CloseIpxSockets;
  1003.   const
  1004.     Retf : Byte = $CB; {Return instruction}
  1005.   var
  1006.     This, Next : IpxEcbPtr;
  1007.     Ecb : IpxEcb;
  1008.     Status : Byte;
  1009.   begin
  1010.     {Create a new Ecb to find start of linked list of Ecb's}
  1011.     FillChar(Ecb, SizeOf(IpxEcb), 0);
  1012.     Ecb.EsrAddress := @RetF;
  1013.     ScheduleSpecialEvent(182, Ecb);
  1014.  
  1015.     {Scan the list of Ecb's}
  1016.     This := Ecb.Link;
  1017.     while This <> nil do begin
  1018.       if Verbose then
  1019.         Write('Ecb: ', HexPtr(This),
  1020.               ' Esr: ', HexPtr(This^.EsrAddress),
  1021.               ' InUse: ', HexW(This^.InUse),
  1022.               ' Socket: ', HexW(This^.SocketNumber));
  1023.       Next := This^.Link;
  1024.       if MemoryRelease(This) or MemoryRelease(This^.ESRAddress) then
  1025.         {Memory of this Ecb will be released}
  1026.         if This^.InUse <> 0 then begin
  1027.           {This Ecb is in use}
  1028.           Status := CancelEvent(This^);
  1029.           if Verbose then
  1030.             Write(' [cancelled]');
  1031.           if This^.SocketNumber <> 0 then begin
  1032.             CloseSocket(This^.SocketNumber);
  1033.             if Verbose then
  1034.               Write(' [closed]');
  1035.           end;
  1036.         end;
  1037.       if Verbose then
  1038.         Writeln;
  1039.       This := Next;
  1040.     end;
  1041.  
  1042.     {Cancel the special event we started}
  1043.     Status := CancelEvent(Ecb);
  1044.   end;
  1045.  
  1046.   procedure FindDevChain;
  1047.     {-Return segment, offset and pointer to NUL device}
  1048.   begin
  1049.     DosPtr := Ptr(OS(DosList).S, OS(DosList).O-2);
  1050.     DevicePtr := @DosPtr^.NullDevice;
  1051.     DeviceSegment := OS(DevicePtr).S;
  1052.     DeviceOffset := OS(DevicePtr).O;
  1053.   end;
  1054.  
  1055.   procedure RestoreDosTable;
  1056.     {-Restore the DOS variables table, except for the buffer pointer}
  1057.   type
  1058.     ByteArray = array[0..32767] of Byte;
  1059.     ByteArrayPtr = ^ByteArray;
  1060.   var
  1061.     DosBase : Pointer;
  1062.     SPtr : Pointer;
  1063.     DPtr : Pointer;
  1064.   begin
  1065.     if Verbose then begin
  1066.       WriteLn('Restoring DOS data area at 0050:0000');
  1067.       {$IFDEF Debug}
  1068.       ReadLn;
  1069.       {$ENDIF}
  1070.     end;
  1071.     DPtr := Ptr($50, 0);
  1072.     Move(DosData, DPtr^, $200);
  1073.  
  1074.     DosBase := Ptr(OS(DosPtr).S, 0);
  1075.     if Verbose then begin
  1076.       WriteLn('Restoring ', DosTableSize,
  1077.               ' bytes of DOS variables table at ', HexPtr(DosBase));
  1078.       {$IFDEF Debug}
  1079.       ReadLn;
  1080.       {$ENDIF}
  1081.     end;
  1082.  
  1083.     {patch up DosTable to reflect current items that must be maintained}
  1084.     {CachePtr}
  1085.     SPtr := @DosPtr^.CachePtr;
  1086.     DPtr := @ByteArrayPtr(DosTable)^[Ofs(DosPtr^.CachePtr)];
  1087.     {$IFDEF Debug}
  1088.     writeln('cacheptr ', hexptr(sptr), '->', hexptr(dptr), ' ', SizeOf(Pointer));
  1089.     {$ENDIF}
  1090.  
  1091.     move(SPtr^, DPtr^, SizeOf(Pointer));
  1092.     if DosV = 5 then begin
  1093.       {Other unknown areas}
  1094.       SPtr := Ptr(OS(DosPtr).S, OS(DosPtr).O+SizeOf(DosRec));
  1095.       DPtr := @ByteArrayPtr(DosTable)^[OS(DosPtr).O+SizeOf(DosRec)];
  1096.       {$IFDEF Debug}
  1097.       writeln('unknown  ', hexptr(sptr), '->', hexptr(dptr), ' ',
  1098.               OS(DosPtr^.FirstSFT).O-OS(DosPtr).O-SizeOf(DosRec)-$3C);
  1099.       {$ENDIF}
  1100.       move(SPtr^, DPtr^, OS(DosPtr^.FirstSFT).O-OS(DosPtr).O-SizeOf(DosRec)-$3C);
  1101.     end;
  1102.  
  1103.     {Restore DOS table}
  1104.     move(DosTable^, DosBase^, DosTableSize);
  1105.   end;
  1106.  
  1107.   procedure RestoreFileTable;
  1108.     {-Copy the internal file table from our memory buffer to its DOS location}
  1109.   var
  1110.     S : SftRecPtr;
  1111.     I : Word;
  1112.   begin
  1113.     S := DosPtr^.FirstSFT;
  1114.     if Verbose then begin
  1115.       WriteLn('Restoring DOS file table at ', HexPtr(S));
  1116.       {$IFDEF Debug}
  1117.       ReadLn;
  1118.       {$ENDIF}
  1119.     end;
  1120.     for I := 1 to FileTableCnt do begin
  1121.       Move(FileTableA[I]^, S^, 6+FileTableA[I]^.Count*FileRecSize);
  1122.       S := S^.Next;
  1123.     end;
  1124.   end;
  1125.  
  1126.   procedure RestoreDeviceDrivers;
  1127.     {-Restore the device driver chain to its original state}
  1128.   var
  1129.     D : Word;
  1130.     DevPtr : DeviceHeaderPtr;
  1131.   begin
  1132.     if Verbose then begin
  1133.       WriteLn('Restoring device driver chain');
  1134.       {$IFDEF Debug}
  1135.       ReadLn;
  1136.       {$ENDIF}
  1137.     end;
  1138.     DevPtr := DevicePtr;
  1139.     for D := 1 to DevCnt do begin
  1140.       DevPtr^ := DevA[D]^;
  1141.       with DevA[D]^ do
  1142.         DevPtr := Ptr(NextHeaderSegment, NextHeaderOffset);
  1143.     end;
  1144.   end;
  1145.  
  1146.   procedure RestoreCommandPSP;
  1147.     {-Copy COMMAND.COM's PSP back into place}
  1148.   var
  1149.     PspPtr : Pointer;
  1150.   begin
  1151.     PspPtr := Ptr(CommandSeg, 0);
  1152.     if Verbose then begin
  1153.       WriteLn('Restoring COMMAND.COM PSP at ', HexPtr(PspPtr));
  1154.       {$IFDEF Debug}
  1155.       ReadLn;
  1156.       {$ENDIF}
  1157.     end;
  1158.     Move(CommandPsp, PspPtr^, $100);
  1159.   end;
  1160.  
  1161.   procedure RestoreCommandPatch;
  1162.     {-Restore the patch that NetWare applies to COMMAND.COM}
  1163.   begin
  1164.     if (PatchSegm <> 0) or (PatchOfst <> 0) then
  1165.       if (Mem[PatchSegm:PatchOfst+$01] <> Byte('/')) or
  1166.       (Mem[PatchSegm:PatchOfst+$11] <> Byte('/')) then begin
  1167.         if Verbose then begin
  1168.           WriteLn('Removing patch at ', HexW(PatchSegm), ':', HexW(PatchOfst));
  1169.           {$IFDEF Debug}
  1170.           ReadLn;
  1171.           {$ENDIF}
  1172.         end;
  1173.         Mem[PatchSegm:PatchOfst+$01] := Byte('/');
  1174.         Mem[PatchSegm:PatchOfst+$11] := Byte('/');
  1175.       end;
  1176.   end;
  1177.  
  1178.   procedure FindEnv(CommandSeg : Word; var EnvSeg, EnvLen : Word);
  1179.     {-Return the segment and length of the master environment}
  1180.   var
  1181.     Mcb : Word;
  1182.   begin
  1183.     Mcb := CommandSeg-1;
  1184.     EnvSeg := MemW[CommandSeg:$2C];
  1185.     if EnvSeg = 0 then
  1186.       {Master environment is next block past COMMAND}
  1187.       EnvSeg := Commandseg+MemW[Mcb:3]+1;
  1188.     EnvLen := MemW[(EnvSeg-1):3] shl 4;
  1189.   end;
  1190.  
  1191.   procedure RestoreDosEnvironment;
  1192.     {-Restore the master copy of the DOS environment}
  1193.   var
  1194.     EnvSeg : Word;
  1195.     CurLen : Word;
  1196.     P : Pointer;
  1197.   begin
  1198.     if RestoreEnvir then begin
  1199.       FindEnv(CommandSeg, EnvSeg, CurLen);
  1200.       if CurLen <> EnvLen then
  1201.         Abort('Environment length changed');
  1202.       if Verbose then begin
  1203.         WriteLn('Restoring DOS environment, ', EnvLen, ' bytes at ', HexW(EnvSeg), ':0000');
  1204.         {$IFDEF Debug}
  1205.         ReadLn;
  1206.         {$ENDIF}
  1207.       end;
  1208.       P := Ptr(EnvSeg, 0);
  1209.       move(EnvPtr^, P^, EnvLen);
  1210.     end;
  1211.   end;
  1212.  
  1213.   procedure SetTimerRate(Rate : Word);
  1214.     {-Program system 8253 timer number 0 to run at specified rate}
  1215.   begin
  1216.     IntsOff;
  1217.     Port[$43] := $36;
  1218.     NullJump;
  1219.     Port[$40] := Lo(Rate);
  1220.     NullJump;
  1221.     Port[$40] := Hi(Rate);
  1222.     IntsOn;
  1223.   end;
  1224.  
  1225.   procedure RestoreTimer;
  1226.     {-Set the system timer to its normal rate}
  1227.   begin
  1228.     if Verbose then begin
  1229.       WriteLn('Restoring system timer to normal rate');
  1230.       {$IFDEF Debug}
  1231.       ReadLn;
  1232.       {$ENDIF}
  1233.     end;
  1234.     SetTimerRate(0);
  1235.   end;
  1236.  
  1237.   function CompaqDOS30 : Boolean; assembler;
  1238.     {-Return true if Compaq DOS 3.0}
  1239.   asm
  1240.     mov ah,$34
  1241.     int $21
  1242.     cmp bx,$019C
  1243.     mov al,1
  1244.     jz @Done
  1245.     dec al
  1246. @Done:
  1247.   end;
  1248.  
  1249.   procedure ValidateDosVersion;
  1250.     {-Assure supported version of DOS and compute size of DOS internal filerec}
  1251.   var
  1252.     DosVer : Word;
  1253.   begin
  1254.     DosVer := DosVersion;
  1255.     case Hi(DosVer) of
  1256.       3 : if (Hi(DosVer) < $0A) and not CompaqDOS30 then
  1257.             {IBM DOS 3.0}
  1258.             FileRecSize := 56
  1259.           else
  1260.             {DOS 3.1+ or Compaq DOS 3.0}
  1261.             FileRecSize := 53;
  1262.       4, 5 : FileRecSize := 59;
  1263.     else
  1264.       Abort('Requires DOS 3, 4, or 5');
  1265.     end;
  1266.   end;
  1267.  
  1268. begin
  1269.   {Assure supported version of DOS}
  1270.   ValidateDosVersion;
  1271.  
  1272.   {Analyze command line for options}
  1273.   GetOptions;
  1274.  
  1275.   {Find the start of the device driver chain via the NUL device}
  1276.   FindDevChain;
  1277.  
  1278.   {Get all allocated memory blocks in normal memory}
  1279.   FindTheBlocks(HiMemSeg, Blocks, BlockMax, StartMcb, CommandSeg);
  1280.  
  1281.   {Find the block marked with the MARK idstring, and MarkName if specified}
  1282.   if not(FindMark(MarkName, MarkID, MarkOffset, MemMark, FilMark, markBlock)) then
  1283.     Abort('No matching marker found, or protected marker encountered.');
  1284.   if MemMark then
  1285.     Abort('Marker must have been placed by MARKNET');
  1286.   markPsp := Blocks[markBlock].psp;
  1287.  
  1288.   {Open and validate the mark file}
  1289.   ValidateMarkFile;
  1290.  
  1291.   {Close IPX sockets and cancel IPX ECBs}
  1292.   if DealWithIpx then
  1293.     if IpxInstalled then
  1294.       CloseIpxSockets;
  1295.  
  1296.   {Get file mark information into memory}
  1297.   ReadMarkFile;
  1298.  
  1299.   {Mark those blocks to be released}
  1300.   MarkBlocks(markBlock);
  1301.  
  1302.   {Copy the vector table from the MARK copy}
  1303.   CopyVectors;
  1304.  
  1305.   {Restore the device driver chain}
  1306.   RestoreDeviceDrivers;
  1307.  
  1308.   {Restore the COMMAND.COM patch possibly made by NetWare}
  1309.   RestoreCommandPatch;
  1310.  
  1311.   {Restore the DOS variables table}
  1312.   RestoreDosTable;
  1313.  
  1314.   {Restore the DOS file table}
  1315.   RestoreFileTable;
  1316.  
  1317.   {Restore the COMMAND.COM PSP}
  1318.   RestoreCommandPSP;
  1319.  
  1320.   {Restore the master DOS environment}
  1321.   RestoreDosEnvironment;
  1322.  
  1323.   {Set the timer to normal rate}
  1324.   if ResetTimer then
  1325.     RestoreTimer;
  1326.  
  1327. (*
  1328.   this isn't necessary, and in fact is harmful, when the DOS file table
  1329.   is being restored above.
  1330.   {Close open file handles}
  1331.   CloseHandles;
  1332. *)
  1333.  
  1334.   {Release normal memory}
  1335.   ReleaseMem;
  1336.  
  1337.   {Deal with expanded memory}
  1338.   if DealWithEMS then
  1339.     if EMSpresent then
  1340.       RestoreEMSmap;
  1341.  
  1342.   {Deal with extended memory}
  1343.   if DealWithXMS then
  1344.     if XMSInstalled then
  1345.       RestoreXMSMap;
  1346.  
  1347.   {Write success message}
  1348.   if not Quiet then
  1349.     WriteLn('Memory released after ', StUpcase(MarkName));
  1350.  
  1351.   if (ReturnCode <> 0) and Verbose then
  1352.     WriteLn(TrappedBytes, ' bytes temporarily trapped until batch file completes');
  1353.  
  1354.   {Stuff keyboard buffer if requested}
  1355.   if Length(Keys) > 0 then
  1356.     StuffKeys(Keys, True);
  1357.  
  1358.   NoRestoreHalt(ReturnCode);
  1359. end.
  1360.