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