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