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