home *** CD-ROM | disk | FTP | other *** search
/ ProfitPress Mega CDROM2 …eeware (MSDOS)(1992)(Eng) / ProfitPress-MegaCDROM2.B6I / UTILITY / SYSTEM / TSRSRC31.ZIP / MARKNET.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1991-11-04  |  18.3 KB  |  677 lines

  1. {**************************************************************************
  2. *   MARKNET - stores system information in a file for later restoration.  *
  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 FMARK 2.6)                                                *
  9. *   Version 2.8 3/10/89                                                   *
  10. *     store the DOS environment                                           *
  11. *     store information about the async ports                             *
  12. *   Version 2.9 5/4/89                                                    *
  13. *     for consistency                                                     *
  14. *   Version 3.0 7/21/91                                                   *
  15. *     for compatibility with DOS 5                                        *
  16. *     add Quiet option                                                    *
  17. *     save BIOS LPT port data areas                                       *
  18. *     save XMS allocation                                                 *
  19. *     add code for tracking high memory                                   *
  20. *   Version 3.1 11/4/91                                                   *
  21. *     no change                                                           *
  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 2048,0,10000}
  29.  
  30. {.$DEFINE Debug}         {Activate for status messages}
  31. {.$DEFINE MeasureStack}  {Activate to measure stack usage}
  32.  
  33. program MarkNet;
  34.  
  35. uses
  36.   Dos,
  37.   MemU,
  38.   Xms,
  39.   Ems;
  40.  
  41. const
  42.   Version = '3.1';
  43.   NmarkID = 'MN3.1 TSR';          {Marking string for TSR file mark}
  44.   NetMarkID = 'MN31';             {ID at start of net mark file}
  45.   NmarkOffset = $60;              {Where NmarkID is found in MARKNET TSR}
  46.  
  47. const
  48.   MarkFOpen : Boolean = False;    {True while mark file is open}
  49.   Quiet : Boolean = False;        {Set True to avoid screen output}
  50.  
  51. var
  52.   MarkName : PathStr;             {Name of mark file}
  53.  
  54.   DevicePtr : ^DeviceHeader;      {Pointer to the next device header}
  55.   DeviceSegment : Word;           {Current device segment}
  56.   DeviceOffset : Word;            {Current device offset}
  57.   MarkF : file;                   {Dump file}
  58.   DosPtr : ^DosRec;               {Pointer to internal DOS table}
  59.   CommandSeg : Word;              {PSP segment of primary COMMAND.COM}
  60.   CommandPsp : array[1..$100] of Byte;
  61.   FileTableA : array[1..5] of SftRecPtr;
  62.   FileTableCnt : Word;
  63.   FileRecSize : Word;
  64.   EHandles : Word;                {For tracking EMS allocation}
  65.   EmsPages : ^PageArray;
  66.   XHandles : Word;                {For tracking XMS allocation}
  67.   XmsPages : XmsHandlesPtr;
  68.   McbG : McbGroup;                {Mcbs allocated as we go resident}
  69.  
  70.   SaveExit : Pointer;
  71.  
  72.   {$IFDEF MeasureStack}
  73.   I : Word;
  74.   {$ENDIF}
  75.  
  76.   procedure ExitHandler; far;
  77.     {-Trap error exits (only)}
  78.   begin
  79.     ExitProc := SaveExit;
  80.     if MarkFOpen then begin
  81.       if IoResult = 0 then ;
  82.       Close(MarkF);
  83.       if IoResult = 0 then ;
  84.       Erase(MarkF);
  85.     end;
  86.     {Turbo will swap back, so undo what we've done already}
  87.     SwapVectors;
  88.   end;
  89.  
  90.   procedure Abort(Msg : String);
  91.     {-Halt in case of error}
  92.   begin
  93.     WriteLn(Msg);
  94.     Halt(1);
  95.   end;
  96.  
  97.   procedure FindDevChain;
  98.     {-Return segment, offset and pointer to NUL device}
  99.   begin
  100.     DosPtr := Ptr(OS(DosList).S, OS(DosList).O-2);
  101.     DevicePtr := @DosPtr^.NullDevice;
  102.     DeviceSegment := OS(DevicePtr).S;
  103.     DeviceOffset := OS(DevicePtr).O;
  104.   end;
  105.  
  106.   procedure CheckWriteError;
  107.     {-Check for errors writing to mark file}
  108.   begin
  109.     if IoResult = 0 then
  110.       Exit;
  111.     Abort('Error writing to '+MarkName);
  112.   end;
  113.  
  114.   procedure SaveStandardInfo;
  115.     {-Save the ID string, the vectors, and so on}
  116.   type
  117.     IDArray = array[1..4] of Char;
  118.   var
  119.     ID : IDArray;
  120.   begin
  121.     {Write the ID string}
  122.     {$IFDEF Debug}
  123.     WriteLn('Writing mark file ID string');
  124.     {$ENDIF}
  125.     ID := NetMarkID;
  126.     BlockWrite(MarkF, ID, SizeOf(IDArray));
  127.     CheckWriteError;
  128.  
  129.     {Write the start address of the device chain}
  130.     {$IFDEF Debug}
  131.     WriteLn('Writing null device address');
  132.     {$ENDIF}
  133.     BlockWrite(MarkF, DevicePtr, SizeOf(Pointer));
  134.     CheckWriteError;
  135.  
  136.     {Write the vector table}
  137.     {$IFDEF Debug}
  138.     WriteLn('Writing interrupt vector table');
  139.     {$ENDIF}
  140.     BlockWrite(MarkF, Mem[0:0], 1024);
  141.     CheckWriteError;
  142.  
  143.     {Write miscellaneous save areas}
  144.     {$IFDEF Debug}
  145.     WriteLn('Writing EGA save table');
  146.     {$ENDIF}
  147.     BlockWrite(MarkF, Mem[$40:$A8], 8); {EGA save table}
  148.     CheckWriteError;
  149.     {$IFDEF Debug}
  150.     WriteLn('Writing interapplications communication area');
  151.     {$ENDIF}
  152.     BlockWrite(MarkF, Mem[$40:$F0], 16); {Interapplications communication area}
  153.     CheckWriteError;
  154.     {$IFDEF Debug}
  155.     WriteLn('Writing parent PSP segment');
  156.     {$ENDIF}
  157.     BlockWrite(MarkF, Mem[PrefixSeg:$16], 2); {Parent's PSP segment}
  158.     CheckWriteError;
  159.     {$IFDEF Debug}
  160.     WriteLn('Writing BIOS printer table');
  161.     {$ENDIF}
  162.     BlockWrite(MarkF, Mem[$40:$8], 10); {Printer ports plus #printers}
  163.     CheckWriteError;
  164.  
  165.     {Write EMS information}
  166.     if EMSpresent then begin
  167.       if MaxAvail < 2048 then
  168.         Abort('Insufficient memory');
  169.       GetMem(EmsPages, 2048);
  170.       EHandles := EMSHandles(EmsPages^);
  171.     end else
  172.       EHandles := 0;
  173.     {$IFDEF Debug}
  174.     WriteLn('Writing EMS handle information');
  175.     {$ENDIF}
  176.     BlockWrite(MarkF, EHandles, SizeOf(Word));
  177.     if EHandles <> 0 then
  178.       BlockWrite(MarkF, EmsPages^, SizeOf(HandlePageRecord)*EHandles);
  179.     CheckWriteError;
  180.  
  181.     {Write XMS information}
  182.     if XmsInstalled then
  183.       XHandles := GetXmsHandles(XmsPages)
  184.     else
  185.       XHandles := 0;
  186.     {$IFDEF Debug}
  187.     WriteLn('Writing XMS handle information');
  188.     {$ENDIF}
  189.     BlockWrite(MarkF, XHandles, SizeOf(Word));
  190.     if XHandles <> 0 then
  191.       BlockWrite(MarkF, XmsPages^, SizeOf(XmsHandleRecord)*XHandles);
  192.     CheckWriteError;
  193.   end;
  194.  
  195.   procedure SaveDevChain;
  196.     {-Save the device driver chain}
  197.   begin
  198.     {$IFDEF Debug}
  199.     WriteLn('Saving device driver chain');
  200.     {$ENDIF}
  201.     while OS(DevicePtr).O <> $FFFF do begin
  202.       BlockWrite(MarkF, DevicePtr^, SizeOf(DeviceHeader));
  203.       CheckWriteError;
  204.       with DevicePtr^ do
  205.         DevicePtr := Ptr(NextHeaderSegment, NextHeaderOffset);
  206.     end;
  207.   end;
  208.  
  209.   procedure BufferFileTable;
  210.     {-Save an image of the system file table}
  211.   var
  212.     S : SftRecPtr;
  213.     Size : Word;
  214.   begin
  215.     with DosPtr^ do begin
  216.       S := FirstSFT;
  217.       FileTableCnt := 0;
  218.       while OS(S).O <> $FFFF do begin
  219.         Inc(FileTableCnt);
  220.         Size := 6+S^.Count*FileRecSize;
  221.         if MaxAvail < Size then
  222.           Abort('Insufficient memory');
  223.         GetMem(FileTableA[FileTableCnt], Size);
  224.         Move(S^, FileTableA[FileTableCnt]^, Size);
  225.         S := S^.Next;
  226.       end;
  227.     end;
  228.   end;
  229.  
  230.   procedure BufferAllocatedMcbs;
  231.     {-Save an array of all allocated Mcbs}
  232.   var
  233.     M : McbPtr;
  234.     Status : Word;
  235.     LinkStatus : Boolean;
  236.     Done : Boolean;
  237.   begin
  238.     {Access high memory if available}
  239.     if HiMemAvailable(DosV) then begin
  240.       LinkStatus := GetUmbLinkStatus;
  241.       Status := SetUmbLinkStatus(True);
  242.     end;
  243.     McbG.Count := 0;
  244.     M := Mcb1;
  245.     repeat
  246.       inc(McbG.Count);
  247.       with McbG.Mcbs[McbG.Count] do begin
  248.         mcb := OS(M).S;
  249.         psp := M^.Psp;
  250.       end;
  251.       Done := (M^.Id = 'Z');
  252.       M := Ptr(OS(M).S+M^.Len+1, 0);
  253.     until Done;
  254.     if HiMemAvailable(DosV) then
  255.       Status := SetUmbLinkStatus(LinkStatus);
  256.   end;
  257.  
  258.   procedure SaveDOSTable;
  259.     {-Save the DOS internal variables table}
  260.   var
  261.     DosBase : Pointer;
  262.     Size : Word;
  263.   begin
  264.     {$IFDEF Debug}
  265.     WriteLn('Saving DOS data area at 0050:0000');
  266.     {$ENDIF}
  267.     BlockWrite(MarkF, mem[$50:$0], $200);
  268.     CheckWriteError;
  269.     DosBase := Ptr(OS(DosPtr).S, 0);
  270.     Size := OS(DosPtr^.FirstSFT).O;
  271.     {$IFDEF Debug}
  272.     WriteLn('Saving DOS variables table at ', HexPtr(DosBase));
  273.     {$ENDIF}
  274.     BlockWrite(MarkF, Size, SizeOf(Word));
  275.     BlockWrite(MarkF, DosBase^, Size);
  276.     CheckWriteError;
  277.   end;
  278.  
  279.   procedure SaveFileTable;
  280.     {-Save the state of the file table}
  281.   var
  282.     I : Word;
  283.     Size : Word;
  284.   begin
  285.     {$IFDEF Debug}
  286.     WriteLn('Saving DOS file table at ', HexPtr(DosPtr^.FirstSFT));
  287.     {$ENDIF}
  288.     BlockWrite(MarkF, FileTableCnt, SizeOf(Word));
  289.     for I := 1 to FileTableCnt do begin
  290.       Size := 6+FileTableA[I]^.Count*FileRecSize;
  291.       BlockWrite(MarkF, FileTableA[I]^, Size);
  292.     end;
  293.     CheckWriteError;
  294.   end;
  295.  
  296.   procedure BufferCommandPSP;
  297.     {-Save the PSP of COMMAND.COM}
  298.   var
  299.     PspPtr : Pointer;
  300.   begin
  301.     CommandSeg := MasterCommandSeg;
  302.     PspPtr := Ptr(CommandSeg, 0);
  303.     Move(PspPtr^, CommandPsp, $100);
  304.   end;
  305.  
  306.   procedure SaveCommandPSP;
  307.   begin
  308.     {$IFDEF Debug}
  309.     WriteLn('Saving COMMAND.COM PSP at ', HexW(CommandSeg), ':0000');
  310.     {$ENDIF}
  311.     BlockWrite(MarkF, CommandPsp, $100);
  312.     CheckWriteError;
  313.   end;
  314.  
  315.   procedure SaveCommandPatch;
  316.     {-Restore the patch that NetWare applies to command.com}
  317.   label
  318.     ExitPoint;
  319.   const
  320.     Patch : array[0..14] of Char = ':/'#0'_______.___'#0;
  321.   var
  322.     Segm : Word;
  323.     Ofst : Word;
  324.     Indx : Word;
  325.   begin
  326.     for Segm := CommandSeg to PrefixSeg do
  327.       for Ofst := 0 to 15 do begin
  328.         Indx := 0;
  329.         while (Indx <= 14) and (Patch[Indx] = Char(Mem[Segm:Ofst+Indx])) do
  330.           Inc(Indx);
  331.         if Indx > 14 then begin
  332.           {$IFDEF Debug}
  333.           WriteLn('Saving COMMAND patch address at ', HexW(Segm), ':', HexW(Ofst));
  334.           {$ENDIF}
  335.           goto ExitPoint;
  336.         end;
  337.       end;
  338.     Segm := 0;
  339.     Ofst := 0;
  340. ExitPoint:
  341.     BlockWrite(MarkF, Ofst, SizeOf(Word));
  342.     BlockWrite(MarkF, Segm, SizeOf(Word));
  343.     CheckWriteError;
  344.   end;
  345.  
  346.   procedure FindEnv(CommandSeg : Word; var EnvSeg, EnvLen : Word);
  347.     {-Return the segment and length of the master environment}
  348.   var
  349.     Mcb : Word;
  350.   begin
  351.     Mcb := CommandSeg-1;
  352.     EnvSeg := MemW[CommandSeg:$2C];
  353.     if EnvSeg = 0 then
  354.       {Master environment is next block past COMMAND}
  355.       EnvSeg := Commandseg+MemW[Mcb:3]+1;
  356.     EnvLen := MemW[(EnvSeg-1):3] shl 4;
  357.   end;
  358.  
  359.   procedure SaveDosEnvironment;
  360.     {-Save the master copy of the DOS environment}
  361.   var
  362.     EnvSeg : Word;
  363.     EnvLen : Word;
  364.     P : Pointer;
  365.   begin
  366.     FindEnv(CommandSeg, EnvSeg, EnvLen);
  367.     {$IFDEF Debug}
  368.     WriteLn('Saving master environment, ', EnvLen, ' bytes at ', HexW(EnvSeg), ':0000');
  369.     {$ENDIF}
  370.     P := Ptr(EnvSeg, 0);
  371.     BlockWrite(MarkF, EnvLen, SizeOf(Word));
  372.     BlockWrite(MarkF, P^, EnvLen);
  373.     CheckWriteError;
  374.   end;
  375.  
  376.   procedure SaveCommState;
  377.     {-Save the state of the communications controllers}
  378.   var
  379.     PicMask : Byte;
  380.     Com : Byte;
  381.     LCRSave : Byte;
  382.     Base : Word;
  383.     ComPortBase : array[1..2] of Word absolute $40:0; {Com port base addresses}
  384.  
  385.     procedure SaveReg(Offset : Byte);
  386.       {-Save one communications register}
  387.     var
  388.       Reg : Byte;
  389.     begin
  390.       Reg := Port[Base+Offset];
  391.       BlockWrite(MarkF, Reg, SizeOf(Byte));
  392.       CheckWriteError;
  393.     end;
  394.  
  395.   begin
  396.     {$IFDEF Debug}
  397.     WriteLn('Saving communications environment');
  398.     {$ENDIF}
  399.  
  400.     {Save the 8259 interrupt enable mask}
  401.     PicMask := Port[$21];
  402.     BlockWrite(MarkF, PicMask, SizeOf(Byte));
  403.     CheckWriteError;
  404.  
  405.     for Com := 1 to 2 do begin
  406.       Base := ComPortBase[Com];
  407.  
  408.       {Save the Com port base address}
  409.       BlockWrite(MarkF, Base, SizeOf(Word));
  410.       CheckWriteError;
  411.  
  412.       if Base <> 0 then begin
  413.         {Save the rest of the control state}
  414.         SaveReg(IER);             {Interrupt enable register}
  415.         SaveReg(LCR);             {Line control register}
  416.         SaveReg(MCR);             {Modem control register}
  417.         LCRSave := Port[Base+LCR]; {Save line control register}
  418.         Port[Base+LCR] := LCRSave or $80; {Enable baud rate divisor registers}
  419.         SaveReg(BRL);             {Baud rate divisor low}
  420.         SaveReg(BRH);             {Baud rate divisor high}
  421.         Port[Base+LCR] := LCRSave; {Restore line control register}
  422.       end;
  423.     end;
  424.   end;
  425.  
  426.   procedure SaveAllocatedMcbs;
  427.     {-Save list of allocated memory control blocks}
  428.   begin
  429.     {$IFDEF Debug}
  430.     WriteLn('Saving memory allocation group');
  431.     {$ENDIF}
  432.     {Save the number of Mcbs}
  433.     BlockWrite(MarkF, McbG.Count, SizeOf(Word));
  434.     CheckWriteError;
  435.     {Save the used Mcbs}
  436.     BlockWrite(MarkF, McbG.Mcbs, 2*SizeOf(Word)*McbG.Count);
  437.     CheckWriteError;
  438.   end;
  439.  
  440.   function CompaqDOS30 : Boolean; assembler;
  441.     {-Return true if Compaq DOS 3.0}
  442.   asm
  443.     mov ah,$34
  444.     int $21
  445.     cmp bx,$019C
  446.     mov al,1
  447.     jz @Done
  448.     dec al
  449. @Done:
  450.   end;
  451.  
  452.   procedure ValidateDosVersion;
  453.     {-Assure supported version of DOS and compute size of DOS internal filerec}
  454.   var
  455.     DosVer : Word;
  456.   begin
  457.     DosVer := DosVersion;
  458.     case Lo(DosVer) of
  459.       3 : if (Hi(DosVer) < $0A) and not CompaqDOS30 then
  460.             {IBM DOS 3.0}
  461.             FileRecSize := 56
  462.           else
  463.             {DOS 3.1+ or Compaq DOS 3.0}
  464.             FileRecSize := 53;
  465.       4, 5 : FileRecSize := 59;
  466.     else
  467.       Abort('Requires DOS 3, 4, or 5');
  468.     end;
  469.   end;
  470.  
  471.   procedure SaveIDStrings;
  472.     {-Save identification strings within the PSP}
  473.   var
  474.     ID : String[10];
  475.   begin
  476.     Move(MarkName, Mem[PrefixSeg:$80], Length(MarkName)+1);
  477.     Mem[PrefixSeg:$80+Length(MarkName)+1] := 13;
  478.     ID := NmarkID;
  479.     Move(ID[1], Mem[PrefixSeg:NmarkOffset], Length(ID));
  480.   end;
  481.  
  482.   procedure CloseStandardFiles;
  483.     {-Close all standard files}
  484.   var
  485.     H : Word;
  486.   begin
  487.     for H := 0 to 4 do
  488.       asm
  489.         mov ah,$3E
  490.         mov bx,H
  491.         int $21
  492.       end;
  493.   end;
  494.  
  495.   procedure GetOptions;
  496.     {-Get command line options}
  497.   var
  498.     I : Word;
  499.     Arg : String[127];
  500.  
  501.     procedure UnknownOption;
  502.     begin
  503.       WriteLn('Unknown command line option: ', Arg);
  504.       Halt(1);
  505.     end;
  506.  
  507.     procedure BadOption;
  508.     begin
  509.       WriteLn('Invalid command line option: ', Arg);
  510.       Halt(1);
  511.     end;
  512.  
  513.     procedure WriteCopyright;
  514.     begin
  515.       WriteLn('MARKNET ', Version, ', Copyright 1991 TurboPower Software');
  516.     end;
  517.  
  518.     procedure WriteHelp;
  519.     begin
  520.       WriteCopyright;
  521.       WriteLn;
  522.       WriteLn('MARKNET saves a picture of the PC system status in a file,');
  523.       WriteLn('so that the state can later be restored by using RELNET.');
  524.       WriteLn;
  525.       WriteLn('MARKNET accepts the following command line syntax:');
  526.       WriteLn;
  527.       WriteLn('  MARKNET [Options] MarkFile');
  528.       WriteLn;
  529.       WriteLn('Options may be preceded by either / or -. Valid options are as follows:');
  530.       WriteLn('     /Q     write no screen output.');
  531.       WriteLn('     /?     write this help screen.');
  532.       Halt(1);
  533.     end;
  534.  
  535.   begin
  536.     MarkName := '';
  537.     I := 1;
  538.     while I <= ParamCount do begin
  539.       Arg := ParamStr(I);
  540.       if Arg = '?' then
  541.         WriteHelp
  542.       else
  543.         case Arg[1] of
  544.           '-', '/' :
  545.             case Length(Arg) of
  546.               1 : BadOption;
  547.               2 : case Upcase(Arg[2]) of
  548.                     '?' : WriteHelp;
  549.                     'Q' : Quiet := True;
  550.                   else
  551.                     BadOption;
  552.                   end;
  553.             else
  554.               UnknownOption;
  555.             end;
  556.         else
  557.           if Length(MarkName) <> 0 then
  558.             BadOption
  559.           else
  560.             MarkName := StUpcase(Arg);
  561.         end;
  562.       Inc(I);
  563.     end;
  564.     {Assure mark file specified}
  565.     if Length(MarkName) = 0 then
  566.       WriteHelp;
  567.     if not Quiet then
  568.       WriteCopyright;
  569.   end;
  570.  
  571. begin
  572.   {$IFDEF MeasureStack}
  573.   fillchar(mem[sseg:0], sptr-16, $AA);
  574.   {$ENDIF}
  575.  
  576.   {Must run with standard DOS vectors}
  577.   SwapVectors;
  578.   SaveExit := ExitProc;
  579.   ExitProc := @ExitHandler;
  580.  
  581.   {Get command line options}
  582.   GetOptions;
  583.  
  584.   {Assure supported version of DOS}
  585.   ValidateDosVersion;
  586.  
  587.   {Find the device driver chain and the DOS internal table}
  588.   FindDevChain;
  589.  
  590.   {Save PSP region of COMMAND.COM}
  591.   BufferCommandPSP;
  592.  
  593.   {Buffer the DOS file table}
  594.   BufferFileTable;
  595.  
  596.   {Deallocate environment}
  597.   asm
  598.     mov es,PrefixSeg
  599.     mov es,es:[$002C]
  600.     mov ah,$49
  601.     int $21
  602.   end;
  603.  
  604.   {Buffer the allocated mcb array}
  605.   BufferAllocatedMcbs;
  606.  
  607.   {Open the mark file}
  608.   Assign(MarkF, MarkName);
  609.   Rewrite(MarkF, 1);
  610.   if IoResult <> 0 then
  611.     Abort('Error creating '+MarkName);
  612.   MarkFOpen := True;
  613.  
  614.   {Save ID string, interrupt vectors and other standard state information}
  615.   SaveStandardInfo;
  616.  
  617.   {Save the device driver chain}
  618.   SaveDevChain;
  619.  
  620.   {Save the DOS internal variables table}
  621.   SaveDOSTable;
  622.  
  623.   {Save the DOS internal file management table}
  624.   SaveFileTable;
  625.  
  626.   {Save the PSP of COMMAND.COM}
  627.   SaveCommandPSP;
  628.  
  629.   {Save the location that NetWare may patch in COMMAND.COM}
  630.   SaveCommandPatch;
  631.  
  632.   {Save the master copy of the DOS environment}
  633.   SaveDosEnvironment;
  634.  
  635.   {Save the state of the communications controllers}
  636.   SaveCommState;
  637.  
  638.   {Save list of allocated memory control blocks}
  639.   SaveAllocatedMcbs;
  640.  
  641.   {Close mark file}
  642.   Close(MarkF);
  643.   CheckWriteError;
  644.  
  645.   {Move ID strings into place}
  646.   SaveIDStrings;
  647.  
  648.   if not Quiet then
  649.     WriteLn('Stored mark information in ', MarkName);
  650.  
  651.   {$IFDEF MeasureStack}
  652.   I := 0;
  653.   while I < SPtr-16 do
  654.     if mem[sseg:i] <> $AA then begin
  655.       writeln('unused stack ', i, ' bytes');
  656.       I := SPtr;
  657.     end else
  658.       inc(I);
  659.   {$ENDIF}
  660.  
  661.   Flush(Output);
  662.  
  663.   {Close file handles}
  664.   CloseStandardFiles;
  665.  
  666.   {Go resident}
  667.   asm
  668.     mov dl,byte ptr markname
  669.     xor dh,dh
  670.     add dx,$0090
  671.     mov cl,4
  672.     shr dx,cl
  673.     mov ax,$3100
  674.     int $21
  675.   end;
  676. end.
  677.