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