home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / INFO / SYSUTL / TSRSRC31.ZIP / MEMU.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1991-11-04  |  18.6 KB  |  689 lines

  1. {**************************************************************************
  2. *   MEMU - utility unit for TSR Utilities.                                *
  3. *   Copyright (c) 1991 Kim Kokkonen, TurboPower Software.                 *
  4. *   May be freely distributed and used but not sold except by permission. *
  5. *                                                                         *
  6. *   Version 3.0 9/24/91                                                   *
  7. *     first release                                                       *
  8. *   Version 3.1 11/4/91                                                   *
  9. *     update for new WATCH identification behavior                        *
  10. *     update HasEnvironment for programs that shrink env size to 0        *
  11. ***************************************************************************}
  12.  
  13. {$R-,S-,I-,V-,B-,F-,A-,E-,N-,G-,X-}
  14.  
  15. unit MemU;
  16.   {-Miscellaneous memory functions needed for TSR Utilities}
  17.  
  18. interface
  19.  
  20. const
  21.   {Offsets into resident copy of WATCH.COM for data storage}
  22.   WatchOfs = $80;             {Location of length of command line}
  23.   WatchOffset = $81;          {Location of start of command line}
  24.   NextChange = $104;          {Data structures within WATCH}
  25.  
  26.   {!!!!!! Following may change when WATCH reassembled. Check WATCH.MAP !!!!!}
  27.   ChangeVectors = $370;
  28.   OrigVectors = $770;
  29.  
  30.   WatchId = 'TSR WATCHER';    {ID placed in WATCH command line}
  31.   MaxChanges = 128;           {Maximum number of vector changes stored in WATCH}
  32.  
  33. const
  34.   MaxBlocks = 256;            {Max number of DOS allocation blocks supported}
  35.  
  36. const
  37.   RBR = 0; {Receiver buffer register offset}
  38.   THR = 0; {Transmitter buffer register offset}
  39.   BRL = 0; {Baud rate low}
  40.   BRH = 1; {Baud rate high}
  41.   IER = 1; {Interrupt enable register}
  42.   IIR = 2; {Interrupt identification register}
  43.   LCR = 3; {Line control register}
  44.   MCR = 4; {Modem control register}
  45.   LSR = 5; {Line status register}
  46.   MSR = 6; {Modem status register}
  47.  
  48. type
  49.   OS =
  50.     record
  51.       O, S : Word;
  52.     end;
  53.  
  54.   NameArray = array[1..8] of Char;
  55.  
  56.   McbPtr = ^Mcb;
  57.   Mcb =
  58.     record
  59.       Id : Char;
  60.       Psp : Word;
  61.       Len : Word;
  62.       Unused : array[1..3] of Byte;
  63.       Name : NameArray;
  64.     end;
  65.  
  66.   Block =
  67.   record                      {Store info about each memory block}
  68.     mcb : Word;
  69.     psp : Word;
  70.     releaseIt : Boolean;
  71.   end;
  72.  
  73.   BlockType = 0..MaxBlocks;
  74.   BlockArray = array[1..MaxBlocks] of Block;
  75.  
  76.   McbGroup =
  77.   record
  78.     Count : Word;
  79.     Mcbs : array[1..MaxBlocks] of
  80.            record
  81.              mcb : Word;
  82.              psp : Word;
  83.            end;
  84.   end;
  85.  
  86.   ChangeBlock =
  87.   record                      {Store info about each vector takeover}
  88.     VecNum : byte;
  89.     case ID : byte of
  90.       0, 1 : (VecOfs, VecSeg : Word);
  91.       2    : (SaveCode : array[1..6] of byte);
  92.       $FF  : (PspAdd : Word);
  93.   end;
  94.   {
  95.   ID is interpreted as follows:
  96.     00 = ChangeBlock holds the new pointer for vector vecnum
  97.     01 = ChangeBlock holds pointer for vecnum but the block is disabled
  98.     02 = ChangeBlock holds the code underneath the vector patch
  99.     FF = ChangeBlock holds the segment of a new PSP
  100.   }
  101.   ChangeArray = array[0..MaxChanges] of ChangeBlock;
  102.  
  103.   {Structure of a device driver header}
  104.   DeviceHeader =
  105.     record
  106.       NextHeaderOffset : Word;    {Offset address of next device in chain}
  107.       NextHeaderSegment : Word;   {Segment address of next device in chain}
  108.       Attributes : Word;          {Device attributes}
  109.       StrategyEntPt : Word;       {Offset in current segment - strategy}
  110.       InterruptEntPt : Word;      {Offset in current segment - interrupt}
  111.       DeviceName : array[1..8] of Char; {Name of the device}
  112.     end;
  113.   DeviceHeaderPtr = ^DeviceHeader;
  114.   DeviceArray = array[1..256] of DeviceHeaderPtr;
  115.  
  116.   FileRec =
  117.     record
  118.       OpenCnt : Word;
  119.       OpenMode : Word;
  120.       Attribute : Byte;
  121.       Unknown1 : Word;
  122.       DCB : Pointer;
  123.       InitCluster : Word;
  124.       Time : Word;
  125.       Date : Word;
  126.       Size : LongInt;
  127.       Pos : LongInt;
  128.       BeginCluster : Word;
  129.       CurCluster : Word;
  130.       Block : Word;
  131.       Unknown2 : Byte;            {Varies with DOS version beyond here}
  132.       Name : array[0..7] of Char;
  133.       Ext : array[0..2] of Char;
  134.       Unknown3 : array[0..5] of Byte;
  135.       Owner : Word;
  136.       Unknown4 : Word;
  137.     end;
  138.  
  139.   SftRecPtr = ^SftRec;
  140.   SftRec =
  141.     record
  142.       Next : SftRecPtr;
  143.       Count : Word;
  144.       Files : array[1..255] of FileRec;
  145.     end;
  146.  
  147.   DosRec =
  148.     record
  149.       McbSeg : Word;
  150.       FirstDPB : Pointer;
  151.       FirstSFT : SftRecPtr;
  152.       ClockDriver : Pointer;
  153.       ConDriver : Pointer;
  154.       MaxBlockBytes : Word;
  155.       CachePtr : Pointer;
  156.       DriveTable : Pointer;
  157.       FcbTable : Pointer;
  158.       ProtectedFcbCount : Word;
  159.       BlockDevices : Byte;
  160.       LastDrive : Byte;
  161.       NullDevice : DeviceHeader;
  162.       JoinedDrives : Byte;           {Following valid DOS 4.0 or later}
  163.       SpecialProgOfs : Word;
  164.       IFSPtr : Pointer;
  165.       IFSList : Pointer;
  166.       BuffersX : Word;
  167.       BuffersY : Word;
  168.       BootDrive : Byte;
  169.       Unknown1 : Byte;
  170.       ExtMemSize : Word;
  171.     end;
  172.   DosRecPtr = ^DosRec;
  173.  
  174.   ComRec =  {State of the communications system}
  175.     record
  176.       Base : Word;
  177.       IERReg : Byte;
  178.       LCRReg : Byte;
  179.       MCRReg : Byte;
  180.       BRLReg : Byte;
  181.       BRHReg : Byte;
  182.     end;
  183.   ComArray = array[1..2] of ComRec;
  184.  
  185. const
  186.   Digits : array[0..$F] of Char = '0123456789ABCDEF';
  187.   DosDelimSet : set of Char = ['\', ':', #0];
  188.  
  189. var
  190.   DosV : Byte;       {Major DOS version number}
  191.   DosList : Pointer; {Pointer to DOS list of lists}
  192.   Mcb1 : McbPtr;     {First MCB in system}
  193.  
  194. function GetDosListPtr : Pointer;
  195.   {-Return address of DOS list of lists}
  196.  
  197. function GetUmbLinkStatus : Boolean;
  198.   {-Return status of DOS 5 upper memory block link}
  199.  
  200. function SetUmbLinkStatus(On : Boolean) : Word;
  201.   {-Change state of DOS 5 upper memory block link}
  202.  
  203. function DosVersion : Byte;
  204.   {-Return major DOS version number}
  205.  
  206. function TopOfMemSeg : Word;
  207.   {-Return segment of top of memory}
  208.  
  209. function HiMemAvailable(DosV : Byte) : Boolean;
  210.   {-Return True if HiMem is available}
  211.  
  212. function HexB(B : Byte) : String;
  213.   {-Return hex string for byte}
  214.  
  215. function HexW(W : Word) : String;
  216.   {-Return hex string for word}
  217.  
  218. function HexPtr(P : Pointer) : string;
  219.   {-Return hex string for pointer}
  220.  
  221. function StUpcase(s : String) : String;
  222.   {-Return the uppercase string}
  223.  
  224. function JustFilename(PathName : String) : String;
  225.   {-Return just the filename of a pathname}
  226.  
  227. function JustName(PathName : String) : String;
  228.   {-Return just the name (no extension, no path) of a pathname}
  229.  
  230. function Extend(S : String; Len : Byte) : String;
  231.   {-Truncate or pad S to length Len}
  232.  
  233. function SmartExtend(S : String; Len : Byte) : String;
  234.   {-Truncate or pad S to length Len; end with '...' if truncated}
  235.  
  236. function Asc2Str(Name : NameArray) : String;
  237.   {-Convert array[1..8] of char to string}
  238.  
  239. procedure StripNonAscii(var S : String);
  240.   {-Return an empty string if input contains non-ASCII characters}
  241.  
  242. function CommaIze(L : LongInt; Width : Byte) : String;
  243.   {-Convert L to a string and add commas for thousands}
  244.  
  245. function HasEnvironment(M : McbPtr) : Boolean;
  246.   {-Return True if M has an associated environment block}
  247.  
  248. function NameFromEnv(M : McbPtr) : String;
  249.   {-Return M's name from its environment (already known to exist)}
  250.  
  251. function NameFromMcb(M : McbPtr) : String;
  252.   {-Return name from the Mcb (DOS 4+ only)}
  253.  
  254. function MasterCommandSeg : Word;
  255.   {-Return PSP segment of master COMMAND.COM}
  256.  
  257. function WatchPspSeg : Word;
  258.   {-Find copy of WATCH.COM in memory, returning its PSP segment or zero}
  259.  
  260. procedure FindTheBlocks(var Blocks : BlockArray;
  261.                         var BlockMax : BlockType;
  262.                         var StartMcb : Word;
  263.                         var CommandSeg : Word);
  264.   {-Scan memory for the allocated memory blocks}
  265.  
  266. procedure StuffKey(W : Word);
  267.   {-Stuff one key into the keyboard buffer}
  268.  
  269. procedure StuffKeys(Keys : string; ClearFirst : Boolean);
  270.   {-Stuff up to 16 keys into keyboard buffer}
  271.  
  272. function ExistFile(path : String) : Boolean;
  273.   {-Return true if file exists}
  274.  
  275.   {=======================================================================}
  276.  
  277. implementation
  278.  
  279.   function GetDosListPtr : Pointer; Assembler;
  280.     {-Return address of DOS list of lists}
  281.   asm
  282.     mov     ah,$52
  283.     int     $21
  284.     mov     dx,es
  285.     mov     ax,bx
  286.   end;
  287.  
  288.   function GetUmbLinkStatus : Boolean; Assembler;
  289.     {-Return status of DOS 5 upper memory block link}
  290.   asm
  291.     mov     ax,$5802
  292.     int     $21
  293.   end;
  294.  
  295.   function SetUmbLinkStatus(On : Boolean) : Word; Assembler;
  296.     {-Change state of DOS 5 upper memory block link}
  297.   asm
  298.     mov     ax,$5803
  299.     mov     bl,On
  300.     xor     bh,bh
  301.     int     $21
  302.     jc      @1
  303.     xor     ax,ax
  304. @1:
  305.   end;
  306.  
  307.   function DosVersion : Byte; Assembler;
  308.     {-Return major DOS version number}
  309.   asm
  310.     mov     ah,$30
  311.     int     $21
  312.   end;
  313.  
  314.   function TopOfMemSeg : Word;
  315.     {-Return segment of top of memory}
  316.   var
  317.     KBRAM : Word;
  318.   begin
  319.     asm
  320.       int $12
  321.       mov KBRAM,ax
  322.     end;
  323.     TopOfMemSeg := KBRAM shl 6;
  324.   end;
  325.  
  326.   function HiMemAvailable(DosV : Byte) : Boolean;
  327.     {-Return True if HiMem is available}
  328.   begin
  329.     HiMemAvailable := (DosV >= 5) and (DosV < 10);
  330.   end;
  331.  
  332.   function HexB(B : Byte) : String;
  333.     {-Return hex string for byte}
  334.   begin
  335.     HexB[0] := #2;
  336.     HexB[1] := Digits[B shr 4];
  337.     HexB[2] := Digits[B and $F];
  338.   end;
  339.  
  340.   function HexW(W : Word) : String;
  341.     {-Return hex string for word}
  342.   begin
  343.     HexW[0] := #4;
  344.     HexW[1] := Digits[Hi(W) shr 4];
  345.     HexW[2] := Digits[Hi(W) and $F];
  346.     HexW[3] := Digits[Lo(W) shr 4];
  347.     HexW[4] := Digits[Lo(W) and $F];
  348.   end;
  349.  
  350.   function HexPtr(P : Pointer) : string;
  351.     {-Return hex string for pointer}
  352.   begin
  353.     HexPtr := HexW(OS(P).S)+':'+HexW(OS(P).O);
  354.   end;
  355.  
  356.   function StUpcase(s : String) : String;
  357.     {-Return the uppercase string}
  358.   var
  359.     i : Byte;
  360.   begin
  361.     for i := 1 to Length(s) do
  362.       s[i] := UpCase(s[i]);
  363.     StUpcase := s;
  364.   end;
  365.  
  366.   function JustFilename(PathName : String) : String;
  367.     {-Return just the filename of a pathname}
  368.   var
  369.     I : Word;
  370.   begin
  371.     I := Word(Length(PathName))+1;
  372.     repeat
  373.       Dec(I);
  374.     until (PathName[I] in DosDelimSet) or (I = 0);
  375.     JustFilename := Copy(PathName, I+1, 64);
  376.   end;
  377.  
  378.   function JustName(PathName : String) : String;
  379.     {-Return just the name (no extension, no path) of a pathname}
  380.   var
  381.     DotPos : Byte;
  382.   begin
  383.     PathName := JustFilename(PathName);
  384.     DotPos := Pos('.', PathName);
  385.     if DotPos > 0 then
  386.       PathName := Copy(PathName, 1, DotPos-1);
  387.     JustName := PathName;
  388.   end;
  389.  
  390.   function Extend(S : String; Len : Byte) : String;
  391.     {-Truncate or pad S to length Len}
  392.   begin
  393.     if Length(S) < Len then
  394.       FillChar(S[Length(S)+1], Len-Length(S), ' ');
  395.     S[0] := Char(Len);
  396.     Extend := S;
  397.   end;
  398.  
  399.   function SmartExtend(S : String; Len : Byte) : String;
  400.     {-Truncate or pad S to length Len; end with '...' if truncated}
  401.   begin
  402.     if Length(S) > Len then
  403.       SmartExtend := copy(S, 1, Len-3)+'...'
  404.     else
  405.       SmartExtend := Extend(S, Len);
  406.   end;
  407.  
  408.   function Asc2Str(Name : NameArray) : String;
  409.     {-Convert array[1..8] of char to string}
  410.   var
  411.     I : Integer;
  412.   begin
  413.     I := 1;
  414.     while (I <= 8) and (Name[I] <> #0) and (Name[I] <> ' ') do begin
  415.       Asc2Str[I] := Name[I];
  416.       Inc(I);
  417.     end;
  418.     Asc2Str[0] := Char(I-1);
  419.   end;
  420.  
  421.   procedure StripNonAscii(var S : String);
  422.     {-Return an empty string if input contains non-ASCII characters}
  423.   var
  424.     I : Integer;
  425.     Ok : Boolean;
  426.   begin
  427.     Ok := True;
  428.     I := 1;
  429.     while Ok and (I <= Length(S)) do begin
  430.       case S[I] of
  431.         #0..#31, #126..#255 : Ok := False;
  432.       end;
  433.       Inc(I);
  434.     end;
  435.     if not Ok then
  436.       S := '';
  437.   end;
  438.  
  439.   function CommaIze(L : LongInt; Width : Byte) : String;
  440.     {-Convert L to a string and add commas for thousands}
  441.   var
  442.     I : Word;
  443.     Len : Word;
  444.     S : String[19];
  445.   begin
  446.     Str(L, S);
  447.     Len := Length(S);
  448.     I := Len;
  449.     while I > 1 do begin
  450.       if (Len+1-I) mod 3 = 0 then
  451.         insert(',', S, I);
  452.       dec(I);
  453.     end;
  454.     while Length(S) < Width do
  455.       insert(' ', S, 1);
  456.     CommaIze := S;
  457.   end;
  458.  
  459.   function HasEnvironment(M : McbPtr) : Boolean;
  460.     {-Return True if M has an associated environment block}
  461.   var
  462.     N : McbPtr;
  463.     EnvSeg : Word;
  464.     Done : Boolean;
  465.   begin
  466.     EnvSeg := MemW[M^.Psp:$2C];
  467.     N := Mcb1;
  468.     repeat
  469.       if (N^.Psp = M^.Psp) and (N^.Len > 0) and (EnvSeg = OS(N).S+1) then begin
  470.         HasEnvironment := True;
  471.         Exit;
  472.       end;
  473.       Done := (N^.Id = 'Z');
  474.       N := Ptr(OS(N).S+N^.Len+1, 0);
  475.     until Done;
  476.     HasEnvironment := False;
  477.   end;
  478.  
  479.   function NameFromEnv(M : McbPtr) : String;
  480.     {-Return M's name from its environment (already known to exist)}
  481.   type
  482.     CharArray = array[0..32767] of Char;
  483.     CharArrayPtr = ^CharArray;
  484.   var
  485.     E : Word;
  486.     Eptr : CharArrayPtr;
  487.     Name : String[79];
  488.     Nlen : Byte absolute Name;
  489.   begin
  490.     Eptr := Ptr(MemW[M^.Psp:$2C], 0);
  491.     E := 0;
  492.     repeat
  493.       if Eptr^[E] = #0 then begin
  494.         Inc(E);
  495.         if Eptr^[E] = #0 then begin
  496.           {found end of environment}
  497.           Inc(E, 3);
  498.           Nlen := 0;
  499.           while (Nlen < 63) and (Eptr^[E] <> #0) do begin
  500.             Inc(Nlen);
  501.             Name[Nlen] := Eptr^[E];
  502.             Inc(E);
  503.           end;
  504.           StripNonAscii(Name);
  505.           NameFromEnv := JustName(Name);
  506.           Exit;
  507.         end;
  508.       end;
  509.       Inc(E);
  510.     until (E > 32767);
  511.     NameFromEnv := '';
  512.   end;
  513.  
  514.   function NameFromMcb(M : McbPtr) : String;
  515.     {-Return name from the Mcb (DOS 4+ only)}
  516.   var
  517.     Name : String[79];
  518.   begin
  519.     Name := Asc2Str(M^.Name);
  520.     StripNonAscii(Name);
  521.     NameFromMcb := Name;
  522.   end;
  523.  
  524.   function MasterCommandSeg : Word;
  525.     {-Return PSP segment of master COMMAND.COM}
  526.   var
  527.     curmcb : mcbptr;
  528.     mseg : word;
  529.     par : word;
  530.   begin
  531.     {First block}
  532.     curmcb := mcb1;
  533.     repeat
  534.       curmcb := ptr(OS(curmcb).s+curmcb^.len+1, 0);
  535.       par := memw[curmcb^.psp:$16];
  536.       mseg := OS(curmcb).s;
  537.       if (par = curmcb^.psp) and (mseg+1 = curmcb^.psp) then begin
  538.         MasterCommandSeg := curmcb^.psp;
  539.         exit;
  540.       end;
  541.     until curmcb^.id = 'Z';
  542.     MasterCommandSeg := 0;
  543.   end;
  544.  
  545.   function WatchPspSeg : Word; assembler;
  546.     {-Find copy of WATCH.COM in memory, returning its PSP segment or zero}
  547.   asm
  548.     mov ax,$7761     {id call to WATCH}
  549.     int $21
  550.     jc @1
  551.     cmp ax,$6177     {WATCH flips ah and al if installed}
  552.     jne @1
  553.     mov ax,bx        {WATCH returns its own CS in BX}
  554.     jmp @2
  555. @1: xor ax,ax        {not installed}
  556. @2:
  557.   end;
  558.  
  559.   procedure FindTheBlocks(var Blocks : BlockArray;
  560.                           var BlockMax : BlockType;
  561.                           var StartMcb : Word;
  562.                           var CommandSeg : Word);
  563.     {-Scan memory for the allocated memory blocks}
  564.   const
  565.     MidBlockID = $4D;         {Byte DOS uses to identify part of MCB chain}
  566.     EndBlockID = $5A;         {Byte DOS uses to identify last block of MCB chain}
  567.   var
  568.     mcbSeg : Word;         {Segment address of current MCB}
  569.     nextSeg : Word;        {Computed segment address for the next MCB}
  570.     gotFirst : Boolean;       {True after first MCB is found}
  571.     gotLast : Boolean;        {True after last MCB is found}
  572.     idbyte : Byte;            {Byte that DOS uses to identify an MCB}
  573.  
  574.     procedure StoreTheBlock(var mcbSeg, nextSeg : Word;
  575.                             var gotFirst, gotLast : Boolean);
  576.       {-Store information regarding the memory block}
  577.     var
  578.       nextID : Byte;
  579.       PspAdd : Word;       {Segment address of the current PSP}
  580.       mcbLen : Word;       {Size of the current memory block in paragraphs}
  581.  
  582.     begin
  583.  
  584.       PspAdd := MemW[mcbSeg:1]; {Address of program segment prefix for MCB}
  585.       mcbLen := MemW[mcbSeg:3]; {Size of the MCB in paragraphs}
  586.       nextSeg := Succ(mcbSeg+mcbLen); {Where the next MCB should be}
  587.       nextID := Mem[nextSeg:0];
  588.  
  589.       if gotLast or (nextID = EndBlockID) or (nextID = MidBlockID) then begin
  590.         inc(BlockMax);
  591.         gotFirst := True;
  592.         with Blocks[BlockMax] do begin
  593.           mcb := mcbSeg;
  594.           psp := PspAdd;
  595.         end;
  596.         {Store master COMMAND.COM segment}
  597.         if CommandSeg = 0 then
  598.           if (McbSeg+1 = PspAdd) and (MemW[PspAdd:$16] = PspAdd) then
  599.             CommandSeg := PspAdd;
  600.       end;
  601.     end;
  602.  
  603.   begin
  604.  
  605.     {Initialize}
  606.     StartMCB := OS(MCB1).S;
  607.     mcbSeg := StartMCB;
  608.     gotFirst := False;
  609.     gotLast := False;
  610.     BlockMax := 0;
  611.     CommandSeg := 0;
  612.  
  613.     {Scan all memory until the last block is found}
  614.     repeat
  615.       idbyte := Mem[mcbSeg:0];
  616.       if idbyte = MidBlockID then begin
  617.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  618.         if gotFirst then
  619.           mcbSeg := nextSeg
  620.         else
  621.           inc(mcbSeg);
  622.       end else if gotFirst and (idbyte = EndBlockID) then begin
  623.         gotLast := True;
  624.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  625.       end else
  626.         {Start block was invalid}
  627.         gotLast := True;
  628.     until gotLast;
  629.   end;
  630.  
  631.   const
  632.     KbdStart = $1E;
  633.     KbdEnd = $3C;
  634.   var
  635.     KbdHead : Word absolute $40 : $1A;
  636.     KbdTail : Word absolute $40 : $1C;
  637.  
  638.   procedure StuffKey(W : Word);
  639.     {-Stuff one key into the keyboard buffer}
  640.   var
  641.     SaveKbdTail : Word;
  642.   begin
  643.     SaveKbdTail := KbdTail;
  644.     if KbdTail = KbdEnd then
  645.       KbdTail := KbdStart
  646.     else
  647.       Inc(KbdTail, 2);
  648.     if KbdTail = KbdHead then
  649.       KbdTail := SaveKbdTail
  650.     else
  651.       MemW[$40:SaveKbdTail] := W;
  652.   end;
  653.  
  654.   procedure StuffKeys(Keys : string; ClearFirst : Boolean);
  655.     {-Stuff up to 16 keys into keyboard buffer}
  656.   var
  657.     Len : Byte;
  658.     I : Byte;
  659.   begin
  660.     if ClearFirst then
  661.       KbdTail := KbdHead;
  662.     Len := Length(Keys);
  663.     if Len > 16 then
  664.       Len := 16;
  665.     for I := 1 to Length(Keys) do
  666.       StuffKey(Ord(Keys[I]));
  667.   end;
  668.  
  669.   function ExistFile(path : String) : Boolean;
  670.     {-Return true if file exists}
  671.   var
  672.     f : file;
  673.   begin
  674.     Assign(f, path);
  675.     Reset(f);
  676.     if IoResult = 0 then begin
  677.       ExistFile := True;
  678.       Close(f);
  679.     end else
  680.       ExistFile := False;
  681.   end;
  682.  
  683. begin
  684.   DosV := DosVersion;
  685.   DosList := GetDosListPtr;     {pointer to dos list of lists}
  686.   Mcb1 := Ptr(MemW[OS(DosList).S:OS(DosList).O-2], 0); {first Mcb}
  687. end.
  688.  
  689.