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