home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PASCAL / TSRUTILS.ZIP / RELNET.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1989-05-04  |  44.2 KB  |  1,462 lines

  1. {**************************************************************************
  2. *   RELNET - releases memory above the last MARKNET call made.            *
  3. *   Copyright (c) 1986,1989 Kim Kokkonen, TurboPower Software.            *
  4. *   May be distributed freely, but not for a profit except with written   *
  5. *   permission from TurboPower Software.                                  *
  6. ***************************************************************************
  7. *   Version 2.7 3/4/89                                                    *
  8. *     first public release                                                *
  9. *     (based on RELEASE 2.6)                                              *
  10. *   Version 2.8 3/10/89                                                   *
  11. *     restore the DOS environment                                         *
  12. *     restore the async ports                                             *
  13. *   Version 2.9 5/4/89                                                    *
  14. *     ignore file marks                                                   *
  15. ***************************************************************************
  16. *   Telephone: 408-438-8608, CompuServe: 72457,2131.                      *
  17. *   Requires Turbo version 5 to compile.                                  *
  18. ***************************************************************************}
  19.  
  20. {$R-,S-,I-}
  21.  
  22. program ReleaseNet;
  23.  
  24. uses
  25.   {$IFDEF ReleaseSocket}
  26.   NetWare,                        {This unit is part of TurboPower's
  27.                                    commercial package B-Tree Filer.
  28.                                    It isn't really needed here.}
  29.   {$ENDIF}
  30.   Dos;
  31.  
  32. const
  33.   Version = '2.9';
  34.   NmarkID = 'MN2.9 TSR';          {Marking string for TSR file mark}
  35.   NetMarkID = 'MN29';             {ID at start of net mark file}
  36.   MarkID = 'M2.9 PARAMETER BLOCK FOLLOWS'; {Marking string for TSR MARK,
  37.                                             just for WATCH here}
  38.   FmarkID = 'FM2.9 TSR';          {Marking string for TSR file mark}
  39.  
  40.   ProtectChar = '!';              {Marks whose name begins with this will be
  41.                                    released ONLY if an exact name match occurs}
  42.   MaxBlocks = 128;                {Max number of DOS allocation blocks supported}
  43.   MaxHandles = 32;                {Max number of EMS allocation blocks supported}
  44.   EMSinterrupt = $67;             {The vector used by the expanded memory manager}
  45.  
  46.   {Offsets into resident copy of MARK.COM for data storage}
  47.   MarkOffset = $103;              {Where markID is found in MARK TSR}
  48.   NmarkOffset = $60;              {Where NmarkID is found in FMARK TSR}
  49.   FmarkOffset = $60;              {Where FmarkID is found in FMARK TSR}
  50.  
  51.   WatchID = 'TSR WATCHER';        {Marking string for WATCH}
  52.  
  53.   {Offsets into resident copy of WATCH.COM for data storage}
  54.   WatchOffset = $81;
  55.   NextChange = $104;
  56.   ChangeVectors = $220;
  57.   OrigVectors = $620;
  58.   CurrVectors = $A20;
  59.   MaxChanges = 128;               {Maximum number of vector deltas in WATCH}
  60.  
  61.   MarkFOpen : Boolean = False;    {True while mark file is open}
  62.   VectorsRestored : Boolean = False; {True after old vector table restored}
  63.  
  64.   Digits : array[0..$F] of Char = '0123456789ABCDEF';
  65.  
  66.   RBR = 0; {Receiver buffer register offset}
  67.   THR = 0; {Transmitter buffer register offset}
  68.   BRL = 0; {Baud rate low}
  69.   BRH = 1; {Baud rate high}
  70.   IER = 1; {Interrupt enable register}
  71.   IIR = 2; {Interrupt identification register}
  72.   LCR = 3; {Line control register}
  73.   MCR = 4; {Modem control register}
  74.   LSR = 5; {Line status register}
  75.   MSR = 6; {Modem status register}
  76.  
  77. type
  78.   HandlePageRecord =
  79.     record
  80.       Handle : Word;
  81.       NumPages : Word;
  82.     end;
  83.  
  84.   PageArray = array[1..MaxHandles] of HandlePageRecord;
  85.   PageArrayPtr = ^PageArray;
  86.  
  87.   Block =
  88.     record                        {Store info about each memory block}
  89.       Mcb : Word;
  90.       Psp : Word;
  91.       ReleaseIt : Boolean;
  92.     end;
  93.  
  94.   BlockType = 0..MaxBlocks;
  95.   BlockArray = array[BlockType] of Block;
  96.  
  97.   {Structure of a device driver header}
  98.   DeviceHeader =
  99.     record
  100.       NextHeaderOffset : Word;    {Offset address of next device in chain}
  101.       NextHeaderSegment : Word;   {Segment address of next device in chain}
  102.       Attributes : Word;          {Device attributes}
  103.       StrategyEntPt : Word;       {Offset in current segment - strategy}
  104.       InterruptEntPt : Word;      {Offset in current segment - interrupt}
  105.       DeviceName : array[1..8] of Char; {Name of the device}
  106.     end;
  107.   DeviceHeaderPtr = ^DeviceHeader;
  108.   DeviceArray = array[1..256] of DeviceHeaderPtr;
  109.  
  110.   SO =
  111.     record
  112.       O, S : Word;
  113.     end;
  114.  
  115.   FileRec =
  116.     record
  117.       OpenCnt : Word;
  118.       OpenMode : Word;
  119.       Attribute : Byte;
  120.       Unknown1 : Word;
  121.       DCB : Pointer;
  122.       InitCluster : Word;
  123.       Time : Word;
  124.       Date : Word;
  125.       Size : LongInt;
  126.       Pos : LongInt;
  127.       BeginCluster : Word;
  128.       CurCluster : Word;
  129.       Block : Word;
  130.       Unknown2 : Byte;            {Varies with DOS version beyond here}
  131.       Name : array[0..7] of Char;
  132.       Ext : array[0..2] of Char;
  133.       Unknown3 : array[0..5] of Byte;
  134.       Owner : Word;
  135.       Unknown4 : Word;
  136.     end;
  137.  
  138.   SftRecPtr = ^SftRec;
  139.   SftRec =
  140.     record
  141.       Next : SftRecPtr;
  142.       Count : Word;
  143.       Files : array[1..255] of FileRec;
  144.     end;
  145.  
  146.   DosRec =
  147.     record
  148.       McbSeg : Word;
  149.       FirstDPB : Pointer;
  150.       FirstSFT : SftRecPtr;
  151.       ClockDriver : Pointer;
  152.       ConDriver : Pointer;
  153.       MaxBlockBytes : Word;
  154.       CachePtr : Pointer;
  155.       DriveTable : Pointer;
  156.       Unknown2 : Pointer;
  157.       Unknown3 : Word;
  158.       BlockDevices : Byte;
  159.       LastDrive : Byte;
  160.       NullDevice : DeviceHeader;
  161.     end;
  162.  
  163.   ComRec =  {State of the communications system}
  164.     record
  165.       Base : Word;
  166.       IERReg : Byte;
  167.       LCRReg : Byte;
  168.       MCRReg : Byte;
  169.       BRLReg : Byte;
  170.       BRHReg : Byte;
  171.     end;
  172.   ComArray = array[1..2] of ComRec;
  173.  
  174. var
  175.   Blocks : BlockArray;
  176.   WatchBlock : BlockType;
  177.   BottomBlock : BlockType;
  178.   BlockNum : BlockType;
  179.  
  180.   MarkName : String[79];
  181.  
  182.   Regs : Registers;
  183.  
  184.   ReturnCode : Word;
  185.   StartMCB : Word;
  186.   StoredHandles : Word;
  187.   EMShandles : Word;
  188.  
  189.   UseWatch : Boolean;
  190.   Revector8259 : Boolean;
  191.   DealWithEMS : Boolean;
  192.   KeepMark : Boolean;
  193.   RestoreEnvir : Boolean;
  194.   ResetTimer : Boolean;
  195.   RestoreComm : Boolean;
  196.   MemMark : Boolean;
  197.   FilMark : Boolean;
  198.   Junk : Boolean;
  199.   Verbose : Boolean;
  200.   Keys : string[16];
  201.  
  202.   FilMarkPageMap : PageArrayPtr;
  203.   Map : PageArrayPtr;
  204.   StoredMap : PageArrayPtr;
  205.   TrappedBytes : LongInt;
  206.  
  207.   {Save areas read in from file mark}
  208.   Vectors : array[0..1023] of Byte;
  209.   EGAsavTable : array[0..7] of Byte;
  210.   IntComTable : array[0..15] of Byte;
  211.   ParentTable : array[0..1] of Byte;
  212.   FilMarkHandles : Word;
  213.   DevA : DeviceArray;             {Temporary array of device headers}
  214.   DevCnt : Word;                  {Number of device headers}
  215.   CommandPsp : array[1..$100] of Byte; {Buffer for COMMAND.COM PSP}
  216.   DosData : array[1..$200] of Byte; {Buffer for DOS data area}
  217.   DosTableSize : Word;
  218.   DosTable : Pointer;             {Dos internal variables}
  219.   FileTableA : array[1..5] of SftRecPtr; {Points to system file table buffers}
  220.   FileTableCnt : Word;            {Number of system file table blocks}
  221.   FileRecSize : Word;             {Bytes in internal DOS file record}
  222.   PatchOfst : Word;               {Address of COMMAND.COM patch}
  223.   PatchSegm : Word;
  224.   EnvLen : Word;                  {Bytes in DOS environment}
  225.   EnvPtr : Pointer;               {Pointer to copy of DOS environment}
  226.   PicMask : Byte;                 {8259 interrupt mask}
  227.   ComData : ComArray;             {Communications data array}
  228.  
  229.   TestPtr : DeviceHeaderPtr;      {Test pointer while getting started on chain}
  230.   DevicePtr : DeviceHeaderPtr;    {Pointer to the next device header}
  231.   DeviceSegment : Word;           {Current device segment}
  232.   DeviceOffset : Word;            {Current device offset}
  233.   MarkF : file;                   {Saved system information file}
  234.   DosPtr : ^DosRec;               {Pointer to internal DOS variable table}
  235.   CommandSeg : Word;              {Segment of primary COMMAND.COM}
  236.  
  237.   procedure Halt(ReturnCode : Word);
  238.     {-Replace Turbo halt with one that doesn't restore any interrupts}
  239.   begin
  240.     if VectorsRestored then begin
  241.       Close(Output);
  242.       with Regs do begin
  243.         AH := $4C;
  244.         AL := Lo(ReturnCode);
  245.         MsDos(Regs);
  246.       end;
  247.     end else
  248.       System.Halt(ReturnCode);
  249.   end;
  250.  
  251.   procedure RemoveMarkFile;
  252.     {-Close and remove the mark file}
  253.   begin
  254.     Close(MarkF);
  255.     if IoResult = 0 then
  256.       if not KeepMark then begin
  257.         Erase(MarkF);
  258.         if IoResult = 0 then ;
  259.       end;
  260.     MarkFOpen := False;
  261.   end;
  262.  
  263.   procedure Abort(Msg : String);
  264.     {-Halt in case of error}
  265.   begin
  266.     if MarkFOpen then
  267.       RemoveMarkFile;
  268.     WriteLn(Msg);
  269.     Halt(255);
  270.   end;
  271.  
  272.   function HexW(W : Word) : string;
  273.     {-Return hex string for word}
  274.   begin
  275.     HexW[0] := #4;
  276.     HexW[1] := Digits[hi(W) shr 4];
  277.     HexW[2] := Digits[hi(W) and $F];
  278.     HexW[3] := Digits[lo(W) shr 4];
  279.     HexW[4] := Digits[lo(W) and $F];
  280.   end;
  281.  
  282.   function HexPtr(P : Pointer) : string;
  283.     {-Return hex string for pointer}
  284.   begin
  285.     HexPtr := HexW(SO(P).S)+':'+HexW(SO(P).O);
  286.   end;
  287.  
  288.   function StUpcase(S : String) : string;
  289.     {-Return uppercase for string}
  290.   var
  291.     I : Integer;
  292.   begin
  293.     for I := 1 to Length(S) do
  294.       S[I] := Upcase(S[I]);
  295.     StUpcase := S;
  296.   end;
  297.  
  298.   procedure FindTheBlocks;
  299.     {-Scan memory for the allocated memory blocks}
  300.   const
  301.     MidBlockID = $4D;             {Byte DOS uses to identify part of MCB chain}
  302.     EndBlockID = $5A;             {Byte DOS uses to identify last block of MCB chain}
  303.   var
  304.     McbSeg : Word;                {Segment address of current MCB}
  305.     NextSeg : Word;               {Computed segment address for the next MCB}
  306.     GotFirst : Boolean;           {True after first MCB is found}
  307.     GotLast : Boolean;            {True after last MCB is found}
  308.     IdByte : Byte;                {Byte that DOS uses to identify an MCB}
  309.  
  310.     procedure StoreTheBlock(var McbSeg, NextSeg : Word;
  311.                             var GotFirst, GotLast : Boolean);
  312.       {-Store information regarding the memory block}
  313.     var
  314.       NextID : Byte;
  315.       PspAdd : Word;              {Segment address of the current PSP}
  316.       McbLen : Word;              {Size of the current memory block in paragraphs}
  317.     begin
  318.       McbLen := MemW[McbSeg:3];   {Size of the MCB in paragraphs}
  319.       NextSeg := Succ(McbSeg+McbLen); {Where the next MCB should be}
  320.       PspAdd := MemW[McbSeg:1];   {Address of program segment prefix for MCB}
  321.       NextID := Mem[NextSeg:0];
  322.  
  323.       if GotLast or (NextID = EndBlockID) or (NextID = MidBlockID) then begin
  324.         Inc(BlockNum);
  325.         GotFirst := True;
  326.         with Blocks[BlockNum] do begin
  327.           Mcb := McbSeg;
  328.           Psp := PspAdd;
  329.         end;
  330.       end;
  331.     end;
  332.  
  333.   begin
  334.     StartMCB := DosPtr^.McbSeg;
  335.     McbSeg := StartMCB;
  336.     GotFirst := False;
  337.     GotLast := False;
  338.     BlockNum := 0;
  339.  
  340.     {Scan all memory until the last block is found}
  341.     repeat
  342.       IdByte := Mem[McbSeg:0];
  343.       if IdByte = MidBlockID then begin
  344.         StoreTheBlock(McbSeg, NextSeg, GotFirst, GotLast);
  345.         if GotFirst then
  346.           McbSeg := NextSeg
  347.         else
  348.           Inc(McbSeg);
  349.       end else if GotFirst and (IdByte = EndBlockID) then begin
  350.         GotLast := True;
  351.         StoreTheBlock(McbSeg, NextSeg, GotFirst, GotLast);
  352.       end else
  353.         {Start block was invalid}
  354.         Abort('Corrupted allocation chain or program error....');
  355.     until GotLast;
  356.   end;
  357.  
  358.   function FindMark(MarkName, MarkID : String;
  359.                     MarkOffset : Word;
  360.                     var MemMark, FilMark : Boolean;
  361.                     var B : BlockType) : Boolean;
  362.     {-Find the last memory block matching idstring at offset idoffset}
  363.  
  364.     function HasIDstring(Segment : Word;
  365.                          IdString : String;
  366.                          IdOffset : Word) : Boolean;
  367.       {-Return true if idstring is found at segment:idoffset}
  368.     var
  369.       Tstring : String;
  370.       Len : Byte;
  371.     begin
  372.       Len := Length(IdString);
  373.       Tstring[0] := Chr(Len);
  374.       Move(Mem[Segment:IdOffset], Tstring[1], Len);
  375.       HasIDstring := (Tstring = IdString);
  376.     end;
  377.  
  378.     function GetMarkName(Segment : Word) : String;
  379.       {-Return a cleaned up mark name from the segment's PSP}
  380.     var
  381.       Tstring : String;
  382.       Tlen : Byte absolute Tstring;
  383.     begin
  384.       Move(Mem[Segment:$80], Tstring[0], 128);
  385.       while (Tlen > 0) and ((Tstring[1] = ' ') or (Tstring[1] = ^I)) do
  386.         Delete(Tstring, 1, 1);
  387.       while (Tlen > 0) and ((Tstring[Tlen] = ' ') or (Tstring[Tlen] = ^I)) do
  388.         Dec(Tlen);
  389.       GetMarkName := StUpcase(Tstring);
  390.     end;
  391.  
  392.     function MatchMemMark(Segment : Word;
  393.                           MarkName : String;
  394.                           var B : BlockType) : Boolean;
  395.       {-Return true if MemMark is unnamed or matches current name}
  396.     var
  397.       Tstring : String;
  398.       FoundIt : Boolean;
  399.     begin
  400.       {Check the mark name stored in the PSP of the mark block}
  401.       Tstring := GetMarkName(Segment);
  402.       if (MarkName <> '') then begin
  403.         FoundIt := (Tstring = MarkName);
  404.         if not(FoundIt) then
  405.           if (Tstring <> '') and (Tstring[1] = ProtectChar) then
  406.             {Current mark is protected, stop searching}
  407.             B := 1;
  408.       end else if (Tstring <> '') and (Tstring[1] = ProtectChar) then begin
  409.         {Stored mark name is protected}
  410.         FoundIt := False;
  411.         {Stop checking}
  412.         B := 1;
  413.       end else
  414.         {Match any mark}
  415.         FoundIt := True;
  416.       if not(FoundIt) then
  417.         Dec(B);
  418.       MatchMemMark := FoundIt;
  419.     end;
  420.  
  421.     function MatchFilMark(Segment : Word;
  422.                           MarkName : String;
  423.                           var B : BlockType) : Boolean;
  424.       {-Return true if FilMark is unnamed or matches current name}
  425.     var
  426.       Tstring : String;
  427.       FoundIt : Boolean;
  428.  
  429.       function ExistFile(Path : String) : Boolean;
  430.         {-Return true if file exists}
  431.       var
  432.         F : file;
  433.       begin
  434.         Assign(F, Path);
  435.         Reset(F);
  436.         if IoResult = 0 then begin
  437.           ExistFile := True;
  438.           Close(F);
  439.         end else
  440.           ExistFile := False;
  441.       end;
  442.  
  443.     begin
  444.       {Check the mark name stored in the PSP of the mark block}
  445.       Tstring := GetMarkName(Segment);
  446.       if (MarkName <> '') then begin
  447.         MarkName := MarkName;
  448.         FoundIt := (Tstring = MarkName);
  449.         if FoundIt then begin
  450.           {Assure named file exists}
  451.           if Verbose then
  452.             WriteLn('Finding mark file ', MarkName);
  453.           FoundIt := ExistFile(MarkName);
  454.         end;
  455.       end else
  456.         {File marks must be named on RELEASE command line}
  457.         FoundIt := False;
  458.       if not(FoundIt) then
  459.         {Net marks are protected marks}
  460.         {Stop checking if a non-matching net mark is found}
  461.         B := 0;
  462.       MatchFilMark := FoundIt;
  463.     end;
  464.  
  465.   begin
  466.     {Scan from the last block down to find the last MARK TSR}
  467.     B := BlockNum;
  468.     MemMark := False;
  469.     FilMark := False;
  470.     repeat
  471.       if Blocks[B].Psp = PrefixSeg then
  472.         {Assure this program's command line is not matched}
  473.         Dec(B)
  474. (* ignore file marks !!!!!!!
  475.       else if HasIDstring(Blocks[b].psp, FmarkID, FmarkOffset) then
  476.         {A file mark, can't release it here. Stop looking}
  477.         b := 0
  478. *)
  479.       else if HasIDstring(Blocks[B].Psp, MarkID, MarkOffset) then
  480.         {An in-memory mark}
  481.         MemMark := MatchMemMark(Blocks[B].Psp, MarkName, B)
  482.       else if HasIDstring(Blocks[B].Psp, NmarkID, NmarkOffset) then
  483.         {A net mark}
  484.         FilMark := MatchFilMark(Blocks[B].Psp, MarkName, B)
  485.       else
  486.         {Not a mark}
  487.         Dec(B);
  488.     until (B < 1) or MemMark or FilMark;
  489.     FindMark := MemMark or FilMark;
  490.   end;
  491.  
  492.   procedure CheckReadError;
  493.     {-Check previous I/O operation}
  494.   begin
  495.     if IoResult = 0 then
  496.       Exit;
  497.     Abort('Error reading '+MarkName);
  498.   end;
  499.  
  500.   function PhysicalAddress(P : Pointer) : LongInt;
  501.   begin
  502.     PhysicalAddress := LongInt(SO(P).S) shl 4+SO(P).O;
  503.   end;
  504.  
  505.   procedure ValidateMarkFile;
  506.     {-Open mark file and assure it's valid}
  507.   type
  508.     IDArray = array[1..4] of Char;
  509.   var
  510.     ID : IDArray;
  511.     ExpectedID : IDArray;
  512.   begin
  513.     Assign(MarkF, MarkName);
  514.     Reset(MarkF, 1);
  515.     if IoResult <> 0 then
  516.       Abort('Mark file '+MarkName+' not found');
  517.     MarkFOpen := True;
  518.  
  519.     {Check the ID at the start of the file}
  520.     ExpectedID := NetMarkID;
  521.     BlockRead(MarkF, ID, SizeOf(IDArray));
  522.     CheckReadError;
  523.     if ID <> ExpectedID then
  524.       Abort(MarkName+' is not a valid net mark file');
  525.  
  526.     {Read the NUL device address}
  527.     BlockRead(MarkF, TestPtr, SizeOf(Pointer));
  528.     CheckReadError;
  529.     if PhysicalAddress(TestPtr) <> PhysicalAddress(DevicePtr) then begin
  530.       if Verbose then
  531.         WriteLn('Old NUL addr:', HexPtr(TestPtr),
  532.                 '   Current NUL addr:', HexPtr(DevicePtr));
  533.       Abort('Unexpected error. NUL device moved');
  534.     end;
  535.   end;
  536.  
  537.   procedure BufferFileTable;
  538.     {-Read the file table from the mark file into memory}
  539.   type
  540.     SftRecStub =
  541.       record
  542.         Next : SftRecPtr;
  543.         Count : Word;
  544.       end;
  545.   var
  546.     I : Word;
  547.     Size : Word;
  548.     P : Pointer;
  549.     S : SftRecStub;
  550.   begin
  551.     BlockRead(MarkF, FileTableCnt, SizeOf(Word));
  552.     for I := 1 to FileTableCnt do begin
  553.       BlockRead(MarkF, S, SizeOf(SftRecStub));
  554.       Size := 6+S.Count*FileRecSize;
  555.       GetMem(FileTableA[I], Size);
  556.       P := FileTableA[I];
  557.       Move(S, P^, SizeOf(SftRecStub));
  558.       Inc(SO(P).O, SizeOf(SftRecStub));
  559.       BlockRead(MarkF, P^, Size-SizeOf(SftRecStub));
  560.     end;
  561.     CheckReadError;
  562.   end;
  563.  
  564.   procedure ReadReg(var B : Byte);
  565.     {-Read a communications register from the mark file}
  566.   begin
  567.     BlockRead(MarkF, B, SizeOf(Byte));
  568.     CheckReadError;
  569.   end;
  570.  
  571.   procedure ReadMarkFile;
  572.     {-Read the mark file info into memory}
  573.   var
  574.     DevPtr : DeviceHeaderPtr;
  575.     Com : Byte;
  576.   begin
  577.     {Read the vector table from the mark file, into a temporary memory area}
  578.     BlockRead(MarkF, Vectors, 1024);
  579.     CheckReadError;
  580.  
  581.     {Read the BIOS miscellaneous save areas into temporary tables}
  582.     BlockRead(MarkF, EGAsavTable, 8);
  583.     BlockRead(MarkF, IntComTable, 16);
  584.     BlockRead(MarkF, ParentTable, 2);
  585.     CheckReadError;
  586.  
  587.     {Read the number of EMS handles stored}
  588.     BlockRead(MarkF, FilMarkHandles, 2);
  589.  
  590.     {Get a page map area and read the page map into it}
  591.     GetMem(FilMarkPageMap, 4*FilMarkHandles);
  592.     BlockRead(MarkF, FilMarkPageMap^, 4*FilMarkHandles);
  593.     CheckReadError;
  594.  
  595.     {Read the device driver chain}
  596.     DevPtr := DevicePtr;
  597.     DevCnt := 0;
  598.     while SO(DevPtr).O <> $FFFF do begin
  599.       Inc(DevCnt);
  600.       GetMem(DevA[DevCnt], SizeOf(DeviceHeader));
  601.       BlockRead(MarkF, DevA[DevCnt]^, SizeOf(DeviceHeader));
  602.       CheckReadError;
  603.       with DevA[DevCnt]^ do
  604.         DevPtr := Ptr(NextHeaderSegment, NextHeaderOffset);
  605.     end;
  606.  
  607.     {Read the DOS data area table}
  608.     BlockRead(MarkF, DosData, $200);
  609.     CheckReadError;
  610.  
  611.     {Read the DOS internal variables table}
  612.     BlockRead(MarkF, DosTableSize, SizeOf(Word));
  613.     if DosTableSize <> 0 then begin
  614.       GetMem(DosTable, DosTableSize);
  615.       BlockRead(MarkF, DosTable^, DosTableSize);
  616.     end;
  617.     CheckReadError;
  618.  
  619.     {Read the internal file table}
  620.     BufferFileTable;
  621.  
  622.     {Read in the copy of COMMAND.COM's PSP}
  623.     BlockRead(MarkF, CommandPsp, $100);
  624.     CheckReadError;
  625.  
  626.     {Read in the address used for COMMAND.COM patching by NetWare}
  627.     BlockRead(MarkF, PatchOfst, SizeOf(Word));
  628.     BlockRead(MarkF, PatchSegm, SizeOf(Word));
  629.     CheckReadError;
  630.  
  631.     {Read in the DOS master environment}
  632.     BlockRead(MarkF, EnvLen, SizeOf(Word));
  633.     GetMem(EnvPtr, EnvLen);
  634.     BlockRead(MarkF, EnvPtr^, EnvLen);
  635.     CheckReadError;
  636.  
  637.     {Read in the communications data area}
  638.     BlockRead(MarkF, PicMask, SizeOf(Byte));
  639.     CheckReadError;
  640.     for Com := 1 to 2 do
  641.       with ComData[Com] do begin
  642.         BlockRead(MarkF, Base, SizeOf(Word));
  643.         CheckReadError;
  644.         if Base <> 0 then begin
  645.           ReadReg(IERReg);
  646.           ReadReg(LCRReg);
  647.           ReadReg(MCRReg);
  648.           ReadReg(BRLReg);
  649.           ReadReg(BRHreg);
  650.         end;
  651.       end;
  652.  
  653.     {Close and possibly erase mark file}
  654.     RemoveMarkFile;
  655.   end;
  656.  
  657.   procedure IntsOff;
  658.     {-Turn off CPU interrupts}
  659.   inline($FA);
  660.  
  661.   procedure IntsOn;
  662.     {-Turn on CPU interrupts}
  663.   inline($FB);
  664.  
  665.   procedure NullJump;
  666.     {-Slight delay}
  667.   inline($EB/$00);
  668.  
  669.   procedure RestoreCommState;
  670.     {-Restore the communications chips to their previous state}
  671.   var
  672.     Com : Byte;
  673.   begin
  674.     for Com := 1 to 2 do
  675.       with ComData[Com] do
  676.         if Base <> 0 then begin
  677.           Port[Base+IER] := IERReg; {Interrupt enable register}
  678.           NullJump;
  679.           Port[Base+MCR] := MCRReg; {Modem control register}
  680.           NullJump;
  681.           Port[Base+LCR] := LCRReg or $80; {Enable baud rate divisor registers}
  682.           NullJump;
  683.           Port[Base+BRL] := BRLReg; {Baud rate low}
  684.           NullJump;
  685.           Port[Base+BRH] := BRHReg; {Baud rate high}
  686.           NullJump;
  687.           Port[Base+LCR] := LCRReg; {Line control register}
  688.           NullJump;
  689.         end;
  690.     {Restore the interrupt mask}
  691.     Port[$21] := PicMask;
  692.   end;
  693.  
  694.   procedure CopyVectors;
  695.     {-Put interrupt vectors back into table}
  696.  
  697.     procedure Reset8259;
  698.       {-Reset the 8259 interrupt controller to its powerup state}
  699.       {-Interrupts assumed OFF prior to calling this routine}
  700.  
  701.       function ATmachine : Boolean;
  702.         {-Return true if machine is AT class}
  703.       var
  704.         MachType : Byte absolute $FFFF : $000E;
  705.       begin
  706.         case MachType of
  707.           $F8, $FC : ATmachine := True;
  708.         else
  709.           ATmachine := False;
  710.         end;
  711.       end;
  712.  
  713.       procedure Reset8259PC;
  714.         {-Reset the 8259 on a PC class machine}
  715.       begin
  716.         inline(
  717.           $E4/$21/                { in      al,$21}
  718.           $88/$C4/                { mov     ah,al}
  719.           $B0/$13/                { mov     al,$13}
  720.           $E6/$20/                { out     $20,al}
  721.           $B0/$08/                { mov     al,8}
  722.           $E6/$21/                { out     $21,al}
  723.           $B0/$09/                { mov     al,9}
  724.           $E6/$21/                { out     $21,al}
  725.           $88/$E0/                { mov     al,ah}
  726.           $E6/$21                 { out     $21,al}
  727.           );
  728.       end;
  729.  
  730.       procedure Reset8259AT;
  731.         {-Reset the 8259 interrupt controllers on an AT machine}
  732.       begin
  733.         inline(
  734.           $32/$C0/                { xor       al,al }
  735.           $E6/$F1/                { out       0f1h,al         ; Switch off an 80287 if necessary}
  736.           {Set up master 8259 }
  737.           $E4/$21/                { in        al,21h          ; Get current interrupt mask }
  738.           $8A/$E0/                { mov       ah,al           ; save it }
  739.           $B0/$11/                { mov       al,11h }
  740.           $E6/$20/                { out       20h,al }
  741.           $EB/$00/                { jmp       short $+2 }
  742.           $B0/$08/                { mov       al,8            ; Set up main interrupt vector number}
  743.           $E6/$21/                { out       21h,al }
  744.           $EB/$00/                { jmp       short $+2 }
  745.           $B0/$04/                { mov       al,4 }
  746.           $E6/$21/                { out       21h,al }
  747.           $EB/$00/                { jmp       short $+2 }
  748.           $B0/$01/                { mov       al,1 }
  749.           $E6/$21/                { out       21h,al }
  750.           $EB/$00/                { jmp       short $+2 }
  751.           $8A/$C4/                { mov       al,ah }
  752.           $E6/$21/                { out       21h,al }
  753.           {Set up slave 8259 }
  754.           $E4/$A1/                { in        al,0a1h         ; Get current interrupt mask }
  755.           $8A/$E0/                { mov       ah,al           ; save it }
  756.           $B0/$11/                { mov       al,11h }
  757.           $E6/$A0/                { out       0a0h,al }
  758.           $EB/$00/                { jmp       short $+2 }
  759.           $B0/$70/                { mov       al,70h }
  760.           $E6/$A1/                { out       0a1h,al }
  761.           $B0/$02/                { mov       al,2 }
  762.           $EB/$00/                { jmp       short $+2 }
  763.           $E6/$A1/                { out       0a1h,al }
  764.           $EB/$00/                { jmp       short $+2 }
  765.           $B0/$01/                { mov       al,1 }
  766.           $E6/$A1/                { out       0a1h,al }
  767.           $EB/$00/                { jmp       short $+2 }
  768.           $8A/$C4/                { mov       al,ah           ; Reset previous interrupt state }
  769.           $E6/$A1                 { out       0a1h,al }
  770.           );
  771.       end;
  772.  
  773.     begin
  774.       if ATmachine then
  775.         Reset8259AT
  776.       else
  777.         Reset8259PC;
  778.     end;
  779.  
  780.   begin
  781.     {Interrupts off}
  782.     IntsOff;
  783.  
  784.     {Reset 8259 if requested}
  785.     if Revector8259 then
  786.       Reset8259;
  787.  
  788.     {Reset the communications state if requested}
  789.     if RestoreComm then
  790.       RestoreCommState;
  791.  
  792.     {Restore the main interrupt vector table and the misc save areas}
  793.     Move(Vectors, Mem[0:0], 1024);
  794.  
  795.     {Interrupts on}
  796.     IntsOn;
  797.  
  798.     {Flag that we don't want system restoring vectors for us}
  799.     VectorsRestored := True;
  800.  
  801.     Move(EGAsavTable, Mem[$40:$A8], 8); {EGA table}
  802.     Move(IntComTable, Mem[$40:$F0], 16); {Interapplications communication area}
  803.     Move(ParentTable, Mem[PrefixSeg:$16], 2); {Parent address}
  804.     Move(Mem[0:$88], Mem[PrefixSeg:$0A], 12); {Int 22,23,24 addresses}
  805.   end;
  806.  
  807.   procedure MarkBlocks(BottomBlock : BlockType);
  808.     {-Mark those blocks to be released}
  809.   var
  810.     B : BlockType;
  811.     CommandPsp, MarkPsp : Word;
  812.  
  813.     procedure BatchWarning(B : BlockType);
  814.       {-Warn about the trapping effect of batch files}
  815.     var
  816.       T : BlockType;
  817.     begin
  818.       ReturnCode := 1;
  819.       {Accumulate number of bytes temporarily trapped}
  820.       for T := 1 to B do
  821.         if Blocks[T].ReleaseIt then
  822.           Inc(TrappedBytes, LongInt(MemW[Blocks[T].Mcb:3]) shl 4);
  823.     end;
  824.  
  825.   begin
  826.     CommandPsp := Blocks[2].Psp;
  827.     MarkPsp := Blocks[BottomBlock].Psp;
  828.  
  829.     for B := 1 to BlockNum do
  830.       with Blocks[B] do
  831.         if (B < BottomBlock) then begin
  832.           {Release any trapped environment block}
  833.           if KeepMark then
  834.             releaseIt := (psp <> PrefixSeg) and (psp > markPsp)
  835.           else
  836.             releaseIt := (psp <> PrefixSeg) and (psp >= markPsp);
  837.         end else if (Psp = CommandPsp) then begin
  838.           {Don't release blocks owned by COMMAND.COM}
  839.           ReleaseIt := False;
  840.           BatchWarning(B);
  841.         end else if KeepMark then
  842.           {Release all but RELNET and the mark}
  843.           releaseIt := (psp <> PrefixSeg) and (psp <> markPsp)
  844.         else
  845.           {Release all but RELNET itself}
  846.           ReleaseIt := (Psp <> PrefixSeg);
  847.  
  848.     {$IFDEF Debug}
  849.     for B := 1 to BlockNum do
  850.       with Blocks[B] do
  851.         WriteLn(B:3, ' ', HexW(Psp), ' ', HexW(Mcb), ' ', ReleaseIt);
  852.     {$ENDIF}
  853.   end;
  854.  
  855.   procedure ReleaseMem;
  856.     {-Release DOS memory marked for release}
  857.   var
  858.     B : BlockType;
  859.   begin
  860.     if Verbose then
  861.       WriteLn('Releasing DOS memory');
  862.     with Regs do
  863.       for B := 1 to BlockNum do
  864.         with Blocks[B] do
  865.           if ReleaseIt then begin
  866.             AH := $49;
  867.             {The block is always 1 paragraph above the MCB}
  868.             ES := Mcb+1;
  869.             MsDos(Regs);
  870.             if Odd(Flags) then begin
  871.               WriteLn('Could not release block at segment ', HexW(ES));
  872.               Abort('Memory may be a mess.... Please reboot');
  873.             end;
  874.           end;
  875.   end;
  876.  
  877.   procedure UpdateWatch(WatchBlock : BlockType);
  878.     {-Write a new watch data area based on the release and the original watch}
  879.   type
  880.     ChangeBlock =
  881.       record
  882.         VecID : Word;
  883.         VecOfs : Word;
  884.         VecSeg : Word;
  885.         PatchWord : Word;
  886.       end;
  887.   var
  888.     Changes : array[0..MaxChanges] of ChangeBlock;
  889.     P : ^ChangeBlock;
  890.     WatchSeg, C, O, I, ActualMax : Word;
  891.     KeepPSP : Boolean;
  892.  
  893.     function WillKeepPSP(PspAdd : Word) : Boolean;
  894.       {-Return true if this psp address will be kept}
  895.     var
  896.       B : BlockType;
  897.     begin
  898.       for B := 1 to BlockNum do
  899.         with Blocks[B] do
  900.           if Psp = PspAdd then begin
  901.             WillKeepPSP := not(ReleaseIt);
  902.             Exit;
  903.           end;
  904.     end;
  905.  
  906.   begin
  907.     WatchSeg := Blocks[WatchBlock].Psp;
  908.     ActualMax := MemW[WatchSeg:NextChange];
  909.  
  910.     {Transfer changes from WATCH into a buffer array}
  911.     I := 0;
  912.     O := 0;
  913.     while I < ActualMax do begin
  914.       P := Ptr(WatchSeg, ChangeVectors+I);
  915.       Move(P^, Changes[O], SizeOf(ChangeBlock));
  916.       Inc(I, SizeOf(ChangeBlock));
  917.       Inc(O);
  918.     end;
  919.  
  920.     {Determine which change records to keep and transfer them back to WATCH}
  921.     KeepPSP := True;
  922.     I := 0;
  923.     for C := 0 to Pred(O) do begin
  924.       with Changes[C] do
  925.         if VecID = $FFFF then
  926.           {This record starts a new PSP. See if PSP is kept in memory}
  927.           KeepPSP := WillKeepPSP(VecOfs);
  928.       if KeepPSP then begin
  929.         P := Ptr(WatchSeg, ChangeVectors+I);
  930.         Move(Changes[C], P^, SizeOf(ChangeBlock));
  931.         I := I+SizeOf(ChangeBlock);
  932.       end;
  933.     end;
  934.     MemW[WatchSeg:NextChange] := I;
  935.  
  936.     {Update the WATCH image of the vector table to whatever's current}
  937.     Move(Mem[0:0], Mem[WatchSeg:CurrVectors], 1024);
  938.   end;
  939.  
  940.   function EMSpresent : Boolean;
  941.     {-Return true if EMS memory manager is present}
  942.   var
  943.     F : file;
  944.   begin
  945.     {"file handle" defined by the expanded memory manager at installation}
  946.     Assign(F, 'EMMXXXX0');
  947.     Reset(F);
  948.     if IoResult = 0 then begin
  949.       EMSpresent := True;
  950.       Close(F);
  951.     end else
  952.       EMSpresent := False;
  953.   end;
  954.  
  955.   procedure RestoreEMSmap;
  956.     {-Restore EMS to state at time of mark}
  957.  
  958.     procedure EMSpageMap(var PageMap : PageArray; var EMShandles : Word);
  959.       {-return an array of the allocated memory blocks}
  960.     begin
  961.       Regs.AH := $4D;
  962.       Regs.ES := Seg(PageMap);
  963.       Regs.DI := Ofs(PageMap);
  964.       Regs.BX := 0;
  965.       Intr(EMSinterrupt, Regs);
  966.       if Regs.AH <> 0 then
  967.         EMShandles := 0
  968.       else
  969.         EMShandles := Regs.BX;
  970.     end;
  971.  
  972.     procedure ReleaseEMSblocks(var OldMap, NewMap : PageArray);
  973.       {-Release those EMS blocks allocated since MARK was installed}
  974.     var
  975.       O, N, NHandle : Word;
  976.  
  977.       procedure EMSdeallocate(EMShandle : Word);
  978.         {-Release the allocated expanded memory}
  979.       begin
  980.         Regs.AH := $45;
  981.         Regs.DX := EMShandle;
  982.         Intr(EMSinterrupt, Regs);
  983.         if Regs.AH <> 0 then begin
  984.           WriteLn('Program error or EMS device not responding');
  985.           Abort('EMS memory may be a mess... Please reboot');
  986.         end;
  987.       end;
  988.  
  989.     begin
  990.       for N := 1 to EMShandles do begin
  991.         {Scan all current handles}
  992.         NHandle := NewMap[N].Handle;
  993.         if StoredHandles > 0 then begin
  994.           {See if current handle matches one stored by MARK}
  995.           O := 1;
  996.           while (OldMap[O].Handle <> NHandle) and (O <= StoredHandles) do
  997.             Inc(O);
  998.           {If not, deallocate the current handle}
  999.           if (O > StoredHandles) then
  1000.             EMSdeallocate(NHandle);
  1001.         end else
  1002.           {No handles stored by MARK, deallocate all current handles}
  1003.           EMSdeallocate(NHandle);
  1004.       end;
  1005.     end;
  1006.  
  1007.   begin
  1008.     {Get the existing EMS page map}
  1009.     GetMem(Map, 2048);
  1010.     EMSpageMap(Map^, EMShandles);
  1011.     if EMShandles > MaxHandles then
  1012.       WriteLn('EMS handle count exceeds capacity of RELNET -- no action taken')
  1013.     else if EMShandles <> 0 then begin
  1014.       {See how many handles were active when MARK was installed}
  1015.       if Verbose then
  1016.         WriteLn('Releasing EMS memory allocated since mark');
  1017.       StoredHandles := FilMarkHandles;
  1018.       {Get the stored page map}
  1019.       StoredMap := FilMarkPageMap;
  1020.       {Compare the two maps and deallocate pages not in the stored map}
  1021.       ReleaseEMSblocks(StoredMap^, Map^);
  1022.     end;
  1023.   end;
  1024.  
  1025.   procedure GetOptions;
  1026.     {-Analyze command line for options}
  1027.   var
  1028.     Arg : String;
  1029.     ArgLen : Byte absolute Arg;
  1030.     I : Word;
  1031.  
  1032.     procedure WriteHelp;
  1033.       {-Show the options}
  1034.     begin
  1035.       WriteLn;
  1036.       WriteLn('RELNET removes memory-resident programs from memory, particularly network');
  1037.       WriteLn('shells like Novell''s NetWare, although it will also release normal memory');
  1038.       WriteLn('resident programs. In combination with MARKNET it thoroughly restores the');
  1039.       WriteLn('system to its state at the time MARKNET was called.');
  1040.       WriteLn;
  1041.       WriteLn('RELNET accepts the following command line syntax:');
  1042.       WriteLn;
  1043.       WriteLn('  RELNET NetMarkFile [Options]');
  1044.       WriteLn;
  1045.       WriteLn('Options may be preceded by either / or -. Valid options are:');
  1046.       WriteLn;
  1047.       WriteLn('  /C         do NOT restore communications state.');
  1048.       WriteLn('  /E         do NOT access EMS memory.');
  1049.       WriteLn('  /K         release memory, but keep the mark in place.');
  1050.       WriteLn('  /P         do NOT restore DOS environment.');
  1051.       WriteLn('  /R         revector 8259 interrupt controller to powerup state.');
  1052.       WriteLn('  /S chars   stuff string (<16 chars) into keyboard buffer on exit.');
  1053.       WriteLn('  /T         do NOT reset system timer chip to default rate.');
  1054.       WriteLn('  /V         verbose: show each step of the restore.');
  1055.       WriteLn('  /?         write this help screen.');
  1056.       Halt(1);
  1057.     end;
  1058.  
  1059.   begin
  1060.     {Initialize defaults}
  1061.     MarkName := '';
  1062.     Keys := '';
  1063.  
  1064.     Revector8259 := False;
  1065.     KeepMark := False;
  1066.     DealWithEMS := True;
  1067.     ResetTimer := True;
  1068.     Verbose := False;
  1069.     RestoreEnvir := True;
  1070.     RestoreComm := True;
  1071.  
  1072.     ReturnCode := 0;
  1073.     TrappedBytes := 00;
  1074.  
  1075.     I := 1;
  1076.     while I <= ParamCount do begin
  1077.       Arg := ParamStr(I);
  1078.       if (Arg[1] = '?') then
  1079.         WriteHelp
  1080.       else if (Arg[1] = '-') or (Arg[1] = '/') then
  1081.         case ArgLen of
  1082.           1 : Abort('Missing command option following '+Arg);
  1083.           2 : case Upcase(Arg[2]) of
  1084.                 'C' : RestoreComm := False;
  1085.                 'E' : DealWithEMS := False;
  1086.                 'K' : KeepMark := True;
  1087.                 'P' : RestoreEnvir := False;
  1088.                 'R' : Revector8259 := True;
  1089.                 'S' : begin
  1090.                         if I >= ParamCount then
  1091.                           Abort('Key string missing');
  1092.                         inc(I);
  1093.                         Arg := ParamStr(I);
  1094.                         if ArgLen > 15 then
  1095.                           Abort('No more than 15 keys may be stuffed');
  1096.                         Keys := Arg+^M;
  1097.                       end;
  1098.                 'T' : ResetTimer := False;
  1099.                 'V' : Verbose := True;
  1100.                 '?' : WriteHelp;
  1101.               else
  1102.                 Abort('Unknown command option: '+Arg);
  1103.               end;
  1104.         else
  1105.           Abort('Unknown command option: '+Arg);
  1106.         end
  1107.       else if Length(MarkName) = 0 then
  1108.         {Mark file}
  1109.         MarkName := StUpcase(Arg)
  1110.       else
  1111.         Abort('Too many mark files specified');
  1112.       Inc(I);
  1113.     end;
  1114.  
  1115.     if Length(MarkName) = 0 then begin
  1116.       WriteLn('No mark file specified');
  1117.       WriteHelp;
  1118.     end;
  1119.   end;
  1120.  
  1121.   {$IFDEF ReleaseSocket}
  1122.   procedure CloseSockets;
  1123.     {-Close IPX sockets. Specific to Novell NetWare}
  1124.   var
  1125.     Socket : Word;
  1126.     Status : Byte;
  1127.   begin
  1128.     if Verbose then
  1129.       Write('Closing sockets ');
  1130.     for Socket := 0 to $1000 do begin
  1131.       Status := IPXOpenSocket(Socket, False);
  1132.       case Status of
  1133.         0 : {Socket wasn't open}
  1134.           IPXCloseSocket(Socket);
  1135.         $FF : {Socket was already open}
  1136.           begin
  1137.             if Verbose then
  1138.               Write(HexW(Socket), ' ');
  1139.             IPXCloseSocket(Socket);
  1140.           end;
  1141.       end;
  1142.     end;
  1143.     if Verbose then
  1144.       WriteLn;
  1145.   end;
  1146.   {$ENDIF}
  1147.  
  1148.   procedure GetDosPtr;
  1149.     {-Return pointer to DOS internal variables table}
  1150.   begin
  1151.     with Regs do begin
  1152.       AH := $52;
  1153.       MsDos(Regs);
  1154.       Dec(BX, 2);
  1155.       DosPtr := Ptr(ES, BX);
  1156.     end;
  1157.   end;
  1158.  
  1159.   procedure FindDevChain;
  1160.     {-Return segment, offset and pointer to NUL device}
  1161.   begin
  1162.     GetDosPtr;
  1163.     DevicePtr := @DosPtr^.NullDevice;
  1164.     DeviceSegment := SO(DevicePtr).S;
  1165.     DeviceOffset := SO(DevicePtr).O;
  1166.   end;
  1167.  
  1168.   procedure RestoreDosTable;
  1169.     {-Restore the DOS variables table, except for the buffer pointer}
  1170.   var
  1171.     DosBase : Pointer;
  1172.     CPtr : Pointer;
  1173.   begin
  1174.     if Verbose then
  1175.       WriteLn('Restoring DOS data area at 0050:0000');
  1176.     CPtr := Ptr($50, 0);
  1177.     Move(DosData, CPtr^, $200);
  1178.     DosBase := Ptr(SO(DosPtr).S, 0);
  1179.     if Verbose then
  1180.       WriteLn('Restoring DOS variables table at ', HexPtr(DosBase));
  1181.     CPtr := DosPtr^.CachePtr;
  1182.     move(DosTable^, DosBase^, DosTableSize);
  1183.     DosPtr^.CachePtr := CPtr;
  1184.   end;
  1185.  
  1186.   procedure RestoreFileTable;
  1187.     {-Copy the internal file table from our memory buffer to its DOS location}
  1188.   var
  1189.     S : SftRecPtr;
  1190.     I : Word;
  1191.   begin
  1192.     S := DosPtr^.FirstSFT;
  1193.     if Verbose then
  1194.       WriteLn('Restoring DOS file table at ', HexPtr(S));
  1195.     for I := 1 to FileTableCnt do begin
  1196.       Move(FileTableA[I]^, S^, 6+FileTableA[I]^.Count*FileRecSize);
  1197.       S := S^.Next;
  1198.     end;
  1199.   end;
  1200.  
  1201.   procedure RestoreDeviceDrivers;
  1202.     {-Restore the device driver chain to its original state}
  1203.   var
  1204.     D : Word;
  1205.     DevPtr : DeviceHeaderPtr;
  1206.   begin
  1207.     if Verbose then
  1208.       WriteLn('Restoring device driver chain');
  1209.     DevPtr := DevicePtr;
  1210.     for D := 1 to DevCnt do begin
  1211.       DevPtr^ := DevA[D]^;
  1212.       with DevA[D]^ do
  1213.         DevPtr := Ptr(NextHeaderSegment, NextHeaderOffset);
  1214.     end;
  1215.   end;
  1216.  
  1217.   procedure RestoreCommandPSP;
  1218.     {-Copy COMMAND.COM's PSP back into place}
  1219.   type
  1220.     McbRec =
  1221.       record
  1222.         ID : Char;
  1223.         PSPSeg : Word;
  1224.         Len : Word;
  1225.       end;
  1226.   var
  1227.     McbPtr : ^McbRec;
  1228.     PspPtr : Pointer;
  1229.   begin
  1230.     {First block}
  1231.     McbPtr := Ptr(DosPtr^.McbSeg, 0);
  1232.     {Next block, which is owned by COMMAND.COM}
  1233.     McbPtr := Ptr(SO(McbPtr).S+McbPtr^.Len+1, 0);
  1234.     CommandSeg := McbPtr^.PSPSeg;
  1235.     PspPtr := Ptr(CommandSeg, 0);
  1236.     if Verbose then
  1237.       WriteLn('Restoring COMMAND.COM PSP at ', HexPtr(PspPtr));
  1238.     Move(CommandPsp, PspPtr^, $100);
  1239.   end;
  1240.  
  1241.   procedure RestoreCommandPatch;
  1242.     {-Restore the patch that NetWare applies to COMMAND.COM}
  1243.   begin
  1244.     if (PatchSegm <> 0) or (PatchOfst <> 0) then
  1245.       if (Mem[PatchSegm:PatchOfst+$01] <> Byte('/')) or
  1246.       (Mem[PatchSegm:PatchOfst+$11] <> Byte('/')) then begin
  1247.         if Verbose then
  1248.           WriteLn('Removing patch at ', HexW(PatchSegm), ':', HexW(PatchOfst));
  1249.         Mem[PatchSegm:PatchOfst+$01] := Byte('/');
  1250.         Mem[PatchSegm:PatchOfst+$11] := Byte('/');
  1251.       end;
  1252.   end;
  1253.  
  1254.   procedure FindEnv(CommandSeg : Word; var EnvSeg, EnvLen : Word);
  1255.     {-Return the segment and length of the master environment}
  1256.   var
  1257.     Mcb : Word;
  1258.   begin
  1259.     Mcb := CommandSeg-1;
  1260.     EnvSeg := MemW[CommandSeg:$2C];
  1261.     if EnvSeg = 0 then
  1262.       {Master environment is next block past COMMAND}
  1263.       EnvSeg := Commandseg+MemW[Mcb:3]+1;
  1264.     EnvLen := MemW[(EnvSeg-1):3] shl 4;
  1265.   end;
  1266.  
  1267.   procedure RestoreDosEnvironment;
  1268.     {-Restore the master copy of the DOS environment}
  1269.   var
  1270.     EnvSeg : Word;
  1271.     CurLen : Word;
  1272.     P : Pointer;
  1273.   begin
  1274.     if RestoreEnvir then begin
  1275.       FindEnv(CommandSeg, EnvSeg, CurLen);
  1276.       if CurLen <> EnvLen then
  1277.         Abort('Environment length changed');
  1278.       if Verbose then
  1279.         WriteLn('Restoring DOS environment, ', EnvLen, ' bytes at ', HexW(EnvSeg), ':0000');
  1280.       P := Ptr(EnvSeg, 0);
  1281.       move(EnvPtr^, P^, EnvLen);
  1282.     end;
  1283.   end;
  1284.  
  1285.   procedure SetTimerRate(Rate : Word);
  1286.     {-Program system 8253 timer number 0 to run at specified rate}
  1287.   begin
  1288.     IntsOff;
  1289.     Port[$43] := $36;
  1290.     NullJump;
  1291.     Port[$40] := Lo(Rate);
  1292.     NullJump;
  1293.     Port[$40] := Hi(Rate);
  1294.     IntsOn;
  1295.   end;
  1296.  
  1297.   procedure RestoreTimer;
  1298.     {-Set the system timer to its normal rate}
  1299.   begin
  1300.     if Verbose then
  1301.       WriteLn('Restoring system timer to normal rate');
  1302.     SetTimerRate(0);
  1303.   end;
  1304.  
  1305.   function CompaqDOS30 : Boolean;
  1306.     {-Return true if Compaq DOS 3.0}
  1307.   begin
  1308.     with Regs do begin
  1309.       AH := $34;
  1310.       MsDos(Regs);
  1311.       CompaqDOS30 := (BX = $19C);
  1312.     end;
  1313.   end;
  1314.  
  1315.   procedure ValidateDosVersion;
  1316.     {-Assure supported version of DOS and compute size of DOS internal filerec}
  1317.   var
  1318.     DosVer : Word;
  1319.   begin
  1320.     DosVer := DosVersion;
  1321.     case Lo(DosVer) of
  1322.       3 : if (Hi(DosVer) < $0A) and not CompaqDOS30 then
  1323.             {IBM DOS 3.0}
  1324.             FileRecSize := 56
  1325.           else
  1326.             {DOS 3.1+ or Compaq DOS 3.0}
  1327.             FileRecSize := 53;
  1328.       4 : FileRecSize := 59;
  1329.     else
  1330.       Abort('Requires DOS 3.x or 4.x');
  1331.     end;
  1332.   end;
  1333.  
  1334.   const
  1335.     KbdStart = $1E;
  1336.     KbdEnd = $3C;
  1337.   var
  1338.     KbdHead : Word absolute $40 : $1A;
  1339.     KbdTail : Word absolute $40 : $1C;
  1340.  
  1341.   procedure StuffKey(W : Word);
  1342.     {-Stuff one key into the keyboard buffer}
  1343.   var
  1344.     SaveKbdTail : Word;
  1345.   begin
  1346.     SaveKbdTail := KbdTail;
  1347.     if KbdTail = KbdEnd then
  1348.       KbdTail := KbdStart
  1349.     else
  1350.       Inc(KbdTail, 2);
  1351.     if KbdTail = KbdHead then
  1352.       KbdTail := SaveKbdTail
  1353.     else
  1354.       MemW[$40:SaveKbdTail] := W;
  1355.   end;
  1356.  
  1357.   procedure StuffKeys(Keys : string; ClearFirst : Boolean);
  1358.     {-Stuff up to 16 keys into keyboard buffer}
  1359.   var
  1360.     Len : Byte;
  1361.     I : Byte;
  1362.   begin
  1363.     if ClearFirst then
  1364.       KbdTail := KbdHead;
  1365.     Len := Length(Keys);
  1366.     if Len > 16 then
  1367.       Len := 16;
  1368.     for I := 1 to Length(Keys) do
  1369.       StuffKey(Ord(Keys[I]));
  1370.   end;
  1371.  
  1372. begin
  1373.   WriteLn('RELNET ', Version, ', by TurboPower Software');
  1374.  
  1375.   {Assure supported version of DOS}
  1376.   ValidateDosVersion;
  1377.  
  1378.   {Analyze command line for options}
  1379.   GetOptions;
  1380.  
  1381.   {Find the start of the device driver chain via the NUL device}
  1382.   FindDevChain;
  1383.  
  1384.   {Get all allocated memory blocks in normal memory}
  1385.   FindTheBlocks;
  1386.  
  1387.   {Find the block marked with the MARK idstring, and MarkName if specified}
  1388.   if not(FindMark(MarkName, MarkID, MarkOffset, MemMark, FilMark, BottomBlock)) then
  1389.     Abort('No matching marker found, or protected marker encountered.');
  1390.   if MemMark then
  1391.     Abort('Marker must have been placed by NETMARK');
  1392.  
  1393.   {Find the watch block, if any}
  1394.   UseWatch := FindMark('', WatchID, WatchOffset, Junk, Junk, WatchBlock);
  1395.  
  1396.   {Mark those blocks to be released}
  1397.   MarkBlocks(BottomBlock);
  1398.  
  1399.   {Open and validate the mark file}
  1400.   ValidateMarkFile;
  1401.  
  1402.   {$IFDEF ReleaseSocket}
  1403.   {Close IPX sockets}
  1404.   if IPXServicesAvail then
  1405.     CloseSockets;
  1406.   {$ENDIF}
  1407.  
  1408.   {Get file mark information into memory}
  1409.   ReadMarkFile;
  1410.  
  1411.   {Copy the vector table from the MARK copy}
  1412.   CopyVectors;
  1413.  
  1414.   {Restore the device driver chain}
  1415.   RestoreDeviceDrivers;
  1416.  
  1417.   {Restore the COMMAND.COM patch possibly made by NetWare}
  1418.   RestoreCommandPatch;
  1419.  
  1420.   {Restore the DOS variables table}
  1421.   RestoreDosTable;
  1422.  
  1423.   {Restore the DOS file table}
  1424.   RestoreFileTable;
  1425.  
  1426.   {Restore the COMMAND.COM PSP}
  1427.   RestoreCommandPSP;
  1428.  
  1429.   {Restore the master DOS environment}
  1430.   RestoreDosEnvironment;
  1431.  
  1432.   {Set the timer to normal rate}
  1433.   if ResetTimer then
  1434.     RestoreTimer;
  1435.  
  1436.   {Update the watch block if requested}
  1437.   if UseWatch then
  1438.     if not Blocks[WatchBlock].ReleaseIt then
  1439.       {Watch itself won't be released}
  1440.       UpdateWatch(WatchBlock);
  1441.  
  1442.   {Release normal memory}
  1443.   ReleaseMem;
  1444.  
  1445.   {Deal with expanded memory}
  1446.   if DealWithEMS then
  1447.     if EMSpresent then
  1448.       RestoreEMSmap;
  1449.  
  1450.   {Write success message}
  1451.   WriteLn('Memory released above ', StUpcase(MarkName));
  1452.  
  1453.   if (ReturnCode <> 0) and Verbose then
  1454.     WriteLn(TrappedBytes, ' bytes temporarily trapped until batch file completes');
  1455.  
  1456.   {Stuff keyboard buffer if requested}
  1457.   if Length(Keys) > 0 then
  1458.     StuffKeys(Keys, True);
  1459.  
  1460.   Halt(ReturnCode);
  1461. end.
  1462.