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