home *** CD-ROM | disk | FTP | other *** search
/ World of Shareware - Software Farm 2 / wosw_2.zip / wosw_2 / PASCAL / TSRUTILS.ZIP / MARKNET.PAS < prev    next >
Pascal/Delphi Source File  |  1989-05-04  |  18KB  |  660 lines

  1. {**************************************************************************
  2. *   MARKNET - stores system information in a file for later restoration.  *
  3. *   Copyright (c) 1986,1989 Kim Kokkonen, TurboPower Software.            *
  4. *   May be distributed freely, but not for a profit except with written   *
  5. *   permission from TurboPower Software.                                  *
  6. ***************************************************************************
  7. *   Version 2.7 3/4/89                                                    *
  8. *     first public release                                                *
  9. *     (based on FMARK 2.6)                                                *
  10. *   Version 2.8 3/10/89                                                   *
  11. *     store the DOS environment                                           *
  12. *     store information about the async ports                             *
  13. *   Version 2.9 5/4/89                                                    *
  14. *     for consistency                                                     *
  15. ***************************************************************************
  16. *   Telephone: 408-438-8608, CompuServe: 72457,2131.                      *
  17. *   Requires Turbo version 5 to compile.                                  *
  18. ***************************************************************************}
  19.  
  20. {$R-,S-,I-}
  21. {.$DEFINE Debug} {Activate for status messages}
  22.  
  23. program MarkNet;
  24.  
  25. uses
  26.   Dos;
  27.  
  28. const
  29.   Version = '2.9';
  30.   NmarkID = 'MN2.9 TSR';          {Marking string for TSR file mark}
  31.   NetMarkID = 'MN29';             {ID at start of net mark file}
  32.  
  33.   NmarkOffset = $60;              {Where NmarkID is found in MARKNET TSR}
  34.  
  35.   MaxHandles = 32;                {Max number of EMS allocation blocks supported}
  36.   EMSinterrupt = $67;             {The vector used by the expanded memory manager}
  37.  
  38.   MarkFOpen : Boolean = False;    {True while mark file is open}
  39.  
  40.   Digits : array[0..$F] of Char = '0123456789ABCDEF';
  41.  
  42.   RBR = 0; {Receiver buffer register offset}
  43.   THR = 0; {Transmitter buffer register offset}
  44.   BRL = 0; {Baud rate low}
  45.   BRH = 1; {Baud rate high}
  46.   IER = 1; {Interrupt enable register}
  47.   IIR = 2; {Interrupt identification register}
  48.   LCR = 3; {Line control register}
  49.   MCR = 4; {Modem control register}
  50.   LSR = 5; {Line status register}
  51.   MSR = 6; {Modem status register}
  52.  
  53. type
  54.   DeviceHeader =
  55.     record
  56.       NextHeaderOffset : Word;    {Offset address of next device in chain}
  57.       NextHeaderSegment : Word;   {Segment address of next device in chain}
  58.       Attributes : Word;          {Device attributes}
  59.       StrategyEntPt : Word;       {Offset in current segment - strategy}
  60.       InterruptEntPt : Word;      {Offset in current segment - interrupt}
  61.       DeviceName : array[1..8] of Char; {Name of the device}
  62.     end;
  63.  
  64.   SO =
  65.     record
  66.       O, S : Word;
  67.     end;
  68.  
  69.   FileRec =
  70.     record
  71.       OpenCnt : Word;
  72.       OpenMode : Word;
  73.       Attribute : Byte;
  74.       Unknown1 : Word;
  75.       DCB : Pointer;
  76.       InitCluster : Word;
  77.       Time : Word;
  78.       Date : Word;
  79.       Size : LongInt;
  80.       Pos : LongInt;
  81.       BeginCluster : Word;
  82.       CurCluster : Word;
  83.       Block : Word;
  84.       Unknown2 : Byte;            {Varies with DOS version below here}
  85.       Name : array[0..7] of Char;
  86.       Ext : array[0..2] of Char;
  87.       Unknown3 : array[0..5] of Byte;
  88.       Owner : Word;
  89.       Unknown4 : Word;
  90.     end;
  91.  
  92.   SftRecPtr = ^SftRec;
  93.   SftRec =
  94.     record
  95.       Next : SftRecPtr;
  96.       Count : Word;
  97.       Files : array[1..255] of FileRec;
  98.     end;
  99.  
  100.   DosRec =
  101.     record
  102.       McbSeg : Word;
  103.       FirstDPB : Pointer;
  104.       FirstSFT : SftRecPtr;
  105.       ClockDriver : Pointer;
  106.       ConDriver : Pointer;
  107.       MaxBlockBytes : Word;
  108.       CachePtr : Pointer;
  109.       DriveTable : Pointer;
  110.       Unknown2 : Pointer;
  111.       Unknown3 : Word;
  112.       BlockDevices : Byte;
  113.       LastDrive : Byte;
  114.       NullDevice : DeviceHeader;
  115.     end;
  116.  
  117.   HandlePageRecord =
  118.     record
  119.       Handle : Word;
  120.       NumPages : Word;
  121.     end;
  122.  
  123.   PageArray = array[1..MaxHandles] of HandlePageRecord;
  124.   PageArrayPtr = ^PageArray;
  125.  
  126. var
  127.   MarkName : String[79];          {Name of mark file}
  128.  
  129.   Regs : Registers;               {Machine registers for MS-DOS calls}
  130.   DevicePtr : ^DeviceHeader;      {Pointer to the next device header}
  131.   DeviceSegment : Word;           {Current device segment}
  132.   DeviceOffset : Word;            {Current device offset}
  133.   MarkF : file;                   {Dump file}
  134.   DosPtr : ^DosRec;               {Pointer to internal DOS table}
  135.   DosTableSize : Word;            {Bytes saved in DOS table}
  136.   CommandSeg : Word;              {PSP segment of primary COMMAND.COM}
  137.   CommandPsp : array[1..$100] of Byte;
  138.   FileTableA : array[1..5] of SftRecPtr;
  139.   FileTableCnt : Word;
  140.   FileRecSize : Word;
  141.   EmsHandles : Word;
  142.   EmsPages : PageArrayPtr;
  143.  
  144.   SaveExit : Pointer;
  145.  
  146.   {$F+}
  147.   procedure ExitHandler;
  148.   begin
  149.     ExitProc := SaveExit;
  150.     if MarkFOpen then begin
  151.       Close(MarkF);
  152.       if IoResult = 0 then
  153.         Erase(MarkF);
  154.     end;
  155.     {Turbo will swap back, so undo what we've done already}
  156.     SwapVectors;
  157.   end;
  158.   {$F-}
  159.  
  160.   procedure Abort(Msg : String);
  161.     {-Halt in case of error}
  162.   begin
  163.     WriteLn(Msg);
  164.     Halt(255);
  165.   end;
  166.  
  167.   function HexW(W : Word) : string;
  168.     {-Return hex string for word}
  169.   begin
  170.     HexW[0] := #4;
  171.     HexW[1] := Digits[hi(W) shr 4];
  172.     HexW[2] := Digits[hi(W) and $F];
  173.     HexW[3] := Digits[lo(W) shr 4];
  174.     HexW[4] := Digits[lo(W) and $F];
  175.   end;
  176.  
  177.   function HexPtr(P : Pointer) : string;
  178.     {-Return hex string for pointer}
  179.   begin
  180.     HexPtr := HexW(SO(P).S)+':'+HexW(SO(P).O);
  181.   end;
  182.  
  183.   function StUpcase(S : String) : string;
  184.     {-Return uppercase for string}
  185.   var
  186.     I : Integer;
  187.   begin
  188.     for I := 1 to Length(S) do
  189.       S[I] := Upcase(S[I]);
  190.     StUpcase := S;
  191.   end;
  192.  
  193.   procedure GetDosPtr;
  194.     {-Return pointer to DOS internal variables table}
  195.   begin
  196.     with Regs do begin
  197.       AH := $52;
  198.       MsDos(Regs);
  199.       Dec(BX, 2);
  200.       DosPtr := Ptr(ES, BX);
  201.     end;
  202.   end;
  203.  
  204.   procedure FindDevChain;
  205.     {-Return segment, offset and pointer to NUL device}
  206.   begin
  207.     GetDosPtr;
  208.     DevicePtr := @DosPtr^.NullDevice;
  209.     DeviceSegment := SO(DevicePtr).S;
  210.     DeviceOffset := SO(DevicePtr).O;
  211.   end;
  212.  
  213.   procedure CheckWriteError;
  214.     {-Check for errors writing to mark file}
  215.   begin
  216.     if IoResult = 0 then
  217.       Exit;
  218.     Abort('Error writing to '+MarkName);
  219.   end;
  220.  
  221.   function EMSpresent : Boolean;
  222.     {-Return true if EMS memory manager is present}
  223.   var
  224.     F : file;
  225.   begin
  226.     {"file handle" defined by the expanded memory manager at installation}
  227.     Assign(F, 'EMMXXXX0');
  228.     Reset(F);
  229.     if IoResult = 0 then begin
  230.       EMSpresent := True;
  231.       Close(F);
  232.     end else
  233.       EMSpresent := False;
  234.   end;
  235.  
  236.   procedure EMSpageMap(var PageMap : PageArray; var EmsHandles : Word);
  237.     {-Return an array of the allocated EMS memory blocks}
  238.   begin
  239.     Regs.AH := $4D;
  240.     Regs.ES := Seg(PageMap);
  241.     Regs.DI := Ofs(PageMap);
  242.     Regs.BX := 0;
  243.     Intr(EMSinterrupt, Regs);
  244.     if Regs.AH <> 0 then
  245.       EmsHandles := 0
  246.     else
  247.       EmsHandles := Regs.BX;
  248.   end;
  249.  
  250.   procedure SaveStandardInfo;
  251.     {-Save the ID string, the vectors, and so on}
  252.   type
  253.     IDArray = array[1..4] of Char;
  254.   var
  255.     ID : IDArray;
  256.   begin
  257.     {Write the ID string}
  258.     {$IFDEF Debug}
  259.     WriteLn('Writing mark file ID string');
  260.     {$ENDIF}
  261.     ID := NetMarkID;
  262.     BlockWrite(MarkF, ID, SizeOf(IDArray));
  263.     CheckWriteError;
  264.  
  265.     {Write the start address of the device chain}
  266.     {$IFDEF Debug}
  267.     WriteLn('Writing null device address');
  268.     {$ENDIF}
  269.     BlockWrite(MarkF, DevicePtr, SizeOf(Pointer));
  270.     CheckWriteError;
  271.  
  272.     {Write the vector table}
  273.     {$IFDEF Debug}
  274.     WriteLn('Writing interrupt vector table');
  275.     {$ENDIF}
  276.     BlockWrite(MarkF, Mem[0:0], 1024);
  277.     CheckWriteError;
  278.  
  279.     {Write miscellaneous save areas}
  280.     {$IFDEF Debug}
  281.     WriteLn('Writing EGA save table');
  282.     {$ENDIF}
  283.     BlockWrite(MarkF, Mem[$40:$A8], 8); {EGA save table}
  284.     CheckWriteError;
  285.     {$IFDEF Debug}
  286.     WriteLn('Writing interapplications communication area');
  287.     {$ENDIF}
  288.     BlockWrite(MarkF, Mem[$40:$F0], 16); {Interapplications communication area}
  289.     CheckWriteError;
  290.     {$IFDEF Debug}
  291.     WriteLn('Writing parent PSP segment');
  292.     {$ENDIF}
  293.     BlockWrite(MarkF, Mem[PrefixSeg:$16], 2); {Parent's PSP segment}
  294.     CheckWriteError;
  295.  
  296.     {Write EMS information}
  297.     if EMSpresent then begin
  298.       GetMem(EmsPages, 2048);
  299.       EMSpageMap(EmsPages^, EmsHandles);
  300.     end else
  301.       EmsHandles := 0;
  302.     {$IFDEF Debug}
  303.     WriteLn('Writing EMS handle information');
  304.     {$ENDIF}
  305.     BlockWrite(MarkF, EmsHandles, SizeOf(Word));
  306.     if EmsHandles <> 0 then
  307.       BlockWrite(MarkF, EmsPages^, 4*EmsHandles);
  308.     CheckWriteError;
  309.   end;
  310.  
  311.   procedure SaveDevChain;
  312.     {-Save the device driver chain}
  313.   begin
  314.     {$IFDEF Debug}
  315.     WriteLn('Saving device driver chain');
  316.     {$ENDIF}
  317.     while SO(DevicePtr).O <> $FFFF do begin
  318.       BlockWrite(MarkF, DevicePtr^, SizeOf(DeviceHeader));
  319.       CheckWriteError;
  320.       with DevicePtr^ do
  321.         DevicePtr := Ptr(NextHeaderSegment, NextHeaderOffset);
  322.     end;
  323.   end;
  324.  
  325.   procedure BufferFileTable;
  326.     {-Save an image of the system file table}
  327.   var
  328.     S : SftRecPtr;
  329.     Size : Word;
  330.   begin
  331.     with DosPtr^ do begin
  332.       S := FirstSFT;
  333.       FileTableCnt := 0;
  334.       while SO(S).O <> $FFFF do begin
  335.         Inc(FileTableCnt);
  336.         Size := 6+S^.Count*FileRecSize;
  337.         GetMem(FileTableA[FileTableCnt], Size);
  338.         Move(S^, FileTableA[FileTableCnt]^, Size);
  339.         S := S^.Next;
  340.       end;
  341.     end;
  342.   end;
  343.  
  344.   procedure SaveDOSTable;
  345.     {-Save the DOS internal variables table}
  346.   var
  347.     DosBase : Pointer;
  348.     Size : Word;
  349.   begin
  350.     {$IFDEF Debug}
  351.     WriteLn('Saving DOS data area at 0050:0000');
  352.     {$ENDIF}
  353.     BlockWrite(MarkF, mem[$50:$0], $200);
  354.     CheckWriteError;
  355.     DosBase := Ptr(SO(DosPtr).S, 0);
  356.     {$IFDEF Debug}
  357.     WriteLn('Saving DOS variables table at ', HexPtr(DosBase));
  358.     {$ENDIF}
  359.     Size := SO(DosPtr^.FirstSFT).O;
  360.     BlockWrite(MarkF, Size, SizeOf(Word));
  361.     BlockWrite(MarkF, DosBase^, Size);
  362.     CheckWriteError;
  363.   end;
  364.  
  365.   procedure SaveFileTable;
  366.     {-Save the state of the file table}
  367.   var
  368.     I : Word;
  369.     Size : Word;
  370.   begin
  371.     {$IFDEF Debug}
  372.     WriteLn('Saving DOS file table at ', HexPtr(DosPtr^.FirstSFT));
  373.     {$ENDIF}
  374.     BlockWrite(MarkF, FileTableCnt, SizeOf(Word));
  375.     for I := 1 to FileTableCnt do begin
  376.       Size := 6+FileTableA[I]^.Count*FileRecSize;
  377.       BlockWrite(MarkF, FileTableA[I]^, Size);
  378.     end;
  379.     CheckWriteError;
  380.   end;
  381.  
  382.   procedure BufferCommandPSP;
  383.     {-Save the PSP of COMMAND.COM}
  384.   type
  385.     McbRec =
  386.       record
  387.         ID : Char;
  388.         PSPSeg : Word;
  389.         Len : Word;
  390.       end;
  391.   var
  392.     McbPtr : ^McbRec;
  393.     PspPtr : Pointer;
  394.   begin
  395.     {First block}
  396.     McbPtr := Ptr(DosPtr^.McbSeg, 0);
  397.     {Next block, which is COMMAND.COM}
  398.     McbPtr := Ptr(SO(McbPtr).S+McbPtr^.Len+1, 0);
  399.     CommandSeg := McbPtr^.PSPSeg;
  400.     PspPtr := Ptr(CommandSeg, 0);
  401.     Move(PspPtr^, CommandPsp, $100);
  402.   end;
  403.  
  404.   procedure SaveCommandPSP;
  405.   begin
  406.     {$IFDEF Debug}
  407.     WriteLn('Saving COMMAND.COM PSP at ', HexW(CommandSeg), ':0000');
  408.     {$ENDIF}
  409.     BlockWrite(MarkF, CommandPsp, $100);
  410.     CheckWriteError;
  411.   end;
  412.  
  413.   procedure SaveCommandPatch;
  414.     {-Restore the patch that NetWare applies to command.com}
  415.   label
  416.     ExitPoint;
  417.   const
  418.     Patch : array[0..14] of Char = ':/'#0'_______.___'#0;
  419.   var
  420.     Segm : Word;
  421.     Ofst : Word;
  422.     Indx : Word;
  423.   begin
  424.     for Segm := CommandSeg to PrefixSeg do
  425.       for Ofst := 0 to 15 do begin
  426.         Indx := 0;
  427.         while (Indx <= 14) and (Patch[Indx] = Char(Mem[Segm:Ofst+Indx])) do
  428.           Inc(Indx);
  429.         if Indx > 14 then begin
  430.           {$IFDEF Debug}
  431.           WriteLn('Saving COMMAND patch address at ', HexW(Segm), ':', HexW(Ofst));
  432.           {$ENDIF}
  433.           goto ExitPoint;
  434.         end;
  435.       end;
  436.     Segm := 0;
  437.     Ofst := 0;
  438. ExitPoint:
  439.     BlockWrite(MarkF, Ofst, SizeOf(Word));
  440.     BlockWrite(MarkF, Segm, SizeOf(Word));
  441.     CheckWriteError;
  442.   end;
  443.  
  444.   procedure FindEnv(CommandSeg : Word; var EnvSeg, EnvLen : Word);
  445.     {-Return the segment and length of the master environment}
  446.   var
  447.     Mcb : Word;
  448.   begin
  449.     Mcb := CommandSeg-1;
  450.     EnvSeg := MemW[CommandSeg:$2C];
  451.     if EnvSeg = 0 then
  452.       {Master environment is next block past COMMAND}
  453.       EnvSeg := Commandseg+MemW[Mcb:3]+1;
  454.     EnvLen := MemW[(EnvSeg-1):3] shl 4;
  455.   end;
  456.  
  457.   procedure SaveDosEnvironment;
  458.     {-Save the master copy of the DOS environment}
  459.   var
  460.     EnvSeg : Word;
  461.     EnvLen : Word;
  462.     P : Pointer;
  463.   begin
  464.     FindEnv(CommandSeg, EnvSeg, EnvLen);
  465.     {$IFDEF Debug}
  466.     WriteLn('Saving master environment, ', EnvLen, ' bytes at ', HexW(EnvSeg), ':0000');
  467.     {$ENDIF}
  468.     P := Ptr(EnvSeg, 0);
  469.     BlockWrite(MarkF, EnvLen, SizeOf(Word));
  470.     BlockWrite(MarkF, P^, EnvLen);
  471.     CheckWriteError;
  472.   end;
  473.  
  474.   procedure SaveCommState;
  475.     {-Save the state of the communications controllers}
  476.   var
  477.     PicMask : Byte;
  478.     Com : Byte;
  479.     LCRSave : Byte;
  480.     Base : Word;
  481.     ComPortBase : array[1..2] of Word absolute $40:0; {Com port base addresses}
  482.  
  483.     procedure SaveReg(Offset : Byte);
  484.       {-Save one communications register}
  485.     var
  486.       Reg : Byte;
  487.     begin
  488.       Reg := Port[Base+Offset];
  489.       BlockWrite(MarkF, Reg, SizeOf(Byte));
  490.       CheckWriteError;
  491.     end;
  492.  
  493.   begin
  494.     {$IFDEF Debug}
  495.     WriteLn('Saving communications environment');
  496.     {$ENDIF}
  497.  
  498.     {Save the 8259 interrupt enable mask}
  499.     PicMask := Port[$21];
  500.     BlockWrite(MarkF, PicMask, SizeOf(Byte));
  501.     CheckWriteError;
  502.  
  503.     for Com := 1 to 2 do begin
  504.       Base := ComPortBase[Com];
  505.  
  506.       {Save the Com port base address}
  507.       BlockWrite(MarkF, Base, SizeOf(Word));
  508.       CheckWriteError;
  509.  
  510.       if Base <> 0 then begin
  511.         {Save the rest of the control state}
  512.         SaveReg(IER);             {Interrupt enable register}
  513.         SaveReg(LCR);             {Line control register}
  514.         SaveReg(MCR);             {Modem control register}
  515.         LCRSave := Port[Base+LCR]; {Save line control register}
  516.         Port[Base+LCR] := LCRSave or $80; {Enable baud rate divisor registers}
  517.         SaveReg(BRL);             {Baud rate divisor low}
  518.         SaveReg(BRH);             {Baud rate divisor high}
  519.         Port[Base+LCR] := LCRSave; {Restore line control register}
  520.       end;
  521.     end;
  522.   end;
  523.  
  524.   function CompaqDOS30 : Boolean;
  525.     {-Return true if Compaq DOS 3.0}
  526.   begin
  527.     with Regs do begin
  528.       AH := $34;
  529.       MsDos(Regs);
  530.       CompaqDOS30 := (BX = $19C);
  531.     end;
  532.   end;
  533.  
  534.   procedure ValidateDosVersion;
  535.     {-Assure supported version of DOS and compute size of DOS internal filerec}
  536.   var
  537.     DosVer : Word;
  538.   begin
  539.     DosVer := DosVersion;
  540.     case Lo(DosVer) of
  541.       3 : if (Hi(DosVer) < $0A) and not CompaqDOS30 then
  542.             {IBM DOS 3.0}
  543.             FileRecSize := 56
  544.           else
  545.             {DOS 3.1+ or Compaq DOS 3.0}
  546.             FileRecSize := 53;
  547.       4 : FileRecSize := 59;
  548.     else
  549.       Abort('Requires DOS 3.x or 4.x');
  550.     end;
  551.   end;
  552.  
  553.   procedure SaveIDStrings;
  554.     {-Save identification strings within the PSP}
  555.   var
  556.     ID : String[10];
  557.   begin
  558.     Move(MarkName, Mem[PrefixSeg:$80], Length(MarkName)+1);
  559.     Mem[PrefixSeg:$80+Length(MarkName)+1] := 13;
  560.     ID := NmarkID;
  561.     Move(ID[1], Mem[PrefixSeg:NmarkOffset], Length(ID));
  562.   end;
  563.  
  564.   procedure CloseStandardFiles;
  565.     {-Close all standard files}
  566.   var
  567.     H : Word;
  568.   begin
  569.     with Regs do
  570.     for H := 0 to 4 do begin
  571.       AH := $3E;
  572.       BX := H;
  573.       MsDos(Regs);
  574.     end;
  575.   end;
  576.  
  577. begin
  578.   {Must run with standard DOS vectors}
  579.   SwapVectors;
  580.   SaveExit := ExitProc;
  581.   ExitProc := @ExitHandler;
  582.  
  583.   WriteLn('MARKNET ', Version, ', by TurboPower Software');
  584.  
  585.   {Assure supported version of DOS}
  586.   ValidateDosVersion;
  587.  
  588.   {Assure mark file specified}
  589.   if ParamCount = 0 then
  590.     Abort('Usage: MARKNET NetMarkFile');
  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.   {Open the mark file}
  602.   MarkName := StUpcase(ParamStr(1));
  603.   Assign(MarkF, MarkName);
  604.   Rewrite(MarkF, 1);
  605.   if IoResult <> 0 then
  606.     Abort('Error creating '+MarkName);
  607.   MarkFOpen := True;
  608.  
  609.   {Save ID string, interrupt vectors and other standard state information}
  610.   SaveStandardInfo;
  611.  
  612.   {Save the device driver chain}
  613.   SaveDevChain;
  614.  
  615.   {Save the DOS internal variables table}
  616.   SaveDOSTable;
  617.  
  618.   {Save the DOS internal file management table}
  619.   SaveFileTable;
  620.  
  621.   {Save the PSP of COMMAND.COM}
  622.   SaveCommandPSP;
  623.  
  624.   {Save the location that NetWare may patch in COMMAND.COM}
  625.   SaveCommandPatch;
  626.  
  627.   {Save the master copy of the DOS environment}
  628.   SaveDosEnvironment;
  629.  
  630.   {Save the state of the communications controllers}
  631.   SaveCommState;
  632.  
  633.   {Close mark file}
  634.   Close(MarkF);
  635.   CheckWriteError;
  636.  
  637.   {Move ID strings into place}
  638.   SaveIDStrings;
  639.  
  640.   {Deallocate environment}
  641.   with Regs do begin
  642.     ES := MemW[PrefixSeg:$2C];
  643.     AH := $49;
  644.     MsDos(Regs);
  645.   end;
  646.  
  647.   WriteLn('Stored mark information in ', MarkName);
  648.   Flush(Output);
  649.  
  650.   {Close file handles}
  651.   CloseStandardFiles;
  652.  
  653.   {Go resident}
  654.   with Regs do begin
  655.     dx := ($90+Length(MarkName)) shr 4;
  656.     ax := $3100;
  657.     MsDos(Regs);
  658.   end;
  659. end.
  660.